Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
riobard committed Feb 4, 2017
0 parents commit 443dd01
Show file tree
Hide file tree
Showing 16 changed files with 1,535 additions and 0 deletions.
132 changes: 132 additions & 0 deletions cipher.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package main

import (
"crypto/aes"
"crypto/cipher"
"errors"
"fmt"
"net"
"strings"

"golang.org/x/crypto/chacha20poly1305"

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

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

func pickCipher(name string, key []byte) (core.StreamConnCipher, core.PacketConnCipher, error) {

switch strings.ToLower(name) {
case "aes-128-gcm", "aes-192-gcm", "aes-256-gcm":
aead, err := aesGCM(key, 0) // 0 for standard 12-byte nonce
return aeadStream(aead), aeadPacket(aead), err

case "aes-128-gcm-16", "aes-192-gcm-16", "aes-256-gcm-16":
aead, err := aesGCM(key, 16) // 16-byte nonce for better collision avoidance
return aeadStream(aead), aeadPacket(aead), err

case "chacha20-ietf-poly1305":
aead, err := chacha20poly1305.New(key)
return aeadStream(aead), aeadPacket(aead), err

case "aes-128-ctr", "aes-192-ctr", "aes-256-ctr":
ciph, err := aesCTR(key)
return streamStream(ciph), streamPacket(ciph), err

case "aes-128-cfb", "aes-192-cfb", "aes-256-cfb":
ciph, err := aesCFB(key)
return streamStream(ciph), streamPacket(ciph), err

case "chacha20-ietf":
if len(key) != chacha20.KeySize {
return nil, nil, ErrKeySize
}
k := chacha20ietfkey(key)
return streamStream(k), streamPacket(k), nil

case "dummy": // only for benchmarking and debugging
return dummyStream(), dummyPacket(), nil

default:
err := fmt.Errorf("cipher not supported: %s", name)
return nil, nil, err
}
}

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

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 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) // standard 12-byte nonce
}

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

type ctrStream struct{ cipher.Block }

func (b *ctrStream) IVSize() int { return b.BlockSize() }
func (b *ctrStream) Encrypter(iv []byte) cipher.Stream { return cipher.NewCTR(b, iv) }
func (b *ctrStream) Decrypter(iv []byte) cipher.Stream { return b.Encrypter(iv) }

func aesCTR(key []byte) (shadowstream.Cipher, error) {
blk, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
return &ctrStream{blk}, nil
}

type cfbStream struct{ cipher.Block }

func (b *cfbStream) IVSize() int { return b.BlockSize() }
func (b *cfbStream) Encrypter(iv []byte) cipher.Stream { return cipher.NewCFBEncrypter(b, iv) }
func (b *cfbStream) Decrypter(iv []byte) cipher.Stream { return cipher.NewCFBDecrypter(b, iv) }

func aesCFB(key []byte) (shadowstream.Cipher, error) {
blk, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
return &ctrStream{blk}, nil
}

type chacha20ietfkey []byte

func (k chacha20ietfkey) IVSize() int { return chacha20.INonceSize }
func (k chacha20ietfkey) Encrypter(iv []byte) cipher.Stream {
ciph, err := chacha20.NewCipher(k, iv)
if err != nil {
panic(err)
}
return ciph
}
func (k chacha20ietfkey) Decrypter(iv []byte) cipher.Stream { return k.Encrypter(iv) }
2 changes: 2 additions & 0 deletions core/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Package core provides essential interfaces for Shadowsocks
package core
10 changes: 10 additions & 0 deletions core/packet.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
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
}
25 changes: 25 additions & 0 deletions core/stream.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package core

import "net"

type StreamConnCipher func(net.Conn) net.Conn

type listener struct {
net.Listener
StreamConnCipher
}

func Listen(network, address string, ciph StreamConnCipher) (net.Listener, error) {
l, err := net.Listen(network, address)
return &listener{l, ciph}, err
}

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

func Dial(network, address string, ciph StreamConnCipher) (net.Conn, error) {
c, err := net.Dial(network, address)
return ciph(c), err
}
99 changes: 99 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package main

import (
"encoding/hex"
"flag"
"log"
"os"
"os/signal"
"strings"
"syscall"
"time"
)

var config struct {
Verbose bool
UDPTimeout time.Duration
}

func logf(f string, v ...interface{}) {
if config.Verbose {
log.Printf(f, v...)
}
}

func main() {

var flags struct {
Client string
Server string
Cipher string
Key string
Socks string
RedirTCP string
RedirTCP6 string
TCPTun string
UDPTun string
}

flag.BoolVar(&config.Verbose, "verbose", false, "verbose mode")
flag.StringVar(&flags.Cipher, "cipher", "aes-128-gcm", "cipher to encrypt/decrypt")
flag.StringVar(&flags.Key, "key", "", "secret key in hexadecimal")
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")
flag.StringVar(&flags.RedirTCP, "redir", "", "(client-only) redirect TCP from this address")
flag.StringVar(&flags.RedirTCP6, "redir6", "", "(client-only) redirect TCP IPv6 from this address")
flag.StringVar(&flags.TCPTun, "tcptun", "", "(client-only) TCP tunnel (laddr1=raddr1,laddr2=raddr2,...)")
flag.StringVar(&flags.UDPTun, "udptun", "", "(client-only) UDP tunnel (laddr1=raddr1,laddr2=raddr2,...)")
flag.DurationVar(&config.UDPTimeout, "udptimeout", 5*time.Minute, "UDP tunnel timeout")
flag.Parse()

key, err := hex.DecodeString(flags.Key)
if err != nil {
log.Fatalf("failed to parse key: %v", err)
}

streamCipher, packetCipher, err := pickCipher(flags.Cipher, key)
if err != nil {
log.Fatalf("failed to create cipher %s: %v", flags.Cipher, err)
}

if flags.Client != "" { // client mode
if flags.UDPTun != "" {
for _, tun := range strings.Split(flags.UDPTun, ",") {
p := strings.Split(tun, "=")
go udpLocal(p[0], flags.Client, p[1], packetCipher)
}
}

if flags.TCPTun != "" {
for _, tun := range strings.Split(flags.TCPTun, ",") {
p := strings.Split(tun, "=")
go tcpTun(p[0], flags.Client, p[1], streamCipher)
}
}

if flags.Socks != "" {
go socksLocal(flags.Socks, flags.Client, streamCipher)
}

if flags.RedirTCP != "" {
go redirLocal(flags.RedirTCP, flags.Client, streamCipher)
}

if flags.RedirTCP6 != "" {
go redir6Local(flags.RedirTCP6, flags.Client, streamCipher)
}
} else 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)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
<-sigCh
}
35 changes: 35 additions & 0 deletions shadowaead/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
Package shadowaead implements a simple AEAD-protected secure protocol.
In general, there are two types of connections: stream-oriented and packet-oriented.
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.
Each encrypted record has the following structure:
[encrypted payload length]
[payload length tag]
[encrypted payload]
[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,
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:
[nonce]
[encrypted payload]
[payload tag]
Packets are encrypted/decrypted independently.
In both stream-oriented and packet-oriented connections, length of nonce and tag varies
depending on which AEAD is used. Nonces are assumed to be randomly generated and
of sufficient length (at least 12 bytes).
*/
package shadowaead
79 changes: 79 additions & 0 deletions shadowaead/packet.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package shadowaead

import (
"crypto/cipher"
"crypto/rand"
"errors"
"io"
"net"
)

// ErrShortPacket means that the packet is too short for a valid encrypted packet.
var ErrShortPacket = errors.New("shadow: short packet")

// Pack encrypts plaintext using aead with a randomly generated nonce and
// returns a slice of dst containing the encrypted packet and any error occurred.
// Ensure len(dst) >= aead.NonceSize() + len(plaintext) + aead.Overhead().
func Pack(dst, plaintext []byte, aead cipher.AEAD) ([]byte, error) {
nsiz := aead.NonceSize()
if len(dst) < nsiz+len(plaintext)+aead.Overhead() {
return nil, io.ErrShortBuffer
}

nonce := dst[:nsiz]
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return nil, err
}

b := aead.Seal(dst[nsiz:nsiz], nonce, plaintext, nil)
return dst[:nsiz+len(b)], nil
}

// Unpack decrypts pkt using aead and returns a slice of dst containing the decrypted payload and any error occurred.
// Ensure len(dst) >= len(pkt) - aead.NonceSize() - aead.Overhead().
func Unpack(dst, pkt []byte, aead cipher.AEAD) ([]byte, error) {
nsiz := aead.NonceSize()

if len(pkt) < nsiz+aead.Overhead() {
return nil, ErrShortPacket
}

if len(dst) < len(pkt)-nsiz-aead.Overhead() {
return nil, io.ErrShortBuffer
}

b, err := aead.Open(dst[:0], pkt[:nsiz], pkt[nsiz:], nil)
return b, err
}

// packetConn encrypts net.packetConn with cipher.AEAD
type packetConn struct {
net.PacketConn
cipher.AEAD
}

// NewPacketConn wraps a net.PacketConn with AEAD protection.
func NewPacketConn(c net.PacketConn, aead cipher.AEAD) net.PacketConn {
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())
buf, err := Pack(buf, b, c.AEAD)
if err != nil {
return 0, err
}
_, err = c.PacketConn.WriteTo(buf, addr)
return len(b), err
}

// ReadFrom reads from the embedded PacketConn and decrypts into b.
func (c *packetConn) ReadFrom(b []byte) (int, net.Addr, error) {
n, addr, err := c.PacketConn.ReadFrom(b)
if err != nil {
return n, addr, err
}
b, err = Unpack(b, b[:n], c.AEAD)
return len(b), addr, err
}
Loading

0 comments on commit 443dd01

Please sign in to comment.