From 8a7de7e1e252a73d786bafab042eedde8c025ad6 Mon Sep 17 00:00:00 2001 From: Brandon Wiley Date: Tue, 15 Nov 2016 14:52:04 -0600 Subject: Removed transports from shapeshifter-dispatcher (now located in shapeshifter-transports) --- transports/scramblesuit/base.go | 99 ----- transports/scramblesuit/conn.go | 523 ------------------------- transports/scramblesuit/handshake_ticket.go | 228 ----------- transports/scramblesuit/handshake_uniformdh.go | 173 -------- transports/scramblesuit/hkdf_expand.go | 67 ---- 5 files changed, 1090 deletions(-) delete mode 100644 transports/scramblesuit/base.go delete mode 100644 transports/scramblesuit/conn.go delete mode 100644 transports/scramblesuit/handshake_ticket.go delete mode 100644 transports/scramblesuit/handshake_uniformdh.go delete mode 100644 transports/scramblesuit/hkdf_expand.go (limited to 'transports/scramblesuit') diff --git a/transports/scramblesuit/base.go b/transports/scramblesuit/base.go deleted file mode 100644 index b6f7cb7..0000000 --- a/transports/scramblesuit/base.go +++ /dev/null @@ -1,99 +0,0 @@ -/* - * 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 provides an implementation of the ScrambleSuit -// obfuscation protocol. The implementation is client only. -package scramblesuit - -import ( - "fmt" - "net" - - "git.torproject.org/pluggable-transports/goptlib.git" - "github.com/OperatorFoundation/obfs4/transports/base" -) - -const transportName = "scramblesuit" - -// Transport is the ScrambleSuit implementation of the base.Transport interface. -type Transport struct{} - -// Name returns the name of the ScrambleSuit transport protocol. -func (t *Transport) Name() string { - return transportName -} - -// ClientFactory returns a new ssClientFactory instance. -func (t *Transport) ClientFactory(stateDir string) (base.ClientFactory, error) { - tStore, err := loadTicketStore(stateDir) - if err != nil { - return nil, err - } - cf := &ssClientFactory{transport: t, ticketStore: tStore} - return cf, nil -} - -// ServerFactory will one day return a new ssServerFactory instance. -func (t *Transport) ServerFactory(stateDir string, args *pt.Args) (base.ServerFactory, error) { - // TODO: Fill this in eventually, though obfs4 is better. - return nil, fmt.Errorf("server not supported") -} - -type ssClientFactory struct { - transport base.Transport - ticketStore *ssTicketStore -} - -func (cf *ssClientFactory) Transport() base.Transport { - return cf.transport -} - -func (cf *ssClientFactory) ParseArgs(args *pt.Args) (interface{}, error) { - return newClientArgs(args) -} - -func (cf *ssClientFactory) Dial(network, addr string, dialFn base.DialFunc, args interface{}) (net.Conn, error) { - // Validate args before opening outgoing connection. - ca, ok := args.(*ssClientArgs) - if !ok { - return nil, fmt.Errorf("invalid argument type for args") - } - - conn, err := dialFn(network, addr) - if err != nil { - return nil, err - } - dialConn := conn - if conn, err = newScrambleSuitClientConn(conn, cf.ticketStore, ca); err != nil { - dialConn.Close() - return nil, err - } - return conn, nil -} - -var _ base.ClientFactory = (*ssClientFactory)(nil) -var _ base.Transport = (*Transport)(nil) diff --git a/transports/scramblesuit/conn.go b/transports/scramblesuit/conn.go deleted file mode 100644 index 5efd12f..0000000 --- a/transports/scramblesuit/conn.go +++ /dev/null @@ -1,523 +0,0 @@ -/* - * 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/aes" - "crypto/cipher" - "crypto/hmac" - "crypto/sha256" - "encoding/base32" - "encoding/binary" - "errors" - "fmt" - "hash" - "io" - "net" - "time" - - "git.torproject.org/pluggable-transports/goptlib.git" - "github.com/OperatorFoundation/obfs4/common/csrand" - "github.com/OperatorFoundation/obfs4/common/drbg" - "github.com/OperatorFoundation/obfs4/common/probdist" - "github.com/OperatorFoundation/obfs4/common/uniformdh" -) - -const ( - passwordArg = "password" - - maxSegmentLength = 1448 - maxPayloadLength = 1427 - sharedSecretLength = 160 / 8 // k_B - clientHandshakeTimeout = time.Duration(60) * time.Second - - minLenDistLength = 21 - maxLenDistLength = maxSegmentLength - - keyLength = 32 + 8 + 32 - - pktPrngSeedLength = 32 - pktOverhead = macLength + pktHdrLength - pktHdrLength = 2 + 2 + 1 - pktPayload = 1 - pktNewTicket = 1 << 1 - pktPrngSeed = 1 << 2 -) - -var ( - // ErrNotSupported is the error returned for a unsupported operation. - ErrNotSupported = errors.New("scramblesuit: operation not supported") - - // ErrInvalidPacket is the error returned when a invalid packet is received. - ErrInvalidPacket = errors.New("scramblesuit: invalid packet") - - zeroPadBytes [maxPayloadLength]byte -) - -type ssSharedSecret [sharedSecretLength]byte - -type ssClientArgs struct { - kB *ssSharedSecret - sessionKey *uniformdh.PrivateKey -} - -func newClientArgs(args *pt.Args) (ca *ssClientArgs, err error) { - ca = &ssClientArgs{} - if ca.kB, err = parsePasswordArg(args); err != nil { - return nil, err - } - - // Generate the client keypair before opening a connection since the time - // taken is visible to an adversary. This key might not end up being used - // if a session ticket is present, but this doesn't take that long. - if ca.sessionKey, err = uniformdh.GenerateKey(csrand.Reader); err != nil { - return nil, err - } - return -} - -func parsePasswordArg(args *pt.Args) (*ssSharedSecret, error) { - str, ok := args.Get(passwordArg) - if !ok { - return nil, fmt.Errorf("missing argument '%s'", passwordArg) - } - - // To match the obfsproxy behavior, 'str' should contain a Base32 encoded - // shared secret (k_B) used for handshaking. - decoded, err := base32.StdEncoding.DecodeString(str) - if err != nil { - return nil, fmt.Errorf("failed to decode password: %s", err) - } - if len(decoded) != sharedSecretLength { - return nil, fmt.Errorf("password length %d is invalid", len(decoded)) - } - ss := new(ssSharedSecret) - copy(ss[:], decoded) - return ss, nil -} - -type ssCryptoState struct { - s cipher.Stream - mac hash.Hash -} - -func newCryptoState(aesKey []byte, ivPrefix []byte, macKey []byte) (*ssCryptoState, error) { - // The ScrambleSuit CTR-AES256 link crypto uses an 8 byte prefix from the - // KDF, and a 64 bit counter initialized to 1 as the IV. The initial value - // of the counter isn't documented in the spec either. - var initialCtr = []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01} - iv := make([]byte, 0, aes.BlockSize) - iv = append(iv, ivPrefix...) - iv = append(iv, initialCtr...) - b, err := aes.NewCipher(aesKey) - if err != nil { - return nil, err - } - s := cipher.NewCTR(b, iv) - mac := hmac.New(sha256.New, macKey) - return &ssCryptoState{s: s, mac: mac}, nil -} - -type ssConn struct { - net.Conn - - isServer bool - - lenDist *probdist.WeightedDist - receiveBuffer *bytes.Buffer - receiveDecodedBuffer *bytes.Buffer - receiveState ssRxState - - txCrypto *ssCryptoState - rxCrypto *ssCryptoState - - ticketStore *ssTicketStore -} - -type ssRxState struct { - mac []byte - hdr []byte - - totalLen int - payloadLen int -} - -func (conn *ssConn) Read(b []byte) (n int, err error) { - // If the receive payload buffer is empty, consume data off the network. - for conn.receiveDecodedBuffer.Len() == 0 { - if err = conn.readPackets(); err != nil { - break - } - } - - // Service the read request using buffered payload. - if conn.receiveDecodedBuffer.Len() > 0 { - n, _ = conn.receiveDecodedBuffer.Read(b) - } - return -} - -func (conn *ssConn) Write(b []byte) (n int, err error) { - var frameBuf bytes.Buffer - p := b - toSend := len(p) - - for toSend > 0 { - // Send as much payload as will fit into each frame as possible. - wrLen := len(p) - if wrLen > maxPayloadLength { - wrLen = maxPayloadLength - } - payload := p[:wrLen] - if err = conn.makePacket(&frameBuf, pktPayload, payload, 0); err != nil { - return 0, err - } - - toSend -= wrLen - p = p[wrLen:] - n += wrLen - } - - // Pad out the burst as appropriate. - if err = conn.padBurst(&frameBuf, conn.lenDist.Sample()); err != nil { - return 0, err - } - - // Write and return. - _, err = conn.Conn.Write(frameBuf.Bytes()) - return -} - -func (conn *ssConn) SetDeadline(t time.Time) error { - return ErrNotSupported -} - -func (conn *ssConn) SetReadDeadline(t time.Time) error { - return ErrNotSupported -} - -func (conn *ssConn) SetWriteDeadline(t time.Time) error { - return ErrNotSupported -} - -func (conn *ssConn) makePacket(w io.Writer, pktType byte, data []byte, padLen int) error { - payloadLen := len(data) - totalLen := payloadLen + padLen - if totalLen > maxPayloadLength { - panic(fmt.Sprintf("BUG: makePacket() len(data) + padLen > maxPayloadLength: %d + %d > %d", len(data), padLen, maxPayloadLength)) - } - - // Build the packet header (total length, payload length, flags), - // and append the payload and padding. - pkt := make([]byte, pktHdrLength, pktHdrLength+payloadLen+padLen) - binary.BigEndian.PutUint16(pkt[0:], uint16(totalLen)) - binary.BigEndian.PutUint16(pkt[2:], uint16(payloadLen)) - pkt[4] = pktType - pkt = append(pkt, data...) - pkt = append(pkt, zeroPadBytes[:padLen]...) - - // Encrypt the packet, and calculate the MAC. - conn.txCrypto.s.XORKeyStream(pkt, pkt) - conn.txCrypto.mac.Reset() - conn.txCrypto.mac.Write(pkt) - mac := conn.txCrypto.mac.Sum(nil)[:macLength] - - // Write out MAC | Packet. Note that this does not go onto the network - // yet, as w is a byte.Buffer (This is done so each call to conn.Write() - // gets padding added). - if _, err := w.Write(mac); err != nil { - return err - } - _, err := w.Write(pkt) - return err -} - -func (conn *ssConn) readPackets() error { - // Consume and buffer up to 1 MSS worth of data. - var buf [maxSegmentLength]byte - rdLen, rdErr := conn.Conn.Read(buf[:]) - conn.receiveBuffer.Write(buf[:rdLen]) - - // Process incoming packets incrementally. conn.receiveState stores - // the results of partial processing. - for conn.receiveBuffer.Len() > 0 { - if conn.receiveState.mac == nil { - // Read and store the packet MAC. - if conn.receiveBuffer.Len() < macLength { - break - } - mac := make([]byte, macLength) - conn.receiveBuffer.Read(mac) - conn.receiveState.mac = mac - } - - if conn.receiveState.hdr == nil { - // Read and store the packet header. - if conn.receiveBuffer.Len() < pktHdrLength { - break - } - hdr := make([]byte, pktHdrLength) - conn.receiveBuffer.Read(hdr) - - // Add the encrypted packet header to the HMAC instance, and then - // decrypt it so that the length of the packet can be determined. - conn.rxCrypto.mac.Reset() - conn.rxCrypto.mac.Write(hdr) - conn.rxCrypto.s.XORKeyStream(hdr, hdr) - - // Store the plaintext packet header, and host byte order length - // values. - totalLen := int(binary.BigEndian.Uint16(hdr[0:])) - payloadLen := int(binary.BigEndian.Uint16(hdr[2:])) - if payloadLen > totalLen || totalLen > maxPayloadLength { - return ErrInvalidPacket - } - conn.receiveState.hdr = hdr - conn.receiveState.totalLen = totalLen - conn.receiveState.payloadLen = payloadLen - } - - var data []byte - if conn.receiveState.totalLen > 0 { - // If the packet actually has payload (including padding), read, - // digest and decrypt it. - if conn.receiveBuffer.Len() < conn.receiveState.totalLen { - break - } - data = make([]byte, conn.receiveState.totalLen) - conn.receiveBuffer.Read(data) - conn.rxCrypto.mac.Write(data) - conn.rxCrypto.s.XORKeyStream(data, data) - } - - // Authenticate the packet, by comparing the received MAC with the one - // calculated over the ciphertext consumed off the network. - cmpMAC := conn.rxCrypto.mac.Sum(nil)[:macLength] - if !hmac.Equal(cmpMAC, conn.receiveState.mac[:]) { - return ErrInvalidPacket - } - - // Based on the packet flags, do something useful with the payload. - data = data[:conn.receiveState.payloadLen] - switch conn.receiveState.hdr[4] { - case pktPayload: - // User data, write it into the decoded payload buffer so that Read - // calls can be serviced. - conn.receiveDecodedBuffer.Write(data) - case pktNewTicket: - // New Session Ticket to be used for future handshakes, store it in - // the Session Ticket store. - if conn.isServer || len(data) != ticketKeyLength+ticketLength { - return ErrInvalidPacket - } - conn.ticketStore.storeTicket(conn.RemoteAddr(), data) - case pktPrngSeed: - // New PRNG_SEED for the protocol polymorphism. Regenerate the - // length obfuscation probability distribution. - if conn.isServer || len(data) != pktPrngSeedLength { - return ErrInvalidPacket - } - seed, err := drbg.SeedFromBytes(data) - if err != nil { - return ErrInvalidPacket - } - conn.lenDist.Reset(seed) - default: - return ErrInvalidPacket - } - - // Done processing a packet, clear the partial state. - conn.receiveState.mac = nil - conn.receiveState.hdr = nil - conn.receiveState.totalLen = 0 - conn.receiveState.payloadLen = 0 - } - return rdErr -} - -func (conn *ssConn) clientHandshake(kB *ssSharedSecret, sessionKey *uniformdh.PrivateKey) error { - if conn.isServer { - return fmt.Errorf("clientHandshake called on server connection") - } - - // Query the Session Ticket store to see if there is a stored session - // ticket. - ticket, err := conn.ticketStore.getTicket(conn.RemoteAddr()) - if err != nil { - return err - } else if ticket != nil { - // Ok, there is an existing ticket, so attempt to do a Session Ticket - // handshake. Until we write to the network, failures are non-fatal as - // we can transition gracefully into doing a UniformDH handshake. - - // Derive the keys from the prestored master key received with the - // ticket. This is done before the actual handshake since the - // handshake uses the outgoing HMAC-SHA256-128 key for authentication. - if err = conn.initCrypto(ticket.key[:]); err != nil { - goto handshakeUDH - } - - // Generate and send the ticket handshake. There is no response, since - // both sides have the keying material. - hs := newTicketClientHandshake(conn.txCrypto.mac, ticket) - blob, err := hs.generateHandshake() - if err != nil { - goto handshakeUDH - } - if _, err = conn.Conn.Write(blob); err != nil { - return err - } - return nil - } - -handshakeUDH: - // No session ticket, so take the slow path and do a UniformDH based - // handshake. - - // Generate and send the client handshake. - hs := newDHClientHandshake(kB, sessionKey) - blob, err := hs.generateHandshake() - if err != nil { - return err - } - if _, err = conn.Conn.Write(blob); err != nil { - return err - } - - // Consume the server handshake. Since we don't actually know the length - // of the respose, we need to consume data off the network till we either - // find the tail marker + MAC digest indicating that a handshake response - // has been received, or the maximum handshake size passes without a valid - // response. - var hsBuf [maxHandshakeLength]byte - for { - var n int - if n, err = conn.Conn.Read(hsBuf[:]); err != nil { - return err - } - conn.receiveBuffer.Write(hsBuf[:n]) - - // Attempt to process all the data seen so far as a response. - var seed []byte - n, seed, err = hs.parseServerHandshake(conn.receiveBuffer.Bytes()) - if err == errMarkNotFoundYet { - // No response found yet, keep trying. - continue - } else if err != nil { - return err - } - - // Ok, done processing the handshake, discard the response, and do the - // key derivation based off the calculated shared secret. - _ = conn.receiveBuffer.Next(n) - err = conn.initCrypto(seed) - return err - } -} - -func (conn *ssConn) initCrypto(seed []byte) error { - // Use HKDF-SHA256 (Expand only, no Extract) to generate session keys from - // initial keying material. - okm := hkdfExpand(sha256.New, seed, nil, kdfSecretLength) - var err error - conn.txCrypto, err = newCryptoState(okm[0:32], okm[32:40], okm[80:112]) - if err != nil { - return err - } - conn.rxCrypto, err = newCryptoState(okm[40:72], okm[72:80], okm[112:144]) - if err != nil { - return err - } - return nil -} - -func (conn *ssConn) padBurst(burst *bytes.Buffer, sampleLen int) error { - // Burst contains the fully encrypted+MACed outgoing payload that will be - // written to the network. Pad it out so that the last segment (based on - // the ScrambleSuit MTU) is sampleLen bytes. - - dataLen := burst.Len() % maxSegmentLength - padLen := 0 - if sampleLen >= dataLen { - padLen = sampleLen - dataLen - } else { - padLen = (maxSegmentLength - dataLen) + sampleLen - } - if padLen < pktOverhead { - // The padLen is less than the MAC + packet header in length, so - // two packets are required. - padLen += maxSegmentLength - } - - if padLen == 0 { - return nil - } - if padLen > maxSegmentLength { - // Note: packetmorpher.py: getPadding is slightly wrong and only - // accounts for one of the two packet headers. - if err := conn.makePacket(burst, pktPayload, nil, 700-pktOverhead); err != nil { - return err - } - return conn.makePacket(burst, pktPayload, nil, padLen-(700+2*pktOverhead)) - } - return conn.makePacket(burst, pktPayload, nil, padLen-pktOverhead) -} - -func newScrambleSuitClientConn(conn net.Conn, tStore *ssTicketStore, ca *ssClientArgs) (net.Conn, error) { - // At this point we have kB and our session key, so we can directly - // start handshaking and seeing what happens. - - // Seed the initial polymorphism distribution. - seed, err := drbg.NewSeed() - if err != nil { - return nil, err - } - dist := probdist.New(seed, minLenDistLength, maxLenDistLength, true) - - // Allocate the client structure. - c := &ssConn{conn, false, dist, bytes.NewBuffer(nil), bytes.NewBuffer(nil), ssRxState{}, nil, nil, tStore} - - // Start the handshake timeout. - deadline := time.Now().Add(clientHandshakeTimeout) - if err := conn.SetDeadline(deadline); err != nil { - return nil, err - } - - // Attempt to handshake. - if err := c.clientHandshake(ca.kB, ca.sessionKey); err != nil { - return nil, err - } - - // Stop the handshake timeout. - if err := conn.SetDeadline(time.Time{}); err != nil { - return nil, err - } - - return c, nil -} diff --git a/transports/scramblesuit/handshake_ticket.go b/transports/scramblesuit/handshake_ticket.go deleted file mode 100644 index c9b97cc..0000000 --- a/transports/scramblesuit/handshake_ticket.go +++ /dev/null @@ -1,228 +0,0 @@ -/* - * 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" - "encoding/base32" - "encoding/json" - "errors" - "fmt" - "hash" - "io/ioutil" - "net" - "os" - "path" - "strconv" - "sync" - "time" - - "github.com/OperatorFoundation/obfs4/common/csrand" -) - -const ( - ticketFile = "scramblesuit_tickets.json" - - ticketKeyLength = 32 - ticketLength = 112 - ticketLifetime = 60 * 60 * 24 * 7 - - ticketMinPadLength = 0 - ticketMaxPadLength = 1388 -) - -var ( - errInvalidTicket = errors.New("scramblesuit: invalid serialized ticket") -) - -type ssTicketStore struct { - sync.Mutex - - filePath string - store map[string]*ssTicket -} - -type ssTicket struct { - key [ticketKeyLength]byte - ticket [ticketLength]byte - issuedAt int64 -} - -type ssTicketJSON struct { - KeyTicket string `json:"key-ticket"` - IssuedAt int64 `json:"issuedAt"` -} - -func (t *ssTicket) isValid() bool { - return t.issuedAt+ticketLifetime > time.Now().Unix() -} - -func newTicket(raw []byte) (*ssTicket, error) { - if len(raw) != ticketKeyLength+ticketLength { - return nil, errInvalidTicket - } - t := &ssTicket{issuedAt: time.Now().Unix()} - copy(t.key[:], raw[0:]) - copy(t.ticket[:], raw[ticketKeyLength:]) - return t, nil -} - -func (s *ssTicketStore) storeTicket(addr net.Addr, rawT []byte) { - t, err := newTicket(rawT) - if err != nil { - // Silently ignore ticket store failures. - return - } - - s.Lock() - defer s.Unlock() - - // Add the ticket to the map, and checkpoint to disk. Serialization errors - // are ignored because the handshake code will just use UniformDH if a - // ticket is not available. - s.store[addr.String()] = t - s.serialize() -} - -func (s *ssTicketStore) getTicket(addr net.Addr) (*ssTicket, error) { - aStr := addr.String() - - s.Lock() - defer s.Unlock() - - t, ok := s.store[aStr] - if ok && t != nil { - // Tickets are one use only, so remove tickets from the map, and - // checkpoint the map to disk. - delete(s.store, aStr) - err := s.serialize() - if !t.isValid() { - // Expired ticket, ignore it. - return nil, err - } - return t, err - } - - // No ticket was found, that's fine. - return nil, nil -} - -func (s *ssTicketStore) serialize() error { - encMap := make(map[string]*ssTicketJSON) - for k, v := range s.store { - kt := make([]byte, 0, ticketKeyLength+ticketLength) - kt = append(kt, v.key[:]...) - kt = append(kt, v.ticket[:]...) - ktStr := base32.StdEncoding.EncodeToString(kt) - jsonObj := &ssTicketJSON{KeyTicket: ktStr, IssuedAt: v.issuedAt} - encMap[k] = jsonObj - } - jsonStr, err := json.Marshal(encMap) - if err != nil { - return err - } - return ioutil.WriteFile(s.filePath, jsonStr, 0600) -} - -func loadTicketStore(stateDir string) (*ssTicketStore, error) { - fPath := path.Join(stateDir, ticketFile) - s := &ssTicketStore{filePath: fPath} - s.store = make(map[string]*ssTicket) - - f, err := ioutil.ReadFile(fPath) - if err != nil { - // No ticket store is fine. - if os.IsNotExist(err) { - return s, nil - } - - // But a file read error is not. - return nil, err - } - - encMap := make(map[string]*ssTicketJSON) - if err = json.Unmarshal(f, &encMap); err != nil { - return nil, fmt.Errorf("failed to load ticket store '%s': '%s'", fPath, err) - } - for k, v := range encMap { - raw, err := base32.StdEncoding.DecodeString(v.KeyTicket) - if err != nil || len(raw) != ticketKeyLength+ticketLength { - // Just silently skip corrupted tickets. - continue - } - t := &ssTicket{issuedAt: v.IssuedAt} - if !t.isValid() { - // Just ignore expired tickets. - continue - } - copy(t.key[:], raw[0:]) - copy(t.ticket[:], raw[ticketKeyLength:]) - s.store[k] = t - } - return s, nil -} - -type ssTicketClientHandshake struct { - mac hash.Hash - ticket *ssTicket - padLen int -} - -func (hs *ssTicketClientHandshake) generateHandshake() ([]byte, error) { - var buf bytes.Buffer - hs.mac.Reset() - - // The client handshake is T | P | M | MAC(T | P | M | E) - hs.mac.Write(hs.ticket.ticket[:]) - m := hs.mac.Sum(nil)[:macLength] - p, err := makePad(hs.padLen) - if err != nil { - return nil, err - } - - // Write T, P, M. - buf.Write(hs.ticket.ticket[:]) - buf.Write(p) - buf.Write(m) - - // Calculate and write the MAC. - e := []byte(strconv.FormatInt(getEpochHour(), 10)) - hs.mac.Write(p) - hs.mac.Write(m) - hs.mac.Write(e) - buf.Write(hs.mac.Sum(nil)[:macLength]) - - hs.mac.Reset() - return buf.Bytes(), nil -} - -func newTicketClientHandshake(mac hash.Hash, ticket *ssTicket) *ssTicketClientHandshake { - hs := &ssTicketClientHandshake{mac: mac, ticket: ticket} - hs.padLen = csrand.IntRange(ticketMinPadLength, ticketMaxPadLength) - return hs -} diff --git a/transports/scramblesuit/handshake_uniformdh.go b/transports/scramblesuit/handshake_uniformdh.go deleted file mode 100644 index 1a577e8..0000000 --- a/transports/scramblesuit/handshake_uniformdh.go +++ /dev/null @@ -1,173 +0,0 @@ -/* - * 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" - - "github.com/OperatorFoundation/obfs4/common/csrand" - "github.com/OperatorFoundation/obfs4/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 -} diff --git a/transports/scramblesuit/hkdf_expand.go b/transports/scramblesuit/hkdf_expand.go deleted file mode 100644 index 9626b38..0000000 --- a/transports/scramblesuit/hkdf_expand.go +++ /dev/null @@ -1,67 +0,0 @@ -/* - * 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 ( - "crypto/hmac" - "hash" -) - -func hkdfExpand(hashFn func() hash.Hash, prk []byte, info []byte, l int) []byte { - // Why, yes. golang.org/x/crypto/hkdf exists, and is a fine - // implementation of HKDF. However it does both the extract - // and expand, while ScrambleSuit only does extract, with no - // way to separate the two steps. - - h := hmac.New(hashFn, prk) - digestSz := h.Size() - if l > 255*digestSz { - panic("hkdf: requested OKM length > 255*HashLen") - } - - var t []byte - okm := make([]byte, 0, l) - toAppend := l - ctr := byte(1) - for toAppend > 0 { - h.Reset() - h.Write(t) - h.Write(info) - h.Write([]byte{ctr}) - t = h.Sum(nil) - ctr++ - - aLen := digestSz - if toAppend < digestSz { - aLen = toAppend - } - okm = append(okm, t[:aLen]...) - toAppend -= aLen - } - return okm -} -- cgit v1.2.3