summaryrefslogtreecommitdiff
path: root/handshake_ntor.go
diff options
context:
space:
mode:
authorYawning Angel <yawning@schwanenlied.me>2014-05-22 18:42:16 +0000
committerYawning Angel <yawning@schwanenlied.me>2014-05-22 18:42:16 +0000
commitfd4e3c7c74ad4d1acb37c43fde8d18786616846a (patch)
tree7430e55ef826ed13a934df0a6d361711cc8308da /handshake_ntor.go
parent7dd875fe4cd214a7678e701adfd2a8bde7882e4d (diff)
Add replay detection to handshakes.
This is done by maintaining a map keyed off the SipHash-2-4 digest of the MAC_C component of the handshake. Collisions, while possible are unlikely in the extreme and are thus treated as replays. In concept this is fairly similar to the ScrambleSuit `replay.py` code, with a few modifications: * There is a upper bound on how large the replay filter can grow. Currently this is set to 102400 entries, though it is unlikely that this limit will be hit. * A doubly linked list is also maintained parallel to the map, so the filter compaction process does not need to iterate over the entire filter.
Diffstat (limited to 'handshake_ntor.go')
-rw-r--r--handshake_ntor.go22
1 files changed, 16 insertions, 6 deletions
diff --git a/handshake_ntor.go b/handshake_ntor.go
index bff500c..3a8b36e 100644
--- a/handshake_ntor.go
+++ b/handshake_ntor.go
@@ -73,6 +73,11 @@ var ErrMarkNotFoundYet = errors.New("handshake: M_[C,S] not found yet")
// connection MUST be dropped.
var ErrInvalidHandshake = errors.New("handshake: Failed to find M_[C,S]")
+// ErrReplayedHandshake is the error returned when the obfs4 handshake fails
+// due it being replayed. This error is fatal and the connection MUST be
+// dropped.
+var ErrReplayedHandshake = errors.New("handshake: Replay detected")
+
// ErrNtorFailed is the error returned when the ntor handshake fails. This
// error is fatal and the connection MUST be dropped.
var ErrNtorFailed = errors.New("handshake: ntor handshake failure")
@@ -242,7 +247,7 @@ func newServerHandshake(nodeID *ntor.NodeID, serverIdentity *ntor.Keypair) *serv
return hs
}
-func (hs *serverHandshake) parseClientHandshake(resp []byte) ([]byte, error) {
+func (hs *serverHandshake) parseClientHandshake(filter *replayFilter, resp []byte) ([]byte, error) {
// No point in examining the data unless the miminum plausible response has
// been received.
if clientMinHandshakeLength > len(resp) {
@@ -281,14 +286,19 @@ func (hs *serverHandshake) parseClientHandshake(resp []byte) ([]byte, error) {
macCmp := hs.mac.Sum(nil)[:macLength]
macRx := resp[pos+markLength : pos+markLength+macLength]
if hmac.Equal(macCmp, macRx) {
+ // Ensure that this handshake has not been seen previously.
+ if filter.testAndSet(time.Now().Unix(), macRx) {
+ // The client either happened to generate exactly the same
+ // session key and padding, or someone is replaying a previous
+ // handshake. In either case, fuck them.
+ return nil, ErrReplayedHandshake
+ }
+
macFound = true
hs.epochHour = epochHour
- // In theory, we should always evaluate all 3 MACs, but at this
- // point we are reasonably confident that the client knows the
- // correct NodeID/Public key, and if this fails, we just ignore the
- // client for a random interval and drop the connection anyway.
- break
+ // We could break out here, but in the name of reducing timing
+ // variation, evaluate all 3 MACs.
}
}
if !macFound {