123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370 |
- /*
- * Copyright (c) 2014, 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 obfs3 provides an implementation of the Tor Project's obfs3
- // obfuscation protocol.
- package obfs3 // import "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/lyrebird/transports/obfs3"
- import (
- "bytes"
- "crypto/aes"
- "crypto/cipher"
- "crypto/hmac"
- "crypto/sha256"
- "errors"
- "io"
- "net"
- "time"
- pt "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/goptlib"
- "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/lyrebird/common/csrand"
- "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/lyrebird/common/uniformdh"
- "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/lyrebird/transports/base"
- )
- const (
- transportName = "obfs3"
- clientHandshakeTimeout = time.Duration(30) * time.Second
- serverHandshakeTimeout = time.Duration(30) * time.Second
- initiatorKdfString = "Initiator obfuscated data"
- responderKdfString = "Responder obfuscated data"
- initiatorMagicString = "Initiator magic"
- responderMagicString = "Responder magic"
- maxPadding = 8194
- keyLen = 16
- )
- // Transport is the obfs3 implementation of the base.Transport interface.
- type Transport struct{}
- // Name returns the name of the obfs3 transport protocol.
- func (t *Transport) Name() string {
- return transportName
- }
- // ClientFactory returns a new obfs3ClientFactory instance.
- func (t *Transport) ClientFactory(stateDir string) (base.ClientFactory, error) {
- cf := &obfs3ClientFactory{transport: t}
- return cf, nil
- }
- // ServerFactory returns a new obfs3ServerFactory instance.
- func (t *Transport) ServerFactory(stateDir string, args *pt.Args) (base.ServerFactory, error) {
- sf := &obfs3ServerFactory{transport: t}
- return sf, nil
- }
- type obfs3ClientFactory struct {
- transport base.Transport
- }
- func (cf *obfs3ClientFactory) Transport() base.Transport {
- return cf.transport
- }
- func (cf *obfs3ClientFactory) ParseArgs(args *pt.Args) (interface{}, error) {
- return nil, nil
- }
- func (cf *obfs3ClientFactory) Dial(network, addr string, dialFn base.DialFunc, args interface{}) (net.Conn, error) {
- conn, err := dialFn(network, addr)
- if err != nil {
- return nil, err
- }
- dialConn := conn
- if conn, err = newObfs3ClientConn(conn); err != nil {
- dialConn.Close()
- return nil, err
- }
- return conn, nil
- }
- // Not yet implemented
- func (cf *obfs3ClientFactory) OnEvent(f func(base.TransportEvent)) {}
- type obfs3ServerFactory struct {
- transport base.Transport
- }
- func (sf *obfs3ServerFactory) Transport() base.Transport {
- return sf.transport
- }
- func (sf *obfs3ServerFactory) Args() *pt.Args {
- return nil
- }
- func (sf *obfs3ServerFactory) WrapConn(conn net.Conn) (net.Conn, error) {
- return newObfs3ServerConn(conn)
- }
- type obfs3Conn struct {
- net.Conn
- isInitiator bool
- rxMagic []byte
- txMagic []byte
- rxBuf *bytes.Buffer
- rx *cipher.StreamReader
- tx *cipher.StreamWriter
- }
- func newObfs3ClientConn(conn net.Conn) (c *obfs3Conn, err error) {
- // Initialize a client connection, and start the handshake timeout.
- c = &obfs3Conn{conn, true, nil, nil, new(bytes.Buffer), nil, nil}
- deadline := time.Now().Add(clientHandshakeTimeout)
- if err = c.SetDeadline(deadline); err != nil {
- return nil, err
- }
- // Handshake.
- if err = c.handshake(); err != nil {
- return nil, err
- }
- // Disarm the handshake timer.
- if err = c.SetDeadline(time.Time{}); err != nil {
- return nil, err
- }
- return
- }
- func newObfs3ServerConn(conn net.Conn) (c *obfs3Conn, err error) {
- // Initialize a server connection, and start the handshake timeout.
- c = &obfs3Conn{conn, false, nil, nil, new(bytes.Buffer), nil, nil}
- deadline := time.Now().Add(serverHandshakeTimeout)
- if err = c.SetDeadline(deadline); err != nil {
- return nil, err
- }
- // Handshake.
- if err = c.handshake(); err != nil {
- return nil, err
- }
- // Disarm the handshake timer.
- if err = c.SetDeadline(time.Time{}); err != nil {
- return nil, err
- }
- return
- }
- func (conn *obfs3Conn) handshake() error {
- // The party who opens the connection is the 'initiator'; the one who
- // accepts it is the 'responder'. Each begins by generating a
- // UniformDH keypair, and a random number PADLEN in [0, MAX_PADDING/2].
- // Both parties then send:
- //
- // PUB_KEY | WR(PADLEN)
- privateKey, err := uniformdh.GenerateKey(csrand.Reader)
- if err != nil {
- return err
- }
- padLen := csrand.IntRange(0, maxPadding/2)
- blob := make([]byte, uniformdh.Size+padLen)
- publicKey, err := privateKey.PublicKey.Bytes()
- if err != nil {
- return err
- }
- copy(blob[0:], publicKey)
- if err := csrand.Bytes(blob[uniformdh.Size:]); err != nil {
- return err
- }
- if _, err := conn.Conn.Write(blob); err != nil {
- return err
- }
- // Read the public key from the peer.
- rawPeerPublicKey := make([]byte, uniformdh.Size)
- if _, err := io.ReadFull(conn.Conn, rawPeerPublicKey); err != nil {
- return err
- }
- var peerPublicKey uniformdh.PublicKey
- if err := peerPublicKey.SetBytes(rawPeerPublicKey); err != nil {
- return err
- }
- // After retrieving the public key of the other end, each party
- // completes the DH key exchange and generates a shared-secret for the
- // session (named SHARED_SECRET).
- sharedSecret, err := uniformdh.Handshake(privateKey, &peerPublicKey)
- if err != nil {
- return err
- }
- if err := conn.kdf(sharedSecret); err != nil {
- return err
- }
- return nil
- }
- func (conn *obfs3Conn) kdf(sharedSecret []byte) error {
- // Using that shared-secret each party derives its encryption keys as
- // follows:
- //
- // INIT_SECRET = HMAC(SHARED_SECRET, "Initiator obfuscated data")
- // RESP_SECRET = HMAC(SHARED_SECRET, "Responder obfuscated data")
- // INIT_KEY = INIT_SECRET[:KEYLEN]
- // INIT_COUNTER = INIT_SECRET[KEYLEN:]
- // RESP_KEY = RESP_SECRET[:KEYLEN]
- // RESP_COUNTER = RESP_SECRET[KEYLEN:]
- initHmac := hmac.New(sha256.New, sharedSecret)
- _, _ = initHmac.Write([]byte(initiatorKdfString))
- initSecret := initHmac.Sum(nil)
- initHmac.Reset()
- _, _ = initHmac.Write([]byte(initiatorMagicString))
- initMagic := initHmac.Sum(nil)
- respHmac := hmac.New(sha256.New, sharedSecret)
- _, _ = respHmac.Write([]byte(responderKdfString))
- respSecret := respHmac.Sum(nil)
- respHmac.Reset()
- _, _ = respHmac.Write([]byte(responderMagicString))
- respMagic := respHmac.Sum(nil)
- // The INIT_KEY value keys a block cipher (in CTR mode) used to
- // encrypt values from initiator to responder thereafter. The counter
- // mode's initial counter value is INIT_COUNTER. The RESP_KEY value
- // keys a block cipher (in CTR mode) used to encrypt values from
- // responder to initiator thereafter. That counter mode's initial
- // counter value is RESP_COUNTER.
- //
- // Note: To have this be the last place where the shared secret is used,
- // also generate the magic value to send/scan for here.
- initBlock, err := aes.NewCipher(initSecret[:keyLen])
- if err != nil {
- return err
- }
- initStream := cipher.NewCTR(initBlock, initSecret[keyLen:])
- respBlock, err := aes.NewCipher(respSecret[:keyLen])
- if err != nil {
- return err
- }
- respStream := cipher.NewCTR(respBlock, respSecret[keyLen:])
- if conn.isInitiator {
- conn.tx = &cipher.StreamWriter{S: initStream, W: conn.Conn}
- conn.rx = &cipher.StreamReader{S: respStream, R: conn.rxBuf}
- conn.txMagic = initMagic
- conn.rxMagic = respMagic
- } else {
- conn.tx = &cipher.StreamWriter{S: respStream, W: conn.Conn}
- conn.rx = &cipher.StreamReader{S: initStream, R: conn.rxBuf}
- conn.txMagic = respMagic
- conn.rxMagic = initMagic
- }
- return nil
- }
- func (conn *obfs3Conn) findPeerMagic() error {
- var hsBuf [maxPadding + sha256.Size]byte
- for {
- n, err := conn.Conn.Read(hsBuf[:])
- if err != nil {
- // Yes, Read can return partial data and an error, but continuing
- // past that is nonsensical.
- return err
- }
- conn.rxBuf.Write(hsBuf[:n])
- pos := bytes.Index(conn.rxBuf.Bytes(), conn.rxMagic)
- if pos == -1 {
- if conn.rxBuf.Len() >= maxPadding+sha256.Size {
- return errors.New("failed to find peer magic value")
- }
- continue
- } else if pos > maxPadding {
- return errors.New("peer sent too much pre-magic-padding")
- }
- // Discard the padding/MAC.
- pos += len(conn.rxMagic)
- _ = conn.rxBuf.Next(pos)
- return nil
- }
- }
- func (conn *obfs3Conn) Read(b []byte) (n int, err error) {
- // If this is the first time we read data post handshake, scan for the
- // magic value.
- if conn.rxMagic != nil {
- if err = conn.findPeerMagic(); err != nil {
- conn.Close()
- return
- }
- conn.rxMagic = nil
- }
- // If the handshake receive buffer is still present...
- if conn.rxBuf != nil {
- // And it is empty...
- if conn.rxBuf.Len() == 0 {
- // There is no more trailing data left from the handshake process,
- // so rewire the cipher.StreamReader to pull data from the network
- // instead of the temporary receive buffer.
- conn.rx.R = conn.Conn
- conn.rxBuf = nil
- }
- }
- return conn.rx.Read(b)
- }
- func (conn *obfs3Conn) Write(b []byte) (n int, err error) {
- // If this is the first time we write data post handshake, send the
- // padding/magic value.
- if conn.txMagic != nil {
- padLen := csrand.IntRange(0, maxPadding/2)
- blob := make([]byte, padLen+len(conn.txMagic))
- if err = csrand.Bytes(blob[:padLen]); err != nil {
- conn.Close()
- return
- }
- copy(blob[padLen:], conn.txMagic)
- if _, err = conn.Conn.Write(blob); err != nil {
- conn.Close()
- return
- }
- conn.txMagic = nil
- }
- return conn.tx.Write(b)
- }
- var _ base.ClientFactory = (*obfs3ClientFactory)(nil)
- var _ base.ServerFactory = (*obfs3ServerFactory)(nil)
- var _ base.Transport = (*Transport)(nil)
- var _ net.Conn = (*obfs3Conn)(nil)
|