You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
obfs4/transports/scramblesuit/handshake_uniformdh.go

174 lines
4.9 KiB
Go

/*
* Copyright (c) 2015, Yawning Angel <yawning at schwanenlied dot me>
* 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"
"gitlab.com/yawning/obfs4.git/common/csrand"
"gitlab.com/yawning/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{}
if err := hs.serverPublicKey.SetBytes(y); 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
}