Skip to content

Commit

Permalink
Reworked cipher handling
Browse files Browse the repository at this point in the history
  • Loading branch information
riobard committed Feb 5, 2017
1 parent a25fd3c commit 1535a57
Show file tree
Hide file tree
Showing 5 changed files with 53 additions and 145 deletions.
33 changes: 12 additions & 21 deletions cipher/aead.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,27 @@ package cipher
import (
"crypto/aes"
"crypto/cipher"
"net"

"github.com/riobard/go-shadowsocks2/core"
"github.com/riobard/go-shadowsocks2/shadowaead"
"golang.org/x/crypto/chacha20poly1305"
)

// AEAD ciphers

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

// AES-GCM with standard 12-byte nonce
func aesGCM(key []byte) (cipher.AEAD, error) {
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 16-byte nonce for better collision avoidance
func aesGCM16(key []byte) (cipher.AEAD, error) {
blk, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
return cipher.NewGCMWithNonceSize(blk, 16)
}
// 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) }
84 changes: 4 additions & 80 deletions cipher/doc.go
Original file line number Diff line number Diff line change
@@ -1,86 +1,10 @@
// Package cipher provides ciphers for Shadowsocks
package cipher

import (
"crypto/cipher"
"errors"
"sort"
"strings"
import "strconv"

"github.com/riobard/go-shadowsocks2/core"
"github.com/riobard/go-shadowsocks2/shadowstream"
"golang.org/x/crypto/chacha20poly1305"
)
type KeySizeError int

// ErrKeySize means the key size does not meet the requirement of cipher.
var ErrKeySize = errors.New("key size error")

// ErrCipherNotSupported means the cipher has not been implemented.
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(key []byte) (cipher.AEAD, error)
}{
"aes-128-gcm": {16, aesGCM},
"aes-192-gcm": {24, aesGCM},
"aes-256-gcm": {32, aesGCM},
"aes-128-gcm-16": {16, aesGCM16},
"aes-192-gcm-16": {24, aesGCM16},
"aes-256-gcm-16": {32, aesGCM16},
"chacha20-ietf-poly1305": {32, chacha20poly1305.New},
}

// 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, aesCTR},
"aes-192-ctr": {24, aesCTR},
"aes-256-ctr": {32, aesCTR},
"aes-128-cfb": {16, aesCFB},
"aes-192-cfb": {24, aesCFB},
"aes-256-cfb": {32, aesCFB},
"chacha20-ietf": {32, newChacha20ietf},
}

// List returns a list of available cipher names sorted alphabetically.
func List() []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
}

// New returns a pair of ciphers for the given key.
func New(name string, key []byte) (core.StreamConnCipher, core.PacketConnCipher, error) {
name = strings.ToLower(name)

if choice, ok := aeadList[name]; ok {
if len(key) != choice.KeySize {
return nil, nil, ErrKeySize
}
aead, err := choice.New(key)
return aeadStream(aead), aeadPacket(aead), err
}

if choice, ok := streamList[name]; ok {
if len(key) != choice.KeySize {
return nil, nil, ErrKeySize
}
ciph, err := choice.New(key)
return streamStream(ciph), streamPacket(ciph), err
}

if name == "dummy" {
return dummyStream(), dummyPacket(), nil
}
return nil, nil, ErrCipherNotSupported
func (e KeySizeError) Error() string {
return "key size error: need " + strconv.Itoa(int(e)) + " bytes"
}
16 changes: 0 additions & 16 deletions cipher/dummy.go

This file was deleted.

20 changes: 5 additions & 15 deletions cipher/stream.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,21 @@ package cipher
import (
"crypto/aes"
"crypto/cipher"
"net"

"github.com/Yawning/chacha20"
"github.com/riobard/go-shadowsocks2/core"
"github.com/riobard/go-shadowsocks2/shadowstream"
)

// Stream ciphers

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

// 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) {
func AESCTR(key []byte) (shadowstream.Cipher, error) {
blk, err := aes.NewCipher(key)
if err != nil {
return nil, err
Expand All @@ -42,12 +32,12 @@ 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) {
func AESCFB(key []byte) (shadowstream.Cipher, error) {
blk, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
return &ctrStream{blk}, nil
return &cfbStream{blk}, nil
}

// IETF-variant of chacha20
Expand All @@ -63,9 +53,9 @@ func (k chacha20ietfkey) Encrypter(iv []byte) cipher.Stream {
return ciph
}

func newChacha20ietf(key []byte) (shadowstream.Cipher, error) {
func Chacha20IETF(key []byte) (shadowstream.Cipher, error) {
if len(key) != chacha20.KeySize {
return nil, ErrKeySize
return nil, KeySizeError(chacha20.KeySize)
}
return chacha20ietfkey(key), nil
}
45 changes: 32 additions & 13 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
package main

import (
"encoding/hex"
"crypto/rand"
"encoding/base64"
"flag"
"fmt"
"io"
"log"
"os"
"os/signal"
"strings"
"syscall"
"time"

"github.com/riobard/go-shadowsocks2/cipher"
)

var config struct {
Expand All @@ -32,6 +32,8 @@ func main() {
Server string
Cipher string
Key string
Password string
Keygen int
Socks string
RedirTCP string
RedirTCP6 string
Expand All @@ -41,7 +43,9 @@ func main() {

flag.BoolVar(&config.Verbose, "verbose", false, "verbose mode")
flag.StringVar(&flags.Cipher, "cipher", "", "cipher")
flag.StringVar(&flags.Key, "key", "", "secret key in hexadecimal")
flag.StringVar(&flags.Key, "key", "", "base64url-encoded key")
flag.IntVar(&flags.Keygen, "keygen", 0, "generate a base64url-encoded random key of given length in byte")
flag.StringVar(&flags.Password, "password", "", "password")
flag.StringVar(&flags.Server, "s", "", "server listen address")
flag.StringVar(&flags.Client, "c", "", "client connect address")
flag.StringVar(&flags.Socks, "socks", ":1080", "(client-only) SOCKS listen address")
Expand All @@ -52,21 +56,37 @@ func main() {
flag.DurationVar(&config.UDPTimeout, "udptimeout", 5*time.Minute, "UDP tunnel timeout")
flag.Parse()

if flags.Keygen > 0 {
key := make([]byte, flags.Keygen)
io.ReadFull(rand.Reader, key)
fmt.Println(base64.URLEncoding.EncodeToString(key))
return
}

if flags.Client == "" && flags.Server == "" {
flag.Usage()
return
}

if flags.Cipher == "" {
ls := cipher.List()
ls := listCipher()
fmt.Fprintf(os.Stderr, "# available ciphers\n")
for _, each := range ls {
fmt.Fprintf(os.Stderr, "%s\n", each)
}
return
}

key, err := hex.DecodeString(flags.Key)
if err != nil {
log.Fatalf("key: %v", err)
var key []byte
if flags.Key != "" {
k, err := base64.URLEncoding.DecodeString(flags.Key)
if err != nil {
log.Fatalf("key: %v", err)
}
key = k
}

streamCipher, packetCipher, err := cipher.New(flags.Cipher, key)
streamCipher, packetCipher, err := pickCipher(flags.Cipher, key, flags.Password)
if err != nil {
log.Fatalf("cipher: %v", err)
}
Expand Down Expand Up @@ -97,12 +117,11 @@ func main() {
if flags.RedirTCP6 != "" {
go redir6Local(flags.RedirTCP6, flags.Client, streamCipher)
}
} else if flags.Server != "" { // server mode
}

if flags.Server != "" { // server mode
go udpRemote(flags.Server, packetCipher)
go tcpRemote(flags.Server, streamCipher)
} else {
flag.Usage()
return
}

sigCh := make(chan os.Signal, 1)
Expand Down

0 comments on commit 1535a57

Please sign in to comment.