mirror of https://gitlab.com/yawning/obfs4
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.
174 lines
4.9 KiB
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
|
|
}
|