Cloak/internal/multiplex/obfs.go

198 lines
5.8 KiB
Go
Raw Normal View History

2018-12-09 23:45:06 +00:00
package multiplex
2018-10-07 17:09:45 +00:00
import (
"crypto/aes"
2019-07-31 23:16:33 +00:00
"crypto/cipher"
2018-10-07 17:09:45 +00:00
"encoding/binary"
2018-12-09 23:45:06 +00:00
"errors"
2019-09-01 19:23:45 +00:00
"fmt"
2020-04-14 00:53:28 +00:00
"github.com/cbeuw/Cloak/internal/common"
"golang.org/x/crypto/chacha20poly1305"
"golang.org/x/crypto/salsa20"
2018-10-07 17:09:45 +00:00
)
2020-04-13 21:48:28 +00:00
type Obfser func(*Frame, []byte, int) (int, error)
2018-12-09 23:45:06 +00:00
type Deobfser func([]byte) (*Frame, error)
var u32 = binary.BigEndian.Uint32
var u64 = binary.BigEndian.Uint64
var putU32 = binary.BigEndian.PutUint32
var putU64 = binary.BigEndian.PutUint64
const HEADER_LEN = 14
const salsa20NonceSize = 8
const (
E_METHOD_PLAIN = iota
E_METHOD_AES_GCM
E_METHOD_CHACHA20_POLY1305
)
2020-04-08 11:18:20 +00:00
// Obfuscator is responsible for the obfuscation and deobfuscation of frames
type Obfuscator struct {
// Used in Stream.Write. Add multiplexing headers, encrypt and add TLS header
Obfs Obfser
// Remove TLS header, decrypt and unmarshall frames
Deobfs Deobfser
SessionKey [32]byte
maxOverhead int
2020-04-08 11:18:20 +00:00
}
// MakeObfs returns a function of type Obfser. An Obfser takes three arguments:
// a *Frame with all the field set correctly, a []byte as buffer to put encrypted
// message in, and an int called payloadOffsetInBuf to be used when *Frame.payload
// is in the byte slice used as buffer (2nd argument). payloadOffsetInBuf specifies
// the index at which data belonging to *Frame.Payload starts in the buffer.
func MakeObfs(salsaKey [32]byte, payloadCipher cipher.AEAD) Obfser {
2020-04-13 21:48:28 +00:00
obfs := func(f *Frame, buf []byte, payloadOffsetInBuf int) (int, error) {
2019-08-20 21:43:04 +00:00
// we need the encrypted data to be at least 8 bytes to be used as nonce for salsa20 stream header encryption
// this will be the case if the encryption method is an AEAD cipher, however for plain, it's well possible
// that the frame payload is smaller than 8 bytes, so we need to add on the difference
2020-04-13 21:48:28 +00:00
payloadLen := len(f.Payload)
if payloadLen == 0 {
return 0, errors.New("payload cannot be empty")
}
2020-04-12 15:10:48 +00:00
var extraLen int
2019-08-04 09:38:49 +00:00
if payloadCipher == nil {
extraLen = salsa20NonceSize - payloadLen
if extraLen < 0 {
// if our payload is already greater than 8 bytes
2020-04-12 15:10:48 +00:00
extraLen = 0
2019-08-04 09:38:49 +00:00
}
} else {
2020-04-12 15:10:48 +00:00
extraLen = payloadCipher.Overhead()
if extraLen < salsa20NonceSize {
2020-04-12 15:10:48 +00:00
return 0, errors.New("AEAD's Overhead cannot be fewer than 8 bytes")
}
2019-08-04 09:38:49 +00:00
}
2020-04-13 21:48:28 +00:00
usefulLen := HEADER_LEN + payloadLen + extraLen
if len(buf) < usefulLen {
return 0, errors.New("obfs buffer too small")
2019-08-04 09:38:49 +00:00
}
2019-08-20 21:43:04 +00:00
// we do as much in-place as possible to save allocation
2020-04-13 21:48:28 +00:00
payload := buf[HEADER_LEN : HEADER_LEN+payloadLen]
if payloadOffsetInBuf != HEADER_LEN {
// if payload is not at the correct location in buffer
copy(payload, f.Payload)
}
2019-08-20 21:43:04 +00:00
2020-04-13 21:48:28 +00:00
header := buf[:HEADER_LEN]
putU32(header[0:4], f.StreamID)
putU64(header[4:12], f.Seq)
header[12] = f.Closing
2020-04-12 15:10:48 +00:00
header[13] = byte(extraLen)
2019-07-31 23:43:33 +00:00
if payloadCipher == nil {
2020-04-12 15:10:48 +00:00
if extraLen != 0 { // read nonce
2020-04-13 21:48:28 +00:00
extra := buf[usefulLen-extraLen : usefulLen]
2020-04-14 00:53:28 +00:00
common.CryptoRandRead(extra)
2019-08-04 09:38:49 +00:00
}
2019-07-31 23:43:33 +00:00
} else {
payloadCipher.Seal(payload[:0], header[:payloadCipher.NonceSize()], payload, nil)
2019-06-09 14:03:28 +00:00
}
2019-06-09 11:05:41 +00:00
nonce := buf[usefulLen-salsa20NonceSize : usefulLen]
salsa20.XORKeyStream(header, header, nonce, &salsaKey)
2019-08-04 09:38:49 +00:00
return usefulLen, nil
2018-10-07 17:09:45 +00:00
}
return obfs
}
// MakeDeobfs returns a function Deobfser. A Deobfser takes in a single byte slice,
// containing the message to be decrypted, and returns a *Frame containing the frame
// information and plaintext
func MakeDeobfs(salsaKey [32]byte, payloadCipher cipher.AEAD) Deobfser {
// stream header length + minimum data size (i.e. nonce size of salsa20)
const minInputLen = HEADER_LEN + salsa20NonceSize
2018-12-09 23:45:06 +00:00
deobfs := func(in []byte) (*Frame, error) {
if len(in) < minInputLen {
return nil, fmt.Errorf("input size %v, but it cannot be shorter than %v bytes", len(in), minInputLen)
2018-12-09 23:45:06 +00:00
}
header := in[:HEADER_LEN]
pldWithOverHead := in[HEADER_LEN:] // payload + potential overhead
nonce := in[len(in)-salsa20NonceSize:]
salsa20.XORKeyStream(header, header, nonce, &salsaKey)
streamID := u32(header[0:4])
seq := u64(header[4:12])
closing := header[12]
extraLen := header[13]
2019-08-07 16:53:34 +00:00
usefulPayloadLen := len(pldWithOverHead) - int(extraLen)
2020-04-12 15:10:48 +00:00
if usefulPayloadLen < 0 || usefulPayloadLen > len(pldWithOverHead) {
return nil, errors.New("extra length is negative or extra length is greater than total pldWithOverHead length")
2019-08-07 16:22:40 +00:00
}
2019-08-07 16:53:34 +00:00
var outputPayload []byte
2019-06-09 11:05:41 +00:00
2019-07-31 23:43:33 +00:00
if payloadCipher == nil {
2019-08-07 16:53:34 +00:00
if extraLen == 0 {
outputPayload = pldWithOverHead
} else {
outputPayload = pldWithOverHead[:usefulPayloadLen]
}
2019-07-31 23:43:33 +00:00
} else {
_, err := payloadCipher.Open(pldWithOverHead[:0], header[:payloadCipher.NonceSize()], pldWithOverHead, nil)
2019-07-31 23:43:33 +00:00
if err != nil {
return nil, err
}
2019-08-07 16:53:34 +00:00
outputPayload = pldWithOverHead[:usefulPayloadLen]
2019-07-31 23:43:33 +00:00
}
2018-12-09 23:45:06 +00:00
ret := &Frame{
2018-10-27 22:35:46 +00:00
StreamID: streamID,
Seq: seq,
Closing: closing,
Payload: outputPayload,
2018-10-07 17:09:45 +00:00
}
2018-12-09 23:45:06 +00:00
return ret, nil
2018-10-07 17:09:45 +00:00
}
return deobfs
}
2020-04-10 15:09:05 +00:00
func MakeObfuscator(encryptionMethod byte, sessionKey [32]byte) (obfuscator Obfuscator, err error) {
obfuscator = Obfuscator{
SessionKey: sessionKey,
}
var payloadCipher cipher.AEAD
switch encryptionMethod {
case E_METHOD_PLAIN:
payloadCipher = nil
obfuscator.maxOverhead = salsa20NonceSize
case E_METHOD_AES_GCM:
var c cipher.Block
2020-04-07 20:15:28 +00:00
c, err = aes.NewCipher(sessionKey[:])
if err != nil {
return
}
payloadCipher, err = cipher.NewGCM(c)
if err != nil {
return
}
obfuscator.maxOverhead = payloadCipher.Overhead()
case E_METHOD_CHACHA20_POLY1305:
2020-04-07 20:15:28 +00:00
payloadCipher, err = chacha20poly1305.New(sessionKey[:])
if err != nil {
return
}
obfuscator.maxOverhead = payloadCipher.Overhead()
default:
2020-04-10 15:09:05 +00:00
return obfuscator, errors.New("Unknown encryption method")
}
if payloadCipher != nil {
if payloadCipher.NonceSize() > HEADER_LEN {
return obfuscator, errors.New("payload AEAD's nonce size cannot be greater than size of frame header")
}
}
obfuscator.Obfs = MakeObfs(sessionKey, payloadCipher)
obfuscator.Deobfs = MakeDeobfs(sessionKey, payloadCipher)
return
}