Skip to content

Commit

Permalink
AEAD ciphers using subkey
Browse files Browse the repository at this point in the history
  • Loading branch information
riobard committed Feb 11, 2017
1 parent f52b52d commit 4cd2023
Show file tree
Hide file tree
Showing 14 changed files with 337 additions and 222 deletions.
70 changes: 34 additions & 36 deletions cipher.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
package main

import (
"crypto/cipher"
"crypto/md5"
"errors"
"net"
"sort"
"strings"

sscipher "github.com/riobard/go-shadowsocks2/cipher"
"github.com/riobard/go-shadowsocks2/core"
"github.com/riobard/go-shadowsocks2/shadowaead"
"github.com/riobard/go-shadowsocks2/shadowstream"
Expand All @@ -19,29 +17,26 @@ 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)
New func([]byte) (shadowaead.Cipher, 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},
"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, 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},
"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.
Expand All @@ -58,55 +53,58 @@ func listCipher() []string {
}

// derive key from password if given key is empty
func pickCipher(name string, key []byte, password string) (core.StreamConnCipher, core.PacketConnCipher, error) {
func pickCipher(name string, key []byte, password string) (core.Cipher, error) {
name = strings.ToLower(name)

if name == "dummy" {
return dummyStream(), dummyPacket(), nil
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, nil, sscipher.KeySizeError(choice.KeySize)
return nil, shadowaead.KeySizeError(choice.KeySize)
}
aead, err := choice.New(key)
return aeadStream(aead), aeadPacket(aead), err
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, nil, sscipher.KeySizeError(choice.KeySize)
return nil, shadowstream.KeySizeError(choice.KeySize)
}
ciph, err := choice.New(key)
return streamStream(ciph), streamPacket(ciph), err
return &streamCipher{ciph}, err
}

return nil, nil, errCipherNotSupported
return 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) }
}
type aeadCipher struct{ shadowaead.Cipher }

func streamStream(ciph shadowstream.Cipher) core.StreamConnCipher {
return func(c net.Conn) net.Conn { return shadowstream.NewConn(c, ciph) }
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)
}
func streamPacket(ciph shadowstream.Cipher) core.PacketConnCipher {
return func(c net.PacketConn) net.PacketConn { return shadowstream.NewPacketConn(c, ciph) }

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
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 } }

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 {
Expand Down
29 changes: 0 additions & 29 deletions cipher/aead.go

This file was deleted.

10 changes: 0 additions & 10 deletions cipher/doc.go

This file was deleted.

17 changes: 17 additions & 0 deletions core/doc.go
Original file line number Diff line number Diff line change
@@ -1,2 +1,19 @@
// Package core provides essential interfaces for Shadowsocks
package core

import (
"net"
)

type Cipher interface {
StreamConnCipher
PacketConnCipher
}

type StreamConnCipher interface {
StreamConn(net.Conn) net.Conn
}

type PacketConnCipher interface {
PacketConn(net.PacketConn) net.PacketConn
}
4 changes: 1 addition & 3 deletions core/packet.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
6 changes: 2 additions & 4 deletions core/stream.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ package core

import "net"

type StreamConnCipher func(net.Conn) net.Conn

type listener struct {
net.Listener
StreamConnCipher
Expand All @@ -16,10 +14,10 @@ func Listen(network, address string, ciph StreamConnCipher) (net.Listener, error

func (l *listener) Accept() (net.Conn, error) {
c, err := l.Listener.Accept()
return l.StreamConnCipher(c), err
return l.StreamConn(c), err
}

func Dial(network, address string, ciph StreamConnCipher) (net.Conn, error) {
c, err := net.Dial(network, address)
return ciph(c), err
return ciph.StreamConn(c), err
}
18 changes: 9 additions & 9 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func main() {
}

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(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")
Expand Down Expand Up @@ -77,7 +77,7 @@ func main() {
key = k
}

streamCipher, packetCipher, err := pickCipher(flags.Cipher, key, flags.Password)
ciph, err := pickCipher(flags.Cipher, key, flags.Password)
if err != nil {
log.Fatal(err)
}
Expand All @@ -86,33 +86,33 @@ func main() {
if flags.UDPTun != "" {
for _, tun := range strings.Split(flags.UDPTun, ",") {
p := strings.Split(tun, "=")
go udpLocal(p[0], flags.Client, p[1], packetCipher)
go udpLocal(p[0], flags.Client, p[1], ciph)
}
}

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 tcpRemote(flags.Server, streamCipher)
go udpRemote(flags.Server, ciph)
go tcpRemote(flags.Server, ciph)
}

sigCh := make(chan os.Signal, 1)
Expand Down
80 changes: 80 additions & 0 deletions shadowaead/cipher.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
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)
}

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
}

func Chacha20IETFPoly1305(psk []byte) (Cipher, error) {
if len(psk) != chacha20poly1305.KeySize {
return nil, KeySizeError(chacha20poly1305.KeySize)
}
return &metaCipher{psk: psk, makeAEAD: chacha20poly1305.New}, nil
}
Loading

0 comments on commit 4cd2023

Please sign in to comment.