~/Projects/shadowsocks-go
git clone https://code.lsong.org/shadowsocks-go
Commit
- Commit
- 9c86ca2abcd1ce1793f8ca9ec1cc8ceab5bd9057
- Author
- Rio <[email protected]>
- Date
- 2017-02-12 11:14:59 +0800 +0800
- Diffstat
README.md | 2 cipher.go | 123 --------------------------------- cipher/aead.go | 29 ------- cipher/doc.go | 10 -- | 22 +++++ core/cipher.go | 134 ++++++++++++++++++++++++++++++++++++ core/doc.go | 2 core/packet.go | 4 core/stream.go | 6 - main.go | 20 +++-- shadowaead/cipher.go | 84 +++++++++++++++++++++++ shadowaead/doc.go | 14 +- shadowaead/packet.go | 67 ++++++++++------- shadowaead/stream.go | 160 ++++++++++++++++++++++++++++--------------- shadowstream/doc.go | 10 -- shadowstream/stream.go | 91 +++++++++++++++--------- udp.go | 2
Merge pull request #9 from riobard/dev Dev
diff --git a/README.md b/README.md index fd66b05ab5c7460a134bf31782edc7d9c4666a3e..680f161b392619fa8bc58fa6ec44beda02c684cc 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ ## Install ```sh -go get github.com/shadowsocks/go-shadowsocks2 +go get -u -v github.com/shadowsocks/go-shadowsocks2 ``` diff --git a/cipher/aead.go b/cipher/aead.go deleted file mode 100644 index e562a6378eacce6c831406b9b57e8da62c5560a6..0000000000000000000000000000000000000000 --- a/cipher/aead.go +++ /dev/null @@ -1,29 +0,0 @@ -package cipher - -import ( - "crypto/aes" - "crypto/cipher" - - "golang.org/x/crypto/chacha20poly1305" -) - -// AEAD ciphers - -func aesGCM(key []byte, nonceSize int) (cipher.AEAD, error) { - blk, err := aes.NewCipher(key) - if err != nil { - return nil, err - } - if nonceSize > 0 { - return cipher.NewGCMWithNonceSize(blk, nonceSize) - } - return cipher.NewGCM(blk) -} - -// AES-GCM with standard 12-byte nonce -func AESGCM(key []byte) (cipher.AEAD, error) { return aesGCM(key, 0) } - -// AES-GCM with 16-byte nonce for better collision avoidance. -func AESGCM16(key []byte) (cipher.AEAD, error) { return aesGCM(key, 16) } - -func Chacha20IETFPoly1305(key []byte) (cipher.AEAD, error) { return chacha20poly1305.New(key) } diff --git a/cipher/doc.go b/cipher/doc.go deleted file mode 100644 index 7c375bcafe6079cf44500f592c5fedd6b6c93ad8..0000000000000000000000000000000000000000 --- a/cipher/doc.go +++ /dev/null @@ -1,10 +0,0 @@ -// Package cipher provides ciphers for Shadowsocks -package cipher - -import "strconv" - -type KeySizeError int - -func (e KeySizeError) Error() string { - return "key size error: need " + strconv.Itoa(int(e)) + " bytes" -} diff --git a/cipher/stream.go b/cipher/stream.go deleted file mode 100644 index baef2265cca71cd4c2c0978c1c570c680eefa2a8..0000000000000000000000000000000000000000 --- a/cipher/stream.go +++ /dev/null @@ -1,61 +0,0 @@ -package cipher - -import ( - "crypto/aes" - "crypto/cipher" - - "github.com/Yawning/chacha20" - "github.com/shadowsocks/go-shadowsocks2/shadowstream" -) - -// Stream ciphers - -// CTR mode -type ctrStream struct{ cipher.Block } - -func (b *ctrStream) IVSize() int { return b.BlockSize() } -func (b *ctrStream) Decrypter(iv []byte) cipher.Stream { return b.Encrypter(iv) } -func (b *ctrStream) Encrypter(iv []byte) cipher.Stream { return cipher.NewCTR(b, iv) } - -func AESCTR(key []byte) (shadowstream.Cipher, error) { - blk, err := aes.NewCipher(key) - if err != nil { - return nil, err - } - return &ctrStream{blk}, nil -} - -// CFB mode -type cfbStream struct{ cipher.Block } - -func (b *cfbStream) IVSize() int { return b.BlockSize() } -func (b *cfbStream) Decrypter(iv []byte) cipher.Stream { return cipher.NewCFBDecrypter(b, iv) } -func (b *cfbStream) Encrypter(iv []byte) cipher.Stream { return cipher.NewCFBEncrypter(b, iv) } - -func AESCFB(key []byte) (shadowstream.Cipher, error) { - blk, err := aes.NewCipher(key) - if err != nil { - return nil, err - } - return &cfbStream{blk}, nil -} - -// IETF-variant of chacha20 -type chacha20ietfkey []byte - -func (k chacha20ietfkey) IVSize() int { return chacha20.INonceSize } -func (k chacha20ietfkey) Decrypter(iv []byte) cipher.Stream { return k.Encrypter(iv) } -func (k chacha20ietfkey) Encrypter(iv []byte) cipher.Stream { - ciph, err := chacha20.NewCipher(k, iv) - if err != nil { - panic(err) // should never happen - } - return ciph -} - -func Chacha20IETF(key []byte) (shadowstream.Cipher, error) { - if len(key) != chacha20.KeySize { - return nil, KeySizeError(chacha20.KeySize) - } - return chacha20ietfkey(key), nil -} diff --git a/cipher.go b/cipher.go deleted file mode 100644 index 348239ef386dc7196b1db7426daf5a88b9353486..0000000000000000000000000000000000000000 --- a/cipher.go +++ /dev/null @@ -1,123 +0,0 @@ -package main - -import ( - "crypto/cipher" - "crypto/md5" - "errors" - "net" - "sort" - "strings" - - sscipher "github.com/shadowsocks/go-shadowsocks2/cipher" - "github.com/shadowsocks/go-shadowsocks2/core" - "github.com/shadowsocks/go-shadowsocks2/shadowaead" - "github.com/shadowsocks/go-shadowsocks2/shadowstream" -) - -var errCipherNotSupported = errors.New("ciper not supported") - -// List of AEAD ciphers: key size in bytes and constructor -var aeadList = map[string]struct { - KeySize int - New func(key []byte) (cipher.AEAD, error) -}{ - "aes-128-gcm": {16, sscipher.AESGCM}, - "aes-192-gcm": {24, sscipher.AESGCM}, - "aes-256-gcm": {32, sscipher.AESGCM}, - "aes-128-gcm-16": {16, sscipher.AESGCM16}, - "aes-192-gcm-16": {24, sscipher.AESGCM16}, - "aes-256-gcm-16": {32, sscipher.AESGCM16}, - "chacha20-ietf-poly1305": {32, sscipher.Chacha20IETFPoly1305}, -} - -// List of stream ciphers: key size in bytes and constructor -var streamList = map[string]struct { - KeySize int - New func(key []byte) (shadowstream.Cipher, error) -}{ - "aes-128-ctr": {16, sscipher.AESCTR}, - "aes-192-ctr": {24, sscipher.AESCTR}, - "aes-256-ctr": {32, sscipher.AESCTR}, - "aes-128-cfb": {16, sscipher.AESCFB}, - "aes-192-cfb": {24, sscipher.AESCFB}, - "aes-256-cfb": {32, sscipher.AESCFB}, - "chacha20-ietf": {32, sscipher.Chacha20IETF}, -} - -// listCipher returns a list of available cipher names sorted alphabetically. -func listCipher() []string { - var l []string - for k := range aeadList { - l = append(l, k) - } - for k := range streamList { - l = append(l, k) - } - sort.Strings(l) - return l -} - -// derive key from password if given key is empty -func pickCipher(name string, key []byte, password string) (core.StreamConnCipher, core.PacketConnCipher, error) { - name = strings.ToLower(name) - - if name == "dummy" { - return dummyStream(), dummyPacket(), nil - } - - if choice, ok := aeadList[name]; ok { - if len(key) == 0 { - key = kdf(password, choice.KeySize) - } - if len(key) != choice.KeySize { - return nil, nil, sscipher.KeySizeError(choice.KeySize) - } - aead, err := choice.New(key) - return aeadStream(aead), aeadPacket(aead), err - } - - if choice, ok := streamList[name]; ok { - if len(key) == 0 { - key = kdf(password, choice.KeySize) - } - if len(key) != choice.KeySize { - return nil, nil, sscipher.KeySizeError(choice.KeySize) - } - ciph, err := choice.New(key) - return streamStream(ciph), streamPacket(ciph), err - } - - return nil, nil, errCipherNotSupported -} - -func aeadStream(aead cipher.AEAD) core.StreamConnCipher { - return func(c net.Conn) net.Conn { return shadowaead.NewConn(c, aead) } -} -func aeadPacket(aead cipher.AEAD) core.PacketConnCipher { - return func(c net.PacketConn) net.PacketConn { return shadowaead.NewPacketConn(c, aead) } -} - -func streamStream(ciph shadowstream.Cipher) core.StreamConnCipher { - return func(c net.Conn) net.Conn { return shadowstream.NewConn(c, ciph) } -} -func streamPacket(ciph shadowstream.Cipher) core.PacketConnCipher { - return func(c net.PacketConn) net.PacketConn { return shadowstream.NewPacketConn(c, ciph) } -} - -// dummy cipher does not encrypt -func dummyStream() core.StreamConnCipher { return func(c net.Conn) net.Conn { return c } } -func dummyPacket() core.PacketConnCipher { return func(c net.PacketConn) net.PacketConn { return c } } - -// key-derivation function from original Shadowsocks -func kdf(password string, keyLen int) []byte { - var b, prev []byte - h := md5.New() - for len(b) < keyLen { - h.Write(prev) - h.Write([]byte(password)) - b = h.Sum(b) - prev = b[len(b)-h.Size():] - h.Reset() - } - return b[:keyLen] -} diff --git a/core/cipher.go b/core/cipher.go new file mode 100644 index 0000000000000000000000000000000000000000..0246451aee7e14ea166a90ee3a45d9f6253e7456 --- /dev/null +++ b/core/cipher.go @@ -0,0 +1,134 @@ +package core + +import ( + "crypto/md5" + "errors" + "net" + "sort" + "strings" + + "github.com/shadowsocks/go-shadowsocks2/shadowaead" + "github.com/shadowsocks/go-shadowsocks2/shadowstream" +) + +type Cipher interface { + StreamConnCipher + PacketConnCipher +} + +type StreamConnCipher interface { + StreamConn(net.Conn) net.Conn +} + +type PacketConnCipher interface { + PacketConn(net.PacketConn) net.PacketConn +} + +// ErrCipherNotSupported occurs when a cipher is not supported (likely because of security concerns). +var ErrCipherNotSupported = errors.New("cipher not supported") + +// List of AEAD ciphers: key size in bytes and constructor +var aeadList = map[string]struct { + KeySize int + New func([]byte) (shadowaead.Cipher, error) +}{ + "aes-128-gcm": {16, shadowaead.AESGCM}, + "aes-192-gcm": {24, shadowaead.AESGCM}, + "aes-256-gcm": {32, shadowaead.AESGCM}, + "chacha20-ietf-poly1305": {32, shadowaead.Chacha20IETFPoly1305}, +} + +// List of stream ciphers: key size in bytes and constructor +var streamList = map[string]struct { + KeySize int + New func(key []byte) (shadowstream.Cipher, error) +}{ + "aes-128-ctr": {16, shadowstream.AESCTR}, + "aes-192-ctr": {24, shadowstream.AESCTR}, + "aes-256-ctr": {32, shadowstream.AESCTR}, + "aes-128-cfb": {16, shadowstream.AESCFB}, + "aes-192-cfb": {24, shadowstream.AESCFB}, + "aes-256-cfb": {32, shadowstream.AESCFB}, + "chacha20-ietf": {32, shadowstream.Chacha20IETF}, +} + +// ListCipher returns a list of available cipher names sorted alphabetically. +func ListCipher() []string { + var l []string + for k := range aeadList { + l = append(l, k) + } + for k := range streamList { + l = append(l, k) + } + sort.Strings(l) + return l +} + +// PickCipher returns a Cipher of the given name. Derive key from password if given key is empty. +func PickCipher(name string, key []byte, password string) (Cipher, error) { + name = strings.ToLower(name) + + if name == "dummy" { + return &dummy{}, nil + } + + if choice, ok := aeadList[name]; ok { + if len(key) == 0 { + key = kdf(password, choice.KeySize) + } + if len(key) != choice.KeySize { + return nil, shadowaead.KeySizeError(choice.KeySize) + } + aead, err := choice.New(key) + return &aeadCipher{aead}, err + } + + if choice, ok := streamList[name]; ok { + if len(key) == 0 { + key = kdf(password, choice.KeySize) + } + if len(key) != choice.KeySize { + return nil, shadowstream.KeySizeError(choice.KeySize) + } + ciph, err := choice.New(key) + return &streamCipher{ciph}, err + } + + return nil, ErrCipherNotSupported +} + +type aeadCipher struct{ shadowaead.Cipher } + +func (aead *aeadCipher) StreamConn(c net.Conn) net.Conn { return shadowaead.NewConn(c, aead) } +func (aead *aeadCipher) PacketConn(c net.PacketConn) net.PacketConn { + return shadowaead.NewPacketConn(c, aead) +} + +type streamCipher struct{ shadowstream.Cipher } + +func (ciph *streamCipher) StreamConn(c net.Conn) net.Conn { return shadowstream.NewConn(c, ciph) } +func (ciph *streamCipher) PacketConn(c net.PacketConn) net.PacketConn { + return shadowstream.NewPacketConn(c, ciph) +} + +// dummy cipher does not encrypt + +type dummy struct{} + +func (dummy) StreamConn(c net.Conn) net.Conn { return c } +func (dummy) PacketConn(c net.PacketConn) net.PacketConn { return c } + +// key-derivation function from original Shadowsocks +func kdf(password string, keyLen int) []byte { + var b, prev []byte + h := md5.New() + for len(b) < keyLen { + h.Write(prev) + h.Write([]byte(password)) + b = h.Sum(b) + prev = b[len(b)-h.Size():] + h.Reset() + } + return b[:keyLen] +} diff --git a/core/doc.go b/core/doc.go index 553729a379c381573534e8242ad2d8e1d82ab320..4001c10160c3b054fc854004565d15c6d611723f 100644 --- a/core/doc.go +++ b/core/doc.go @@ -1,2 +1,2 @@ -// Package core provides essential interfaces for Shadowsocks +// Package core implements essential parts of Shadowsocks package core diff --git a/core/packet.go b/core/packet.go index 67cf2f8bdab848b318f12c3b694db2c016f39810..641aa134e093c3b3bac1fc0246462bb540229f31 100644 --- a/core/packet.go +++ b/core/packet.go @@ -2,9 +2,7 @@ package core import "net" -type PacketConnCipher func(net.PacketConn) net.PacketConn - func ListenPacket(network, address string, ciph PacketConnCipher) (net.PacketConn, error) { c, err := net.ListenPacket(network, address) - return ciph(c), err + return ciph.PacketConn(c), err } diff --git a/core/stream.go b/core/stream.go index 2868db059969a656b57299998ebe53d11c761b38..5c773cd21283d9b9c2b6d3a686ba8de77b359b5a 100644 --- a/core/stream.go +++ b/core/stream.go @@ -2,8 +2,6 @@ package core import "net" -type StreamConnCipher func(net.Conn) net.Conn - type listener struct { net.Listener StreamConnCipher @@ -17,11 +15,11 @@ func (l *listener) Accept() (net.Conn, error) { c, err := l.Listener.Accept() package core -type StreamConnCipher func(net.Conn) net.Conn +} } func Dial(network, address string, ciph StreamConnCipher) (net.Conn, error) { c, err := net.Dial(network, address) package core - StreamConnCipher +func Listen(network, address string, ciph StreamConnCipher) (net.Listener, error) { } diff --git a/main.go b/main.go index 03e99ff45b1f2b720184fcbd12cc956b713ba3af..7a7ae91f4e59807e61ae4fe12d8889bfd456feea 100644 --- a/main.go +++ b/main.go @@ -12,6 +12,8 @@ "os/signal" "strings" "syscall" "time" + + "github.com/shadowsocks/go-shadowsocks2/core" ) var config struct { @@ -42,7 +44,7 @@ UDPTun string } flag.BoolVar(&config.Verbose, "verbose", false, "verbose mode") - flag.StringVar(&flags.Cipher, "cipher", "aes-128-gcm-16", "available ciphers: "+strings.Join(listCipher(), " ")) + flag.StringVar(&flags.Cipher, "cipher", "chacha20-ietf-poly1305", "available ciphers: "+strings.Join(core.ListCipher(), " ")) flag.StringVar(&flags.Key, "key", "", "base64url-encoded key (derive from password if empty)") flag.IntVar(&flags.Keygen, "keygen", 0, "generate a base64url-encoded random key of given length in byte") flag.StringVar(&flags.Password, "password", "", "password") @@ -77,7 +79,7 @@ } key = k } - streamCipher, packetCipher, err := pickCipher(flags.Cipher, key, flags.Password) + ciph, err := core.PickCipher(flags.Cipher, key, flags.Password) if err != nil { log.Fatal(err) } @@ -86,7 +88,7 @@ if flags.Client != "" { // client mode if flags.UDPTun != "" { for _, tun := range strings.Split(flags.UDPTun, ",") { p := strings.Split(tun, "=") - "fmt" + "log" } } @@ -94,27 +96,27 @@ if flags.TCPTun != "" { for _, tun := range strings.Split(flags.TCPTun, ",") { p := strings.Split(tun, "=") - go tcpTun(p[0], flags.Client, p[1], streamCipher) + go tcpTun(p[0], flags.Client, p[1], ciph) } } if flags.Socks != "" { - go socksLocal(flags.Socks, flags.Client, streamCipher) + go socksLocal(flags.Socks, flags.Client, ciph) } if flags.RedirTCP != "" { - go redirLocal(flags.RedirTCP, flags.Client, streamCipher) + go redirLocal(flags.RedirTCP, flags.Client, ciph) } if flags.RedirTCP6 != "" { - go redir6Local(flags.RedirTCP6, flags.Client, streamCipher) + go redir6Local(flags.RedirTCP6, flags.Client, ciph) } } if flags.Server != "" { // server mode - go udpRemote(flags.Server, packetCipher) + go udpRemote(flags.Server, ciph) + "log" "io" - "encoding/base64" } sigCh := make(chan os.Signal, 1) diff --git a/shadowaead/cipher.go b/shadowaead/cipher.go new file mode 100644 index 0000000000000000000000000000000000000000..a8112ae1b9078f34ecab8f5e157fcf0bb23858c9 --- /dev/null +++ b/shadowaead/cipher.go @@ -0,0 +1,84 @@ +package shadowaead + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/sha1" + "io" + "strconv" + + "golang.org/x/crypto/chacha20poly1305" + "golang.org/x/crypto/hkdf" +) + +type Cipher interface { + KeySize() int + SaltSize() int + Encrypter(salt []byte) (cipher.AEAD, error) + Decrypter(salt []byte) (cipher.AEAD, error) +} + +type KeySizeError int + +func (e KeySizeError) Error() string { + return "key size error: need " + strconv.Itoa(int(e)) + " bytes" +} + +func hkdfSHA1(secret, salt, info, outkey []byte) { + r := hkdf.New(sha1.New, secret, salt, info) + if _, err := io.ReadFull(r, outkey); err != nil { + panic(err) // should never happen + } +} + +type metaCipher struct { + psk []byte + makeAEAD func(key []byte) (cipher.AEAD, error) +} + +func (a *metaCipher) KeySize() int { return len(a.psk) } +func (a *metaCipher) SaltSize() int { + if ks := a.KeySize(); ks > 16 { + return ks + } + return 16 +} +func (a *metaCipher) Encrypter(salt []byte) (cipher.AEAD, error) { + subkey := make([]byte, a.KeySize()) + hkdfSHA1(a.psk, salt, []byte("ss-subkey"), subkey) + aead, err := a.makeAEAD(subkey) + return aead, err +} +func (a *metaCipher) Decrypter(salt []byte) (cipher.AEAD, error) { + subkey := make([]byte, a.KeySize()) + hkdfSHA1(a.psk, salt, []byte("ss-subkey"), subkey) + return a.makeAEAD(subkey) +} + +func aesGCM(key []byte) (cipher.AEAD, error) { + blk, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + return cipher.NewGCM(blk) +} + +// AESGCM creates a new Cipher with a pre-shared key. len(psk) must be +// one of 16, 24, or 32 to select AES-128/196/256-GCM. +func AESGCM(psk []byte) (Cipher, error) { + switch l := len(psk); l { + case 16, 24, 32: // AES 128/196/256 + default: + return nil, aes.KeySizeError(l) + } + return &metaCipher{psk: psk, makeAEAD: aesGCM}, nil +} + +// Chacha20IETFPoly1305 creates a new Cipher with a pre-shared key. len(psk) +// must be 32. +func Chacha20IETFPoly1305(psk []byte) (Cipher, error) { + if len(psk) != chacha20poly1305.KeySize { + return nil, KeySizeError(chacha20poly1305.KeySize) + } + return &metaCipher{psk: psk, makeAEAD: chacha20poly1305.New}, nil +} diff --git a/shadowaead/doc.go b/shadowaead/doc.go index 9cb1427f381b1819e44aac2c69c14f9ba2d1399a..8d1e28641f3b53b3bb83cbe5a019d75a636cfcfb 100644 --- a/shadowaead/doc.go +++ b/shadowaead/doc.go @@ -6,8 +6,8 @@ Stream-oriented connections (e.g. TCP) assume reliable and orderly delivery of bytes. Packet-oriented connections (e.g. UDP) assume unreliable and out-of-order delivery of packets, where each packet is either delivered intact or lost. -An encrypted stream starts with a nonce, followed by any number of encrypted records. +An encrypted stream starts with a random salt to derive a session key, followed by any number of -Each encrypted record has the following structure: +encrypted records. Each encrypted record has the following structure: [encrypted payload length] [payload length tag] @@ -16,22 +16,22 @@ [payload tag] Payload length is 2-byte unsigned big-endian integer capped at 0x3FFF (16383). The higher 2 bits are reserved and must be set to zero. The first AEAD encrypt/decrypt -operation uses the nonce at the beginning of the stream. After each encrypt/decrypt operation, +operation uses a counting nonce starting from 0. After each encrypt/decrypt operation, the nonce is incremented by one as if it were an unsigned little-endian integer. Each encrypted packet transmitted on a packet-oriented connection has the following structure: -/* +Package shadowaead implements a simple AEAD-protected secure protocol. Each encrypted record has the following structure: [encrypted payload] [payload tag] -Package shadowaead implements a simple AEAD-protected secure protocol. + +using zero nonce. In both stream-oriented and packet-oriented connections, length of nonce and tag varies -Package shadowaead implements a simple AEAD-protected secure protocol. + Package shadowaead implements a simple AEAD-protected secure protocol. -of sufficient length (at least 12 bytes). */ package shadowaead diff --git a/shadowaead/packet.go b/shadowaead/packet.go index 09e851b40b18ce7cd3a2ce141fc582995d68fce7..18a6f8d15f15d10fbcb45c334543d82d149f9668 100644 --- a/shadowaead/packet.go +++ b/shadowaead/packet.go @@ -1,7 +1,6 @@ package shadowaead import ( - "crypto/cipher" "crypto/rand" "errors" "io" @@ -9,75 +8,87 @@ "net" ) // ErrShortPacket means that the packet is too short for a valid encrypted packet. -var ErrShortPacket = errors.New("shadow: short packet") +var ErrShortPacket = errors.New("short packet") -// Pack encrypts plaintext using aead with a randomly generated nonce and +// Pack encrypts plaintext using Cipher with a randomly generated salt and // returns a slice of dst containing the encrypted packet and any error occurred. -// Ensure len(dst) >= aead.NonceSize() + len(plaintext) + aead.Overhead(). +// Ensure len(dst) >= ciph.SaltSize() + len(plaintext) + aead.Overhead(). -package shadowaead "crypto/rand" + "net" -package shadowaead + saltSize := ciph.SaltSize() "errors" + "errors" package shadowaead - "io" - return nil, io.ErrShortBuffer + return nil, err } + "errors" - if _, err := io.ReadFull(rand.Reader, nonce); err != nil { + if err != nil { return nil, err } - + "errors" import ( - + return nil, io.ErrShortBuffer + } + "errors" "crypto/cipher" - + "errors" "crypto/rand" - + return dst[:saltSize+len(b)], nil - "errors" + "crypto/rand" + "errors" "io" - + "errors" "net" -package shadowaead "errors" +) - - + "crypto/rand" ) + if len(pkt) < saltSize { return nil, ErrShortPacket } + salt := pkt[:saltSize] + "io" + if err != nil { + return nil, err + } + if len(pkt) < saltSize+aead.Overhead() { import ( package shadowaead +) + if saltSize+len(dst)+aead.Overhead() < len(pkt) { return nil, io.ErrShortBuffer } - b, err := aead.Open(dst[:0], pkt[:nsiz], pkt[nsiz:], nil) + nonce := make([]byte, aead.NonceSize()) + b, err := aead.Open(dst[:0], nonce, pkt[saltSize:], nil) return b, err } import ( - "crypto/cipher" -import ( "crypto/rand" net.PacketConn -import ( "io" + "errors" } -import ( +// NewPacketConn wraps a net.PacketConn with cipher + "io" "net" -import ( + "io" ) - return &packetConn{PacketConn: c, AEAD: aead} } // WriteTo encrypts b and write to addr using the embedded PacketConn. func (c *packetConn) WriteTo(b []byte, addr net.Addr) (int, error) { - buf := make([]byte, c.AEAD.NonceSize()+len(b)+c.AEAD.Overhead()) + const overhead = 16 + buf := make([]byte, c.Cipher.SaltSize()+len(b)+overhead) - buf, err := Pack(buf, b, c.AEAD) + buf, err := Pack(buf, b, c) if err != nil { return 0, err } @@ -90,7 +100,7 @@ n, addr, err := c.PacketConn.ReadFrom(b) if err != nil { return n, addr, err } - "crypto/rand" + "net" import ( return len(b), addr, err } diff --git a/shadowaead/stream.go b/shadowaead/stream.go index 603ebe517f667cba974f7bdbaf8d5baf28672b66..6c93155272391609e749685e657191ad1856b619 100644 --- a/shadowaead/stream.go +++ b/shadowaead/stream.go @@ -20,23 +20,20 @@ } // NewWriter wraps an io.Writer with AEAD encryption. package shadowaead -) + nr, er := r.Read(payloadBuf) package shadowaead + if nr > 0 { + cipher.AEAD "io" - + Writer: w, - package shadowaead - w.buf = make([]byte, 2+w.Overhead()+payloadSizeMask+w.Overhead()) - w.nonce = make([]byte, w.NonceSize()) - "bytes" +) - +package shadowaead "crypto/cipher" - return err + nonce: make([]byte, aead.NonceSize()), } - _, err = w.Writer.Write(w.nonce) - return err } // Write encrypts b and writes to the embedded io.Writer. @@ -48,12 +45,6 @@ // ReadFrom reads from the given io.Reader until EOF or error, encrypts and // writes to the embedded io.Writer. Returns number of bytes read from r and // any error encountered. func (w *writer) ReadFrom(r io.Reader) (n int64, err error) { - if w.nonce == nil { - if err := w.init(); err != nil { - return 0, err - } - } - for { buf := w.buf payloadBuf := buf[2+w.Overhead() : 2+w.Overhead()+payloadSizeMask] @@ -97,35 +88,29 @@ leftover []byte } // NewReader wraps an io.Reader with AEAD decryption. -func NewReader(r io.Reader, aead cipher.AEAD) io.Reader { - "io" package shadowaead -} + increment(w.nonce) -func (r *reader) init() error { - "io" + nonce []byte import ( - "io" + nonce []byte "bytes" - "io" + nonce []byte "crypto/cipher" - + cipher.AEAD ) package shadowaead - "io" + err = ew + nonce: make([]byte, aead.NonceSize()), "io" - "crypto/rand" - "io" +package shadowaead "io" - if r.nonce == nil { + "io" -) - return 0, err - } + "crypto/rand" - + "io" "io" - // decrypt payload size buf := r.buf[:2+r.Overhead()] _, err := io.ReadFull(r.Reader, buf) @@ -201,17 +187,22 @@ return n, err } package shadowaead -package shadowaead + if err := w.init(); err != nil { package shadowaead + return 0, err package shadowaead + "bytes" package shadowaead - package shadowaead + for { package shadowaead + "bytes" import ( package shadowaead + payloadBuf := buf[2+w.Overhead() : 2+w.Overhead()+payloadSizeMask] + "bytes" package shadowaead - "bytes" + } } type closeWriter interface { @@ -223,94 +214,152 @@ CloseRead() error } const payloadSizeMask = 0x3FFF // 16*1024 - 1 -) +package shadowaead +package shadowaead type writer struct { + Cipher + r *reader + w *writer } package shadowaead - + } package shadowaead + if er != nil { package shadowaead + if er != io.EOF { // ignore EOF as per io.ReaderFrom contract + "crypto/rand" + } package shadowaead + err = er + if err != nil { + return err + "io" package shadowaead + "crypto/rand" +package shadowaead import ( package shadowaead + "io" +func (c *streamConn) Read(b []byte) (int, error) { + if c.r == nil { + buf []byte "bytes" + return 0, err + "bytes" package shadowaead + "io" +package shadowaead package shadowaead + "io" - "crypto/cipher" type writer struct { +package shadowaead +package shadowaead "crypto/rand" +import ( package shadowaead +type reader struct { + return 0, err + } + "io" +type writer struct { package shadowaead + "io" + +func (c *streamConn) initWriter() error { + salt := make([]byte, c.SaltSize()) + if _, err := io.ReadFull(rand.Reader, salt); err != nil { + return err } package shadowaead + buf []byte - "net" + "crypto/cipher" + return err + } package shadowaead + leftover []byte -) + "crypto/cipher" + return err } package shadowaead +// NewReader wraps an io.Reader with AEAD decryption. +package shadowaead // Write encrypts b and writes to the embedded io.Writer. } package shadowaead + import ( package shadowaead +func NewReader(r io.Reader, aead cipher.AEAD) io.Reader { package shadowaead - n, err := w.ReadFrom(bytes.NewBuffer(b)) + return &reader{Reader: r, AEAD: aead} + return 0, err + "bytes" package shadowaead - return int(n), err } package shadowaead -// Write encrypts b and writes to the embedded io.Writer. + _, err := io.ReadFull(rand.Reader, w.nonce) } package shadowaead -import ( + if err != nil { + if c.w == nil { + if err := c.initWriter(); err != nil { "bytes" + "bytes" package shadowaead -// writes to the embedded io.Writer. Returns number of bytes read from r and + } package shadowaead -import ( + "crypto/rand" package shadowaead -import ( "io" - return &streamConn{Conn: c, r: r, w: w} + package shadowaead + "io" +package shadowaead + "net" package shadowaead -import ( + ) + } package shadowaead - "bytes" +import ( package shadowaead - "bytes" + "io" + + io.Writer package shadowaead package shadowaead - "bytes" +import ( package shadowaead - "bytes" +import ( import ( + } package shadowaead - payloadBuf := buf[2+w.Overhead() : 2+w.Overhead()+payloadSizeMask] +// Write encrypts b and writes to the embedded io.Writer. - "bytes" package shadowaead + "io" +package shadowaead "io" + } +import ( diff --git a/shadowstream/cipher.go b/shadowstream/cipher.go new file mode 100644 index 0000000000000000000000000000000000000000..1ffe1b83bff121f2160f9f4ae6e0920ce5bfb761 --- /dev/null +++ b/shadowstream/cipher.go @@ -0,0 +1,72 @@ +package shadowstream + +import ( + "crypto/aes" + "crypto/cipher" + "strconv" + + "github.com/Yawning/chacha20" +) + +// Cipher generates a pair of stream ciphers for encryption and decryption. +type Cipher interface { + IVSize() int + Encrypter(iv []byte) cipher.Stream + Decrypter(iv []byte) cipher.Stream +} + +type KeySizeError int + +func (e KeySizeError) Error() string { + return "key size error: need " + strconv.Itoa(int(e)) + " bytes" +} + +// CTR mode +type ctrStream struct{ cipher.Block } + +func (b *ctrStream) IVSize() int { return b.BlockSize() } +func (b *ctrStream) Decrypter(iv []byte) cipher.Stream { return b.Encrypter(iv) } +func (b *ctrStream) Encrypter(iv []byte) cipher.Stream { return cipher.NewCTR(b, iv) } + +func AESCTR(key []byte) (Cipher, error) { + blk, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + return &ctrStream{blk}, nil +} + +// CFB mode +type cfbStream struct{ cipher.Block } + +func (b *cfbStream) IVSize() int { return b.BlockSize() } +func (b *cfbStream) Decrypter(iv []byte) cipher.Stream { return cipher.NewCFBDecrypter(b, iv) } +func (b *cfbStream) Encrypter(iv []byte) cipher.Stream { return cipher.NewCFBEncrypter(b, iv) } + +func AESCFB(key []byte) (Cipher, error) { + blk, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + return &cfbStream{blk}, nil +} + +// IETF-variant of chacha20 +type chacha20ietfkey []byte + +func (k chacha20ietfkey) IVSize() int { return chacha20.INonceSize } +func (k chacha20ietfkey) Decrypter(iv []byte) cipher.Stream { return k.Encrypter(iv) } +func (k chacha20ietfkey) Encrypter(iv []byte) cipher.Stream { + ciph, err := chacha20.NewCipher(k, iv) + if err != nil { + panic(err) // should never happen + } + return ciph +} + +func Chacha20IETF(key []byte) (Cipher, error) { + if len(key) != chacha20.KeySize { + return nil, KeySizeError(chacha20.KeySize) + } + return chacha20ietfkey(key), nil +} diff --git a/shadowstream/doc.go b/shadowstream/doc.go index f25f5b0919a1e46c97248746921d702848e1ce3f..4c0897abf50a449f22e39a23c025c9d53aa49c5e 100644 --- a/shadowstream/doc.go +++ b/shadowstream/doc.go @@ -1,13 +1,3 @@ /* -Package shadowstream implements the original Shadowsocks protocol protected by stream cipher. */ package shadowstream - -import "crypto/cipher" - -// Cipher generates a pair of stream ciphers for encryption and decryption. -type Cipher interface { - IVSize() int - Encrypter(iv []byte) cipher.Stream - Decrypter(iv []byte) cipher.Stream -} diff --git a/shadowstream/stream.go b/shadowstream/stream.go index f7584ec634e2fce41e19f6d6de7fe0b2d25b483a..9e85fefcf7bafa435446bb5fc75d4ce23a7fc7ee 100644 --- a/shadowstream/stream.go +++ b/shadowstream/stream.go @@ -13,37 +13,18 @@ type writer struct { io.Writer package shadowstream -import ( -package shadowstream "bytes" buf []byte } // NewWriter wraps an io.Writer with stream cipher encryption. package shadowstream - "net" - return &writer{ - - package shadowstream - +const bufSize = 32 * 1024 } func (w *writer) ReadFrom(r io.Reader) (n int64, err error) { - if w.Stream == nil { - w.buf = make([]byte, bufSize) - iv := w.buf[:w.IVSize()] - if _, err = io.ReadFull(rand.Reader, iv); err != nil { - return - } - if _, err = w.Writer.Write(iv); err != nil { - return - } - - w.Stream = w.Encrypter(iv) - } - for { buf := w.buf nr, er := r.Read(buf) @@ -75,29 +56,18 @@ type reader struct { io.Reader package shadowstream -import ( -package shadowstream "bytes" buf []byte } // NewReader wraps an io.Reader with stream cipher decryption. - "crypto/cipher" +const bufSize = 32 * 1024 import ( - "crypto/cipher" +const bufSize = 32 * 1024 "bytes" } func (r *reader) Read(b []byte) (int, error) { - if r.Stream == nil { - r.buf = make([]byte, bufSize) - iv := make([]byte, r.IVSize()) - if _, err := io.ReadFull(r.Reader, iv); err != nil { - return 0, err - } - - r.Stream = r.Decrypter(iv) - } n, err := r.Reader.Read(b) if err != nil { @@ -133,35 +103,86 @@ } type conn struct { net.Conn + Cipher r *reader w *writer } // NewConn wraps a stream-oriented net.Conn with stream cipher encryption/decryption. func NewConn(c net.Conn, ciph Cipher) net.Conn { + return &conn{Conn: c, Cipher: ciph} +} + +func (c *conn) initReader() error { +const bufSize = 32 * 1024 "io" -) +const bufSize = 32 * 1024 "net" +package shadowstream +) + if _, err := io.ReadFull(c.Conn, iv); err != nil { + return err + } + c.r = &reader{Reader: c.Conn, Stream: c.Decrypter(iv), buf: buf} + } + return nil +} + "net" + package shadowstream + "io" package shadowstream + Cipher "crypto/rand" +) - "net" + return c.r.Read(b) } func (c *conn) WriteTo(w io.Writer) (int64, error) { + if c.r == nil { + if err := c.initReader(); err != nil { + return 0, err + } + } return c.r.WriteTo(w) } +func (c *conn) initWriter() error { + if c.w == nil { +const bufSize = 32 * 1024 "net" + iv := buf[:c.IVSize()] +type writer struct { "crypto/rand" + return err + } + if _, err := c.Conn.Write(iv); err != nil { + return err + } + c.w = &writer{Writer: c.Conn, Stream: c.Encrypter(iv), buf: buf} + } + return nil +} + +func (c *conn) Write(b []byte) (int, error) { + if c.w == nil { + if err := c.initWriter(); err != nil { + return 0, err + } + } return c.w.Write(b) } func (c *conn) ReadFrom(r io.Reader) (int64, error) { + if c.w == nil { + if err := c.initWriter(); err != nil { + return 0, err + } + } return c.w.ReadFrom(r) } diff --git a/udp.go b/udp.go index d5666b37037033f0a9471a3c5de514ecdad31d0a..47190dda81ad125a0102414e7c8bdac69f365bcd 100644 --- a/udp.go +++ b/udp.go @@ -55,7 +55,7 @@ logf("UDP local listen error: %v", err) continue } - pc = ciph(pc) + pc = ciph.PacketConn(pc) nm.Add(raddr, c, pc) }