additional security sanity checks on pairing messages
This commit is contained in:
parent
57bee4b430
commit
084dc188c7
2 changed files with 46 additions and 9 deletions
|
@ -22,6 +22,7 @@ import Utility.Tense
|
||||||
import Network.Multicast
|
import Network.Multicast
|
||||||
import Network.Socket
|
import Network.Socket
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
|
import Data.Char
|
||||||
|
|
||||||
thisThread :: ThreadName
|
thisThread :: ThreadName
|
||||||
thisThread = "PairListener"
|
thisThread = "PairListener"
|
||||||
|
@ -36,16 +37,18 @@ pairListenerThread st dstatus scanremotes urlrenderer = thread $ withSocketsDo $
|
||||||
go sock cache = getmsg sock [] >>= \msg -> case readish msg of
|
go sock cache = getmsg sock [] >>= \msg -> case readish msg of
|
||||||
Nothing -> go sock cache
|
Nothing -> go sock cache
|
||||||
Just m -> do
|
Just m -> do
|
||||||
|
sane <- checkSane msg
|
||||||
(pip, verified) <- verificationCheck m
|
(pip, verified) <- verificationCheck m
|
||||||
=<< (pairingInProgress <$> getDaemonStatus dstatus)
|
=<< (pairingInProgress <$> getDaemonStatus dstatus)
|
||||||
case pairMsgStage m of
|
case (sane, pairMsgStage m) of
|
||||||
PairReq -> do
|
(False, _) -> go sock cache
|
||||||
|
(_, PairReq) -> do
|
||||||
pairReqReceived verified dstatus urlrenderer m
|
pairReqReceived verified dstatus urlrenderer m
|
||||||
go sock $ invalidateCache m cache
|
go sock $ invalidateCache m cache
|
||||||
PairAck -> do
|
(_, PairAck) -> do
|
||||||
pairAckReceived verified pip st dstatus scanremotes m cache
|
pairAckReceived verified pip st dstatus scanremotes m cache
|
||||||
>>= go sock
|
>>= go sock
|
||||||
PairDone -> do
|
(_, PairDone) -> do
|
||||||
pairDoneReceived verified pip st dstatus scanremotes m
|
pairDoneReceived verified pip st dstatus scanremotes m
|
||||||
go sock cache
|
go sock cache
|
||||||
|
|
||||||
|
@ -53,7 +56,8 @@ pairListenerThread st dstatus scanremotes urlrenderer = thread $ withSocketsDo $
|
||||||
- check its UUID against the UUID we have stored. If
|
- check its UUID against the UUID we have stored. If
|
||||||
- they're the same, someone is sending bogus messages,
|
- they're the same, someone is sending bogus messages,
|
||||||
- which could be an attempt to brute force the shared
|
- which could be an attempt to brute force the shared
|
||||||
- secret. -}
|
- secret.
|
||||||
|
-}
|
||||||
verificationCheck m (Just pip) = do
|
verificationCheck m (Just pip) = do
|
||||||
let verified = verifiedPairMsg m pip
|
let verified = verifiedPairMsg m pip
|
||||||
let sameuuid = pairUUID (inProgressPairData pip) == pairUUID (pairMsgData $ m)
|
let sameuuid = pairUUID (inProgressPairData pip) == pairUUID (pairMsgData $ m)
|
||||||
|
@ -66,6 +70,16 @@ pairListenerThread st dstatus scanremotes urlrenderer = thread $ withSocketsDo $
|
||||||
else return (Just pip, verified && sameuuid)
|
else return (Just pip, verified && sameuuid)
|
||||||
verificationCheck _ Nothing = return (Nothing, False)
|
verificationCheck _ Nothing = return (Nothing, False)
|
||||||
|
|
||||||
|
{- Various sanity checks on the content of the message. -}
|
||||||
|
checkSane msg
|
||||||
|
{- Control characters could be used in a
|
||||||
|
- console poisoning attack. -}
|
||||||
|
| any isControl msg || any (`elem` "\r\n") msg = do
|
||||||
|
runThreadState st $
|
||||||
|
warning "illegal control characters in pairing message; ignoring"
|
||||||
|
return False
|
||||||
|
| otherwise = return True
|
||||||
|
|
||||||
{- PairReqs invalidate the cache of recently finished pairings.
|
{- PairReqs invalidate the cache of recently finished pairings.
|
||||||
- This is so that, if a new pairing is started with the
|
- This is so that, if a new pairing is started with the
|
||||||
- same secret used before, a bogus PairDone is not sent. -}
|
- same secret used before, a bogus PairDone is not sent. -}
|
||||||
|
|
|
@ -3,12 +3,23 @@ have some way of pairing devices.
|
||||||
|
|
||||||
## security
|
## security
|
||||||
|
|
||||||
|
Pairing uses its own network protocol, built on top of multicast UDP.
|
||||||
|
|
||||||
It's important that pairing securely verifies that the right host is being
|
It's important that pairing securely verifies that the right host is being
|
||||||
paired with. This is accomplied by having a shared secret be entered on
|
paired with. This is accomplied by having a shared secret be entered on
|
||||||
both the hosts that will be paired. They can then construct messages that
|
both the hosts that will be paired. Hopefully that secret is communicated
|
||||||
the other host can verify using the shared secret, and so know that,
|
securely out of band.
|
||||||
for example, the ssh public key it received belongs to the right host
|
|
||||||
and has not been altered by a man in the middle.
|
(In practice, the security of that communication will vary. To guard against
|
||||||
|
interception, each pairing session pairs exactly two hosts and then forgets
|
||||||
|
the shared secret. So an attacker who tries to reuse an intercepted secret
|
||||||
|
will not succeed in pairing. This does not guard against an intercepted
|
||||||
|
secret that is used before the legitimate parties finish pairing.)
|
||||||
|
|
||||||
|
Each host can construct messages that the other host can verify using the
|
||||||
|
shared secret, and so know that, for example, the ssh public key it
|
||||||
|
received belongs to the right host and has not been altered by a man in the
|
||||||
|
middle.
|
||||||
|
|
||||||
The verification works like this: Take a HMAC SHA1 checksum of the message,
|
The verification works like this: Take a HMAC SHA1 checksum of the message,
|
||||||
using the shared secret as the HMAC key. Include this checksum after the
|
using the shared secret as the HMAC key. Include this checksum after the
|
||||||
|
@ -44,6 +55,18 @@ by replaying these:
|
||||||
|
|
||||||
So replay attacks don't seem to be a problem.
|
So replay attacks don't seem to be a problem.
|
||||||
|
|
||||||
|
So far I've considered security from a third-party attacker, but either the
|
||||||
|
first or second parties in pairing could also be attackers. Presumably they
|
||||||
|
trust each other with access to their files as mediated by
|
||||||
|
[[git-annex-shell]]. However, one could try to get shell access to the
|
||||||
|
other's computer by sending malicious data in a pairing message. So the
|
||||||
|
pairing code always checks every data field's content, for example the ssh
|
||||||
|
public key is rejected if it looks at all unusual. Any control characters
|
||||||
|
in the pairing message cause it to be rejected, to guard against console
|
||||||
|
poisoning attacks. Furthermore, git-annex is careful not to expose data to
|
||||||
|
the shell, and the webapp uses Yesod's type safety to ensure all user input
|
||||||
|
is escaped before going to the browser.
|
||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
|
|
||||||
* pairing over IPV6 only networks does not work. Haskell's
|
* pairing over IPV6 only networks does not work. Haskell's
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue