123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174 |
- /*
- * 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.torproject.org/tpo/anti-censorship/pluggable-transports/lyrebird/common/csrand"
- "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/lyrebird/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
- }
|