func newServerHandshake(nodeID *ntor.NodeID, serverIdentity *ntor.Keypair, sessionKey *ntor.Keypair) *serverHandshake { hs := new(serverHandshake) hs.keypair = sessionKey hs.nodeID = nodeID hs.serverIdentity = serverIdentity hs.padLen = csrand.IntRange(serverMinPadLength, serverMaxPadLength) hs.mac = hmac.New(sha256.New, append(hs.serverIdentity.Public().Bytes()[:], hs.nodeID.Bytes()[:]...)) return hs }
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) 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) }
func (conn *obfs2Conn) handshake() error { // Each begins by generating a seed and a padding key as follows. // The initiator generates: // // INIT_SEED = SR(SEED_LENGTH) // INIT_PAD_KEY = MAC("Initiator obfuscation padding", INIT_SEED)[:KEYLEN] // // And the responder generates: // // RESP_SEED = SR(SEED_LENGTH) // RESP_PAD_KEY = MAC("Responder obfuscation padding", INIT_SEED)[:KEYLEN] // // Each then generates a random number PADLEN in range from 0 through // MAX_PADDING (inclusive). var seed [seedLen]byte if err := csrand.Bytes(seed[:]); err != nil { return err } var padMagic []byte if conn.isInitiator { padMagic = []byte(initiatorPadString) } else { padMagic = []byte(responderPadString) } padKey, padIV := hsKdf(padMagic, seed[:], conn.isInitiator) padLen := uint32(csrand.IntRange(0, maxPadding)) hsBlob := make([]byte, hsLen+padLen) binary.BigEndian.PutUint32(hsBlob[0:4], magicValue) binary.BigEndian.PutUint32(hsBlob[4:8], padLen) if padLen > 0 { if err := csrand.Bytes(hsBlob[8:]); err != nil { return err } } // The initiator then sends: // // INIT_SEED | E(INIT_PAD_KEY, UINT32(MAGIC_VALUE) | UINT32(PADLEN) | WR(PADLEN)) // // and the responder sends: // // RESP_SEED | E(RESP_PAD_KEY, UINT32(MAGIC_VALUE) | UINT32(PADLEN) | WR(PADLEN)) txBlock, err := aes.NewCipher(padKey) if err != nil { return err } txStream := cipher.NewCTR(txBlock, padIV) conn.tx = &cipher.StreamWriter{S: txStream, W: conn.Conn} if _, err := conn.Conn.Write(seed[:]); err != nil { return err } if _, err := conn.Write(hsBlob); err != nil { return err } // Upon receiving the SEED from the other party, each party derives // the other party's padding key value as above, and decrypts the next // 8 bytes of the key establishment message. var peerSeed [seedLen]byte if _, err := io.ReadFull(conn.Conn, peerSeed[:]); err != nil { return err } var peerPadMagic []byte if conn.isInitiator { peerPadMagic = []byte(responderPadString) } else { peerPadMagic = []byte(initiatorPadString) } peerKey, peerIV := hsKdf(peerPadMagic, peerSeed[:], !conn.isInitiator) rxBlock, err := aes.NewCipher(peerKey) if err != nil { return err } rxStream := cipher.NewCTR(rxBlock, peerIV) conn.rx = &cipher.StreamReader{S: rxStream, R: conn.Conn} hsHdr := make([]byte, hsLen) if _, err := io.ReadFull(conn, hsHdr[:]); err != nil { return err } // If the MAGIC_VALUE does not match, or the PADLEN value is greater than // MAX_PADDING, the party receiving it should close the connection // immediately. if peerMagic := binary.BigEndian.Uint32(hsHdr[0:4]); peerMagic != magicValue { return fmt.Errorf("invalid magic value: %x", peerMagic) } padLen = binary.BigEndian.Uint32(hsHdr[4:8]) if padLen > maxPadding { return fmt.Errorf("padlen too long: %d", padLen) } // Otherwise, it should read the remaining PADLEN bytes of padding data // and discard them. tmp := make([]byte, padLen) if _, err := io.ReadFull(conn.Conn, tmp); err != nil { // Note: Skips AES. return err } // Derive the actual keys. if err := conn.kdf(seed[:], peerSeed[:]); err != nil { return err } return 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 }
// Decode decodes a stream of data and returns the length if any. ErrAgain is // a temporary failure, all other errors MUST be treated as fatal and the // session aborted. func (decoder *Decoder) Decode(data []byte, frames *bytes.Buffer) (int, error) { // A length of 0 indicates that we do not know how big the next frame is // going to be. if decoder.nextLength == 0 { // Attempt to pull out the next frame length. if lengthLength > frames.Len() { return 0, ErrAgain } // Remove the length field from the buffer. var obfsLen [lengthLength]byte _, err := io.ReadFull(frames, obfsLen[:]) if err != nil { return 0, err } // Derive the nonce the peer used. if err = decoder.nonce.bytes(&decoder.nextNonce); err != nil { return 0, err } // Deobfuscate the length field. length := binary.BigEndian.Uint16(obfsLen[:]) lengthMask := decoder.drbg.NextBlock() length ^= binary.BigEndian.Uint16(lengthMask) if maxFrameLength < length || minFrameLength > length { // Per "Plaintext Recovery Attacks Against SSH" by // Martin R. Albrecht, Kenneth G. Paterson and Gaven J. Watson, // there are a class of attacks againt protocols that use similar // sorts of framing schemes. // // While obfs4 should not allow plaintext recovery (CBC mode is // not used), attempt to mitigate out of bound frame length errors // by pretending that the length was a random valid range as per // the countermeasure suggested by Denis Bider in section 6 of the // paper. decoder.nextLengthInvalid = true length = uint16(csrand.IntRange(minFrameLength, maxFrameLength)) } decoder.nextLength = length } if int(decoder.nextLength) > frames.Len() { return 0, ErrAgain } // Unseal the frame. var box [maxFrameLength]byte n, err := io.ReadFull(frames, box[:decoder.nextLength]) if err != nil { return 0, err } out, ok := secretbox.Open(data[:0], box[:n], &decoder.nextNonce, &decoder.key) if !ok || decoder.nextLengthInvalid { // When a random length is used (on length error) the tag should always // mismatch, but be paranoid. return 0, ErrTagMismatch } // Clean up and prepare for the next frame. decoder.nextLength = 0 decoder.nonce.counter++ return len(out), nil }
func newTicketClientHandshake(mac hash.Hash, ticket *ssTicket) *ssTicketClientHandshake { hs := &ssTicketClientHandshake{mac: mac, ticket: ticket} hs.padLen = csrand.IntRange(ticketMinPadLength, ticketMaxPadLength) return hs }