From 0066cfc3932c50323fa596981f18ef5b0e862742 Mon Sep 17 00:00:00 2001 From: Yawning Angel Date: Tue, 17 Feb 2015 11:33:29 +0000 Subject: Add support for acting as a ScrambleSuit client. This allows obfs4proxy to be used as a ScrambleSuit client that is wire compatible with the obfs4proxy implementation, including session ticket support, and length obfuscation. The current implementation has the following limitations: * IAT obfuscation is not supported (and is disabled in all other ScrambleSuit implementations by default). * The length distribution and probabilites are different from those generated by obfsproxy and obfsclient due to a different DRBG. * Server support is missing and is unlikely to be implemented. --- transports/scramblesuit/handshake_uniformdh.go | 174 +++++++++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 transports/scramblesuit/handshake_uniformdh.go (limited to 'transports/scramblesuit/handshake_uniformdh.go') diff --git a/transports/scramblesuit/handshake_uniformdh.go b/transports/scramblesuit/handshake_uniformdh.go new file mode 100644 index 0000000..4345d65 --- /dev/null +++ b/transports/scramblesuit/handshake_uniformdh.go @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2015, Yawning Angel + * 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 scramblesuit + +import ( + "bytes" + "crypto/hmac" + "crypto/sha256" + "errors" + "hash" + "strconv" + "time" + + "git.torproject.org/pluggable-transports/obfs4.git/common/csrand" + "git.torproject.org/pluggable-transports/obfs4.git/common/uniformdh" +) + +const ( + minHandshakeLength = uniformdh.Size + macLength*2 + maxHandshakeLength = 1532 + dhMinPadLength = 0 + dhMaxPadLength = 1308 + macLength = 128 / 8 // HMAC-SHA256-128() + + kdfSecretLength = keyLength * 2 +) + +var ( + errMarkNotFoundYet = errors.New("mark not found yet") + + // ErrInvalidHandshake is the error returned when the handshake fails. + ErrInvalidHandshake = errors.New("invalid handshake") +) + +type ssDHClientHandshake struct { + mac hash.Hash + keypair *uniformdh.PrivateKey + epochHour []byte + padLen int + + serverPublicKey *uniformdh.PublicKey + serverMark []byte +} + +func (hs *ssDHClientHandshake) generateHandshake() ([]byte, error) { + var buf bytes.Buffer + hs.mac.Reset() + + // The client handshake is X | P_C | M_C | MAC(X | P_C | M_C | E) + x, err := hs.keypair.PublicKey.Bytes() + if err != nil { + return nil, err + } + hs.mac.Write(x) + mC := hs.mac.Sum(nil)[:macLength] + pC, err := makePad(hs.padLen) + if err != nil { + return nil, err + } + + // Write X, P_C, M_C. + buf.Write(x) + buf.Write(pC) + buf.Write(mC) + + // Calculate and write the MAC. + hs.epochHour = []byte(strconv.FormatInt(getEpochHour(), 10)) + hs.mac.Write(pC) + hs.mac.Write(mC) + hs.mac.Write(hs.epochHour) + buf.Write(hs.mac.Sum(nil)[:macLength]) + + return buf.Bytes(), nil +} + +func (hs *ssDHClientHandshake) parseServerHandshake(resp []byte) (int, []byte, error) { + if len(resp) < minHandshakeLength { + return 0, nil, errMarkNotFoundYet + } + + // The server response is Y | P_S | M_S | MAC(Y | P_S | M_S | E). + if hs.serverPublicKey == nil { + y := resp[:uniformdh.Size] + + // Pull out the public key, and derive the server mark. + hs.serverPublicKey = &uniformdh.PublicKey{} + err := hs.serverPublicKey.SetBytes(y) + if err != nil { + return 0, nil, err + } + hs.mac.Reset() + hs.mac.Write(y) + hs.serverMark = hs.mac.Sum(nil)[:macLength] + } + + // Find the mark+MAC, if it exits. + endPos := len(resp) + if endPos > maxHandshakeLength-macLength { + endPos = maxHandshakeLength - macLength + } + pos := bytes.Index(resp[uniformdh.Size:endPos], hs.serverMark) + if pos == -1 { + if len(resp) >= maxHandshakeLength { + // Couldn't find the mark in a maximum length response. + return 0, nil, ErrInvalidHandshake + } + return 0, nil, errMarkNotFoundYet + } else if len(resp) < pos+2*macLength { + // Didn't receive the full M_S. + return 0, nil, errMarkNotFoundYet + } + pos += uniformdh.Size + + // Validate the MAC. + hs.mac.Write(resp[uniformdh.Size : pos+macLength]) + hs.mac.Write(hs.epochHour) + macCmp := hs.mac.Sum(nil)[:macLength] + macRx := resp[pos+macLength : pos+2*macLength] + if !hmac.Equal(macCmp, macRx) { + return 0, nil, ErrInvalidHandshake + } + + // Derive the shared secret. + ss, err := uniformdh.Handshake(hs.keypair, hs.serverPublicKey) + if err != nil { + return 0, nil, err + } + seed := sha256.Sum256(ss) + return pos + 2*macLength, seed[:], nil +} + +func newDHClientHandshake(kB *ssSharedSecret, sessionKey *uniformdh.PrivateKey) *ssDHClientHandshake { + hs := &ssDHClientHandshake{keypair: sessionKey} + hs.mac = hmac.New(sha256.New, kB[:]) + hs.padLen = csrand.IntRange(dhMinPadLength, dhMaxPadLength) + return hs +} + +func getEpochHour() int64 { + return time.Now().Unix() / 3600 +} + +func makePad(padLen int) ([]byte, error) { + pad := make([]byte, padLen) + if err := csrand.Bytes(pad); err != nil { + return nil, err + } + return pad, nil +} -- cgit v1.2.3