diff options
Diffstat (limited to 'transports/obfs4/handshake_ntor.go')
-rw-r--r-- | transports/obfs4/handshake_ntor.go | 425 |
1 files changed, 0 insertions, 425 deletions
diff --git a/transports/obfs4/handshake_ntor.go b/transports/obfs4/handshake_ntor.go deleted file mode 100644 index ed7c114..0000000 --- a/transports/obfs4/handshake_ntor.go +++ /dev/null @@ -1,425 +0,0 @@ -/* - * Copyright (c) 2014, Yawning Angel <yawning at torproject dot org> - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -package obfs4 - -import ( - "bytes" - "crypto/hmac" - "crypto/sha256" - "encoding/hex" - "errors" - "fmt" - "hash" - "strconv" - "time" - - "github.com/OperatorFoundation/obfs4/common/csrand" - "github.com/OperatorFoundation/obfs4/common/ntor" - "github.com/OperatorFoundation/obfs4/common/replayfilter" - "github.com/OperatorFoundation/obfs4/transports/obfs4/framing" -) - -const ( - maxHandshakeLength = 8192 - - clientMinPadLength = (serverMinHandshakeLength + inlineSeedFrameLength) - - clientMinHandshakeLength - clientMaxPadLength = maxHandshakeLength - clientMinHandshakeLength - clientMinHandshakeLength = ntor.RepresentativeLength + markLength + macLength - - serverMinPadLength = 0 - serverMaxPadLength = maxHandshakeLength - (serverMinHandshakeLength + - inlineSeedFrameLength) - serverMinHandshakeLength = ntor.RepresentativeLength + ntor.AuthLength + - markLength + macLength - - markLength = sha256.Size / 2 - macLength = sha256.Size / 2 - - inlineSeedFrameLength = framing.FrameOverhead + packetOverhead + seedPacketPayloadLength -) - -// ErrMarkNotFoundYet is the error returned when the obfs4 handshake is -// incomplete and requires more data to continue. This error is non-fatal and -// is the equivalent to EAGAIN/EWOULDBLOCK. -var ErrMarkNotFoundYet = errors.New("handshake: M_[C,S] not found yet") - -// ErrInvalidHandshake is the error returned when the obfs4 handshake fails due -// to the peer not sending the correct mark. This error is fatal and the -// 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") - -// InvalidMacError is the error returned when the handshake MACs do not match. -// This error is fatal and the connection MUST be dropped. -type InvalidMacError struct { - Derived []byte - Received []byte -} - -func (e *InvalidMacError) Error() string { - return fmt.Sprintf("handshake: MAC mismatch: Dervied: %s Received: %s.", - hex.EncodeToString(e.Derived), hex.EncodeToString(e.Received)) -} - -// InvalidAuthError is the error returned when the ntor AUTH tags do not match. -// This error is fatal and the connection MUST be dropped. -type InvalidAuthError struct { - Derived *ntor.Auth - Received *ntor.Auth -} - -func (e *InvalidAuthError) Error() string { - return fmt.Sprintf("handshake: ntor AUTH mismatch: Derived: %s Received:%s.", - hex.EncodeToString(e.Derived.Bytes()[:]), - hex.EncodeToString(e.Received.Bytes()[:])) -} - -type clientHandshake struct { - keypair *ntor.Keypair - nodeID *ntor.NodeID - serverIdentity *ntor.PublicKey - epochHour []byte - - padLen int - mac hash.Hash - - serverRepresentative *ntor.Representative - serverAuth *ntor.Auth - serverMark []byte -} - -func newClientHandshake(nodeID *ntor.NodeID, serverIdentity *ntor.PublicKey, sessionKey *ntor.Keypair) *clientHandshake { - hs := new(clientHandshake) - hs.keypair = sessionKey - hs.nodeID = nodeID - hs.serverIdentity = serverIdentity - hs.padLen = csrand.IntRange(clientMinPadLength, clientMaxPadLength) - hs.mac = hmac.New(sha256.New, append(hs.serverIdentity.Bytes()[:], hs.nodeID.Bytes()[:]...)) - - return hs -} - -func (hs *clientHandshake) generateHandshake() ([]byte, error) { - var buf bytes.Buffer - - hs.mac.Reset() - hs.mac.Write(hs.keypair.Representative().Bytes()[:]) - mark := hs.mac.Sum(nil)[:markLength] - - // The client handshake is X | P_C | M_C | MAC(X | P_C | M_C | E) where: - // * X is the client's ephemeral Curve25519 public key representative. - // * P_C is [clientMinPadLength,clientMaxPadLength] bytes of random padding. - // * M_C is HMAC-SHA256-128(serverIdentity | NodeID, X) - // * MAC is HMAC-SHA256-128(serverIdentity | NodeID, X .... E) - // * E is the string representation of the number of hours since the UNIX - // epoch. - - // Generate the padding - pad, err := makePad(hs.padLen) - if err != nil { - return nil, err - } - - // Write X, P_C, M_C. - buf.Write(hs.keypair.Representative().Bytes()[:]) - buf.Write(pad) - buf.Write(mark) - - // Calculate and write the MAC. - hs.mac.Reset() - hs.mac.Write(buf.Bytes()) - hs.epochHour = []byte(strconv.FormatInt(getEpochHour(), 10)) - hs.mac.Write(hs.epochHour) - buf.Write(hs.mac.Sum(nil)[:macLength]) - - return buf.Bytes(), nil -} - -func (hs *clientHandshake) parseServerHandshake(resp []byte) (int, []byte, error) { - // No point in examining the data unless the miminum plausible response has - // been received. - if serverMinHandshakeLength > len(resp) { - return 0, nil, ErrMarkNotFoundYet - } - - if hs.serverRepresentative == nil || hs.serverAuth == nil { - // Pull out the representative/AUTH. (XXX: Add ctors to ntor) - hs.serverRepresentative = new(ntor.Representative) - copy(hs.serverRepresentative.Bytes()[:], resp[0:ntor.RepresentativeLength]) - hs.serverAuth = new(ntor.Auth) - copy(hs.serverAuth.Bytes()[:], resp[ntor.RepresentativeLength:]) - - // Derive the mark. - hs.mac.Reset() - hs.mac.Write(hs.serverRepresentative.Bytes()[:]) - hs.serverMark = hs.mac.Sum(nil)[:markLength] - } - - // Attempt to find the mark + MAC. - pos := findMarkMac(hs.serverMark, resp, ntor.RepresentativeLength+ntor.AuthLength+serverMinPadLength, - maxHandshakeLength, false) - if pos == -1 { - if len(resp) >= maxHandshakeLength { - return 0, nil, ErrInvalidHandshake - } - return 0, nil, ErrMarkNotFoundYet - } - - // Validate the MAC. - hs.mac.Reset() - hs.mac.Write(resp[:pos+markLength]) - hs.mac.Write(hs.epochHour) - macCmp := hs.mac.Sum(nil)[:macLength] - macRx := resp[pos+markLength : pos+markLength+macLength] - if !hmac.Equal(macCmp, macRx) { - return 0, nil, &InvalidMacError{macCmp, macRx} - } - - // Complete the handshake. - serverPublic := hs.serverRepresentative.ToPublic() - ok, seed, auth := ntor.ClientHandshake(hs.keypair, serverPublic, - hs.serverIdentity, hs.nodeID) - if !ok { - return 0, nil, ErrNtorFailed - } - if !ntor.CompareAuth(auth, hs.serverAuth.Bytes()[:]) { - return 0, nil, &InvalidAuthError{auth, hs.serverAuth} - } - - return pos + markLength + macLength, seed.Bytes()[:], nil -} - -type serverHandshake struct { - keypair *ntor.Keypair - nodeID *ntor.NodeID - serverIdentity *ntor.Keypair - epochHour []byte - serverAuth *ntor.Auth - - padLen int - mac hash.Hash - - clientRepresentative *ntor.Representative - clientMark []byte -} - -func newServerHandshake(nodeID *ntor.NodeID, serverIdentity *ntor.Keypair, sessionKey *ntor.Keypair) *serverHandshake { - hs := new(serverHandshake) - hs.keypair = sessionKey - hs.nodeID = nodeID - hs.serverIdentity = serverIdentity - hs.padLen = csrand.IntRange(serverMinPadLength, serverMaxPadLength) - hs.mac = hmac.New(sha256.New, append(hs.serverIdentity.Public().Bytes()[:], hs.nodeID.Bytes()[:]...)) - - return hs -} - -func (hs *serverHandshake) parseClientHandshake(filter *replayfilter.ReplayFilter, resp []byte) ([]byte, error) { - // No point in examining the data unless the miminum plausible response has - // been received. - if clientMinHandshakeLength > len(resp) { - return nil, ErrMarkNotFoundYet - } - - if hs.clientRepresentative == nil { - // Pull out the representative/AUTH. (XXX: Add ctors to ntor) - hs.clientRepresentative = new(ntor.Representative) - copy(hs.clientRepresentative.Bytes()[:], resp[0:ntor.RepresentativeLength]) - - // Derive the mark. - hs.mac.Reset() - hs.mac.Write(hs.clientRepresentative.Bytes()[:]) - hs.clientMark = hs.mac.Sum(nil)[:markLength] - } - - // Attempt to find the mark + MAC. - pos := findMarkMac(hs.clientMark, resp, ntor.RepresentativeLength+clientMinPadLength, - maxHandshakeLength, true) - if pos == -1 { - if len(resp) >= maxHandshakeLength { - return nil, ErrInvalidHandshake - } - return nil, ErrMarkNotFoundYet - } - - // Validate the MAC. - macFound := false - for _, off := range []int64{0, -1, 1} { - // Allow epoch to be off by up to a hour in either direction. - epochHour := []byte(strconv.FormatInt(getEpochHour()+int64(off), 10)) - hs.mac.Reset() - hs.mac.Write(resp[:pos+markLength]) - hs.mac.Write(epochHour) - 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(), 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 - - // We could break out here, but in the name of reducing timing - // variation, evaluate all 3 MACs. - } - } - if !macFound { - // This probably should be an InvalidMacError, but conveying the 3 MACS - // that would be accepted is annoying so just return a generic fatal - // failure. - return nil, ErrInvalidHandshake - } - - // Client should never sent trailing garbage. - if len(resp) != pos+markLength+macLength { - return nil, ErrInvalidHandshake - } - - clientPublic := hs.clientRepresentative.ToPublic() - ok, seed, auth := ntor.ServerHandshake(clientPublic, hs.keypair, - hs.serverIdentity, hs.nodeID) - if !ok { - return nil, ErrNtorFailed - } - hs.serverAuth = auth - - return seed.Bytes()[:], nil -} - -func (hs *serverHandshake) generateHandshake() ([]byte, error) { - var buf bytes.Buffer - - hs.mac.Reset() - hs.mac.Write(hs.keypair.Representative().Bytes()[:]) - mark := hs.mac.Sum(nil)[:markLength] - - // The server handshake is Y | AUTH | P_S | M_S | MAC(Y | AUTH | P_S | M_S | E) where: - // * Y is the server's ephemeral Curve25519 public key representative. - // * AUTH is the ntor handshake AUTH value. - // * P_S is [serverMinPadLength,serverMaxPadLength] bytes of random padding. - // * M_S is HMAC-SHA256-128(serverIdentity | NodeID, Y) - // * MAC is HMAC-SHA256-128(serverIdentity | NodeID, Y .... E) - // * E is the string representation of the number of hours since the UNIX - // epoch. - - // Generate the padding - pad, err := makePad(hs.padLen) - if err != nil { - return nil, err - } - - // Write Y, AUTH, P_S, M_S. - buf.Write(hs.keypair.Representative().Bytes()[:]) - buf.Write(hs.serverAuth.Bytes()[:]) - buf.Write(pad) - buf.Write(mark) - - // Calculate and write the MAC. - hs.mac.Reset() - hs.mac.Write(buf.Bytes()) - hs.epochHour = []byte(strconv.FormatInt(getEpochHour(), 10)) - hs.mac.Write(hs.epochHour) - buf.Write(hs.mac.Sum(nil)[:macLength]) - - return buf.Bytes(), nil -} - -// getEpochHour returns the number of hours since the UNIX epoch. -func getEpochHour() int64 { - return time.Now().Unix() / 3600 -} - -func findMarkMac(mark, buf []byte, startPos, maxPos int, fromTail bool) (pos int) { - if len(mark) != markLength { - panic(fmt.Sprintf("BUG: Invalid mark length: %d", len(mark))) - } - - endPos := len(buf) - if startPos > len(buf) { - return -1 - } - if endPos > maxPos { - endPos = maxPos - } - if endPos-startPos < markLength+macLength { - return -1 - } - - if fromTail { - // The server can optimize the search process by only examining the - // tail of the buffer. The client can't send valid data past M_C | - // MAC_C as it does not have the server's public key yet. - pos = endPos - (markLength + macLength) - if !hmac.Equal(buf[pos:pos+markLength], mark) { - return -1 - } - - return - } - - // The client has to actually do a substring search since the server can - // and will send payload trailing the response. - // - // XXX: bytes.Index() uses a naive search, which kind of sucks. - pos = bytes.Index(buf[startPos:endPos], mark) - if pos == -1 { - return -1 - } - - // Ensure that there is enough trailing data for the MAC. - if startPos+pos+markLength+macLength > endPos { - return -1 - } - - // Return the index relative to the start of the slice. - pos += startPos - return -} - -func makePad(padLen int) ([]byte, error) { - pad := make([]byte, padLen) - if err := csrand.Bytes(pad); err != nil { - return nil, err - } - - return pad, nil -} |