├── cmd ├── doc.go ├── schannel_keygen │ ├── README.md │ └── keygen.go └── schannel_nc │ ├── README.md │ └── nc.go ├── schannel ├── util.go ├── envelope_test.go ├── util_test.go ├── schannel_cover_test.go ├── doc.go ├── envelope_cover_test.go ├── envelope.go ├── schannel_test.go └── schannel.go ├── README.md └── LICENSE /cmd/doc.go: -------------------------------------------------------------------------------- 1 | // Package cmd contains Go implementations of the tools shipped with 2 | // libschannel. 3 | package cmd 4 | -------------------------------------------------------------------------------- /schannel/util.go: -------------------------------------------------------------------------------- 1 | package schannel 2 | 3 | func zero(in []byte, n int) { 4 | if in == nil { 5 | return 6 | } 7 | 8 | stop := n 9 | if stop > len(in) || stop == 0 { 10 | stop = len(in) 11 | } 12 | 13 | for i := 0; i < stop; i++ { 14 | in[i] ^= in[i] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /cmd/schannel_keygen/README.md: -------------------------------------------------------------------------------- 1 | # schannel_keygen 2 | ## generate schannel identity keypairs 3 | 4 | This is a Go implementation of the `schannel_keygen` program that ships with 5 | libschannel. It is designed to be a simple tool for producing keypairs that 6 | can be used with schannel. 7 | 8 | ## Usage 9 | 10 | ``` 11 | schannel_keygen basenames... 12 | ``` 13 | This program will output a pair of files for each basename: 14 | 15 | * basename.key: private signature (identity key) 16 | * basename.pub: public signature (identity key) 17 | 18 | These files will be in the binary form that can be directly loaded into 19 | the `schannel_dial` and `schannel_listen` functions. 20 | 21 | 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-schannel 2 | 3 | This is an interoperable Go implementation of 4 | [libschannel](https://github.com/kisom/libschannel). It is intended 5 | for small embedded systems (and their counterpart server programs) for 6 | which a full-fledged PKI is unnecessary; most people will want to use 7 | a mutally-authenticated TLS-secured TCP connection. 8 | 9 | GoDoc: [go-schannel](https://godoc.org/github.com/kisom/go-schannel/schannel) 10 | 11 | It provides bi-directional secure channels over an insecure communications 12 | channel (in this case, a Go `io.ReadWriter`). 13 | 14 | 15 | ## LICENSE 16 | 17 | This program is dual licensed. You may choose either the public domain 18 | license or the ISC license; the intent is to provide maximum freedom of 19 | use to the end user. 20 | -------------------------------------------------------------------------------- /schannel/envelope_test.go: -------------------------------------------------------------------------------- 1 | package schannel 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | ) 7 | 8 | func TestEnvelopeBasic(t *testing.T) { 9 | m := []byte("do not go gentle into that good night") 10 | out, ok := packMessage(1, NormalMessage, m) 11 | if !ok { 12 | t.Fatal("Failed to pack message.") 13 | } 14 | 15 | e, ok := unpackMessage(out) 16 | if !ok { 17 | t.Fatal("Failed to unpack message.") 18 | } 19 | 20 | if e.Type != NormalMessage { 21 | t.Fatalf("Invalid message type: expected %d, have %d.", 22 | NormalMessage, e.Type) 23 | } 24 | 25 | if e.Pad != 0 { 26 | t.Fatal("Invalid padding on message.") 27 | } 28 | 29 | if e.Sequence != 1 { 30 | t.Fatal("Invalid sequence number on message.") 31 | } 32 | 33 | if int(e.PayloadLength) != len(m) { 34 | t.Fatal("Invalid payload length.") 35 | } 36 | 37 | if !bytes.Equal(m, e.Payload[:e.PayloadLength]) { 38 | t.Fatal("Invalid payload.") 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /schannel/util_test.go: -------------------------------------------------------------------------------- 1 | package schannel 2 | 3 | import "testing" 4 | 5 | func verifyZeroised(in []byte, t *testing.T) { 6 | for i := 0; i < len(in); i++ { 7 | if in[i] != 0 { 8 | t.Fatal("buffer was not zeroised") 9 | } 10 | } 11 | } 12 | 13 | func verifyNotZeroised(in []byte, t *testing.T) { 14 | for i := 0; i < len(in); i++ { 15 | if in[i] == 0 { 16 | t.Fatal("buffer was zeroised but shouldn't be") 17 | } 18 | } 19 | } 20 | 21 | func newBuffer(n int) []byte { 22 | p := make([]byte, n) 23 | for i := 0; i < n; i++ { 24 | b := byte(n % 255) 25 | if b == 0 { 26 | b = 1 27 | } 28 | p[i] = b 29 | } 30 | 31 | return p 32 | } 33 | 34 | func TestZero(t *testing.T) { 35 | zero(nil, 32) 36 | 37 | p := newBuffer(32) 38 | zero(p, 0) 39 | verifyZeroised(p, t) 40 | 41 | p = newBuffer(64) 42 | zero(p, 0) 43 | verifyZeroised(p, t) 44 | 45 | p = newBuffer(32) 46 | zero(p, 10) 47 | verifyZeroised(p[:10], t) 48 | verifyNotZeroised(p[10:], t) 49 | } 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This program is dual licensed. You may choose either the public domain 2 | license or the ISC license; the intent is to provide maximum freedom of 3 | use to the end user. 4 | 5 | Copyright (c) 2015 Kyle Isom 6 | 7 | Permission to use, copy, modify, and distribute this software for any 8 | purpose with or without fee is hereby granted, provided that the above 9 | copyright notice and this permission notice appear in all copies. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 12 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 13 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 14 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 16 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 17 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 18 | 19 | This software is released into the public domain. 20 | 21 | -------------------------------------------------------------------------------- /cmd/schannel_nc/README.md: -------------------------------------------------------------------------------- 1 | # schannel_nc 2 | ## A netcat-like program that communicates over secure channels. 3 | 4 | This is a Go implementation of the `schannel_nc` program that ships with 5 | libschannel. It is designed to be a simple example of using the schannel 6 | package, and to facilitate some basic use tests of the code. 7 | 8 | ## Usage 9 | 10 | There are two way to use `schannel_nc`: as a client program that sends data 11 | to a server, or as a server program that receives data from a client. 12 | 13 | ### Client 14 | ``` 15 | schannel_nc [-hk] [-s signer] [-v verifier] host port 16 | ``` 17 | 18 | ### Server 19 | ``` 20 | schannel_nc [-hkl] [-s signer] [-v verifier] port 21 | ``` 22 | 23 | ### Flags 24 | The following flags are defined: 25 | * `-h`: print a short usage message and exit 26 | * `-k`: force the program to keep listening after the client 27 | disconnects. This must be used with -l. 28 | * `-l`: listen for an incoming connection 29 | * `-s signer`: specify the path to a signature key 30 | * `-v verifier`: specify the path to a verification key 31 | 32 | If a signature key is specified, it will be used to sign the key exchange. If a 33 | verification key is specified, it will be used to verify the signature on the 34 | key exchange. 35 | 36 | 37 | ## License 38 | 39 | This program is dual licensed. You may choose either the public domain 40 | license or the ISC license; the intent is to provide maximum freedom of 41 | use to the end user. 42 | 43 | -------------------------------------------------------------------------------- /cmd/schannel_keygen/keygen.go: -------------------------------------------------------------------------------- 1 | // schannel_keygen generates identity keypairs for use with schannel. 2 | package main 3 | 4 | import ( 5 | "crypto/rand" 6 | "flag" 7 | "fmt" 8 | "io/ioutil" 9 | "os" 10 | "path/filepath" 11 | 12 | "github.com/agl/ed25519" 13 | "github.com/kisom/die" 14 | ) 15 | 16 | func usage() { 17 | progName := filepath.Base(os.Args[0]) 18 | fmt.Fprintf(os.Stderr, `%s version 1.0 19 | Usage: 20 | %s basenames... 21 | This program will output a pair of files for each basename: 22 | - basename.key: private signature (identity key) 23 | - basename.pub: public signature (identity key) 24 | 25 | These files will be in the binary form that can be 26 | directly loaded into the schannel_dial and schannel_listen 27 | functions. 28 | 29 | `, progName, progName) 30 | } 31 | 32 | func main() { 33 | showHelp := flag.Bool("h", false, "display a short usage message and exit") 34 | flag.Parse() 35 | 36 | if *showHelp { 37 | usage() 38 | os.Exit(0) 39 | } 40 | 41 | if flag.NArg() == 0 { 42 | usage() 43 | os.Exit(1) 44 | } 45 | 46 | for _, baseName := range flag.Args() { 47 | pubFileName := fmt.Sprintf("%s.pub", baseName) 48 | privFileName := fmt.Sprintf("%s.key", baseName) 49 | 50 | pub, priv, err := ed25519.GenerateKey(rand.Reader) 51 | die.If(err) 52 | 53 | err = ioutil.WriteFile(pubFileName, pub[:], 0644) 54 | die.If(err) 55 | 56 | err = ioutil.WriteFile(privFileName, priv[:], 0600) 57 | die.If(err) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /schannel/schannel_cover_test.go: -------------------------------------------------------------------------------- 1 | package schannel 2 | 3 | import ( 4 | "bytes" 5 | "crypto/rand" 6 | "testing" 7 | ) 8 | 9 | // This package provides additional exercising of the code. 10 | 11 | func TestResetNil(t *testing.T) { 12 | var sch *SChannel 13 | sch.reset() 14 | sch.resetCounters() 15 | } 16 | 17 | func TestGenerateKeyPair(t *testing.T) { 18 | var sknil *[kexPrvSize]byte 19 | var pknil *[kexPubSize]byte 20 | if generateKeypair(sknil, pknil) { 21 | t.Fatal("generateKeypair should fail with nil keys") 22 | } 23 | 24 | var sk [kexPrvSize]byte 25 | var pk [kexPubSize]byte 26 | 27 | var buf = &bytes.Buffer{} 28 | prng = buf 29 | defer func() { 30 | prng = rand.Reader 31 | }() 32 | 33 | if generateKeypair(&sk, &pk) { 34 | t.Fatal("generateKeypair should fail bad PRNG") 35 | } 36 | 37 | var p = make([]byte, 48) 38 | buf.Write(p) 39 | if generateKeypair(&sk, &pk) { 40 | t.Fatal("generateKeypair should fail bad PRNG") 41 | } 42 | } 43 | 44 | func TestSigNil(t *testing.T) { 45 | var signer [IdentityPrivateSize]byte 46 | var peer [IdentityPublicSize]byte 47 | 48 | if signKEX(nil, &signer) { 49 | t.Fatal("signKEX should fail with nil kex") 50 | } 51 | 52 | if verifyKEX(nil, &peer) { 53 | t.Fatal("verifyKEX should fail with nil kex") 54 | } 55 | } 56 | 57 | func TestDoKEXFail(t *testing.T) { 58 | sch := &SChannel{} 59 | if sch.doKEX(nil, nil, false) { 60 | t.Fatal("doKEX should fail with nil keys") 61 | } 62 | 63 | var sk = make([]byte, kexPrvSize+1) 64 | var pk = make([]byte, kexPubSize+1) 65 | if sch.doKEX(sk[:kexPrvSize-1], pk[:kexPubSize], true) { 66 | t.Fatal("doKEX should fail with bad key size") 67 | } 68 | 69 | if sch.doKEX(sk, pk[:kexPubSize], true) { 70 | t.Fatal("doKEX should fail with bad key size") 71 | } 72 | 73 | if sch.doKEX(sk[:kexPrvSize], pk, true) { 74 | t.Fatal("doKEX should fail with bad key size") 75 | } 76 | 77 | if sch.doKEX(sk[:kexPrvSize], pk[:kexPubSize-1], true) { 78 | t.Fatal("doKEX should fail with bad key size") 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /schannel/doc.go: -------------------------------------------------------------------------------- 1 | // Package schannel establishes bidirectional secure channels over TCP/IP. 2 | // 3 | // This package is a port of libschannel (https://github.com/kisom/libschannel) 4 | // to Go. For details on the protocol and the properties of a secure channel, 5 | // the libschannel documentation should be consulted. Secure channels use 6 | // Curve25519 ECDH to exchange NaCl secretbox keys, and Ed25519 to sign key 7 | // exchanges. 8 | // 9 | // A secure channel is established with the Dial and Listen functions: one 10 | // side called Dial to set up a key exchange with the other side, and the 11 | // other side calls Listen to finalise the key exchange. For example, the 12 | // client might have the following: 13 | // 14 | // conn, err := net.Dial("tcp", host) 15 | // die.If(err) 16 | // defer conn.Close() 17 | // 18 | // sch, ok := schannel.Dial(conn, idPriv, idPeer) 19 | // if !ok { 20 | // die.With("failed to set up secure channel") 21 | // } 22 | // fmt.Println("secure channel established") 23 | // 24 | // On the server side, the code might look like this: 25 | // ln, err := net.Listen("tcp", ":"+port) 26 | // die.If(err) 27 | // 28 | // fmt.Println("Listening on", ":"+port) 29 | // for { 30 | // conn, err := ln.Accept() 31 | // if err != nil { 32 | // fmt.Printf("Connection error: %v\n", err) 33 | // continue 34 | // } 35 | // sch, ok := schannel.Listen(conn, idPriv, idPeer) 36 | // if !ok { 37 | // log.Printf("failed to establish secure channel") 38 | // } 39 | // log.Printf("secure channel established") 40 | // go run session(sch) 41 | // } 42 | // 43 | // Authentication is done using identity signature keys. These keys must 44 | // be known ahead of time, and key distribution is not a part of this 45 | // library. Each side chooses whether to sign and/or verify the signature 46 | // on the key exchange by providing an appropriate key or a nil key. 47 | // 48 | // The two pairs may send messages over the secure channel using the Send 49 | // function. These messages may be received with the Receive function, 50 | // which returns a *Message that pairs the message type with the message 51 | // contents. 52 | // 53 | // If the message is a NormalMessage, the contents will contain the original 54 | // message that was sent. If it is a KEXMessage or ShutdownMessage, the 55 | // contents will be empty. In the case of a KEXMessage, the receiver does not 56 | // need to do anything: it indicates that a key rotation took place, and is 57 | // provided for informational purposes. However, if the message is a 58 | // ShutdownMessage, the receiver should call the Zero method on the secure 59 | // channel. 60 | package schannel 61 | -------------------------------------------------------------------------------- /schannel/envelope_cover_test.go: -------------------------------------------------------------------------------- 1 | package schannel 2 | 3 | import ( 4 | "encoding/binary" 5 | "testing" 6 | 7 | "github.com/kisom/sbuf" 8 | ) 9 | 10 | // This package exercises the envelope code to 100%. 11 | 12 | var oversized = make([]byte, BufSize+1) 13 | 14 | // utility function to construct arbitrary packed messages 15 | func testPackMessage(length, sequence uint32, v uint8, pad uint16, mType MessageType, message []byte) []byte { 16 | buf := sbuf.NewBuffer(messageOverhead + len(message)) 17 | buf.WriteByte(v) 18 | buf.WriteByte(uint8(mType)) 19 | binary.Write(buf, binary.BigEndian, pad) 20 | 21 | // An sbuf won't fail to write unless it's out of memory, then this 22 | // whole house of cards is coming crashing down anyways. 23 | binary.Write(buf, binary.BigEndian, sequence) 24 | binary.Write(buf, binary.BigEndian, length) 25 | buf.Write(message) 26 | return buf.Bytes() 27 | } 28 | 29 | func TestInvalidPack(t *testing.T) { 30 | _, ok := packMessage(0, NormalMessage, []byte{1}) 31 | if ok { 32 | t.Fatal("expected packMessage to fail with invalid sequence number") 33 | } 34 | 35 | _, ok = packMessage(1, NormalMessage, nil) 36 | if ok { 37 | t.Fatal("expected packMessage to fail with nil message") 38 | } 39 | 40 | _, ok = packMessage(1, NormalMessage, []byte{}) 41 | if ok { 42 | t.Fatal("expected packMessage to fail with empty message") 43 | } 44 | 45 | var i MessageType 46 | for i = 0; i < 255; i++ { 47 | _, ok = packMessage(1, i, []byte{1}) 48 | switch i { 49 | case NormalMessage, KEXMessage, ShutdownMessage: 50 | if !ok { 51 | t.Fatal("expected packMessage to succeed with type ", i) 52 | } 53 | default: 54 | if ok { 55 | t.Fatal("expected packMessage to fail with an invalid message type") 56 | } 57 | } 58 | } 59 | 60 | if _, ok = packMessage(1, NormalMessage, oversized); ok { 61 | t.Fatal("expected packMessage to fail with oversized message") 62 | } 63 | } 64 | 65 | func TestInvalidUnpack(t *testing.T) { 66 | ensureFails := func(in []byte, m string) { 67 | if _, ok := unpackMessage(in); ok { 68 | t.Fatalf("expected unpackMessage to fail with %s", m) 69 | } 70 | } 71 | 72 | m := []byte{1} 73 | 74 | var in = make([]byte, messageOverhead-1) 75 | ensureFails(in, "short message") 76 | 77 | out, _ := packMessage(1, NormalMessage, m) 78 | out[0] = 0 79 | ensureFails(out, "short message") 80 | 81 | var i MessageType 82 | for i = 0; i < 255; i++ { 83 | out = testPackMessage(1, 1, currentVersion, 0, i, m) 84 | switch i { 85 | case NormalMessage, KEXMessage, ShutdownMessage: 86 | _, ok := unpackMessage(out) 87 | if !ok { 88 | t.Fatal("expected unpackMessage to succeed with type ", i) 89 | } 90 | default: 91 | ensureFails(out, "invalid message type") 92 | } 93 | } 94 | 95 | var p uint16 96 | for p = 1; p != 0; p++ { 97 | out = testPackMessage(1, 1, currentVersion, p, NormalMessage, m) 98 | ensureFails(out, "invalid padding") 99 | } 100 | 101 | out = testPackMessage(0, 1, currentVersion, 0, NormalMessage, m) 102 | ensureFails(out, "zero length") 103 | 104 | out = testPackMessage(BufSize+1, 1, currentVersion, 0, NormalMessage, m) 105 | ensureFails(out, "oversized message") 106 | 107 | out = testPackMessage(2, 1, currentVersion, 0, NormalMessage, m) 108 | ensureFails(out, "invalid length") 109 | } 110 | -------------------------------------------------------------------------------- /schannel/envelope.go: -------------------------------------------------------------------------------- 1 | package schannel 2 | 3 | import ( 4 | "encoding/binary" 5 | 6 | "github.com/kisom/sbuf" 7 | ) 8 | 9 | // A MessageType represents a type of message. 10 | type MessageType uint8 11 | 12 | const ( 13 | // InvalidMessage is any message that is invalid. 14 | InvalidMessage MessageType = iota 15 | 16 | // NormalMessage is a normal message. 17 | NormalMessage 18 | 19 | // KEXMessage is a key exchange message. 20 | KEXMessage 21 | 22 | // ShutdownMessage is an indication that the secure channel 23 | // should be shut down. 24 | ShutdownMessage 25 | ) 26 | 27 | // An envelope is used to wrap a message before encryption. 28 | type envelope struct { 29 | // Version stores the message format version. 30 | Version uint8 31 | 32 | // Type contains the message type. 33 | Type MessageType 34 | 35 | // Pad contains two bytes of padding. 36 | Pad uint16 37 | 38 | // Sequence contains the message sequence number. 39 | Sequence uint32 40 | 41 | // PayloadLength contains the length of the payload. 42 | PayloadLength uint32 43 | 44 | // Payload contains the message being sent. 45 | Payload [BufSize]byte 46 | } 47 | 48 | const ( 49 | currentVersion = 1 50 | messageOverhead = 12 // 2 * uint32 + 2 * uint8 + uint16 51 | ) 52 | 53 | // packMessage serialises the message into a byte slice. 54 | func packMessage(sequence uint32, mType MessageType, message []byte) ([]byte, bool) { 55 | if sequence == 0 { 56 | return nil, false 57 | } 58 | 59 | if len(message) == 0 || len(message) > BufSize { 60 | return nil, false 61 | } 62 | 63 | switch mType { 64 | case NormalMessage: 65 | case KEXMessage: 66 | case ShutdownMessage: 67 | default: 68 | return nil, false 69 | } 70 | 71 | buf := sbuf.NewBuffer(messageOverhead + len(message)) 72 | buf.WriteByte(currentVersion) 73 | buf.WriteByte(uint8(mType)) 74 | buf.WriteByte(0) // padding 75 | buf.WriteByte(0) // padding 76 | 77 | // An sbuf won't fail to write unless it's out of memory, then this 78 | // whole house of cards is coming crashing down anyways. 79 | binary.Write(buf, binary.BigEndian, sequence) 80 | binary.Write(buf, binary.BigEndian, uint32(len(message))) 81 | buf.Write(message) 82 | return buf.Bytes(), true 83 | } 84 | 85 | // unpackMessage unpacks a byte slice into a message. 86 | func unpackMessage(in []byte) (*envelope, bool) { 87 | var e envelope 88 | 89 | if len(in) <= messageOverhead { 90 | return nil, false 91 | } 92 | 93 | buf := sbuf.NewBufferFrom(in) 94 | defer buf.Close() 95 | 96 | // ReadByte won't fail given our length check at the beginning. 97 | e.Version, _ = buf.ReadByte() 98 | if e.Version != currentVersion { 99 | return nil, false 100 | } 101 | 102 | c, _ := buf.ReadByte() 103 | e.Type = MessageType(c) 104 | switch e.Type { 105 | case NormalMessage: 106 | case KEXMessage: 107 | case ShutdownMessage: 108 | default: 109 | return nil, false 110 | } 111 | 112 | // Read won't fail here with an sbuf given our length check. 113 | binary.Read(buf, binary.BigEndian, &e.Pad) 114 | if e.Pad != 0 { 115 | return nil, false 116 | } 117 | 118 | // Read won't fail here with an sbuf given our length check. 119 | binary.Read(buf, binary.BigEndian, &e.Sequence) 120 | binary.Read(buf, binary.BigEndian, &e.PayloadLength) 121 | if e.PayloadLength == 0 { 122 | return nil, false 123 | } else if e.PayloadLength > BufSize { 124 | return nil, false 125 | } else if buf.Len() != int(e.PayloadLength) { 126 | return nil, false 127 | } 128 | 129 | // Read won't fail here given the previous length checks. 130 | buf.Read(e.Payload[:int(e.PayloadLength)]) 131 | return &e, true 132 | } 133 | -------------------------------------------------------------------------------- /cmd/schannel_nc/nc.go: -------------------------------------------------------------------------------- 1 | // schannel_nc is a netcat-like program that communicates with secure channels. 2 | package main 3 | 4 | import ( 5 | "flag" 6 | "fmt" 7 | "io" 8 | "log" 9 | "net" 10 | "os" 11 | "path/filepath" 12 | 13 | "github.com/kisom/die" 14 | "github.com/kisom/go-schannel/schannel" 15 | ) 16 | 17 | var ( 18 | idPriv *[64]byte 19 | idPub *[32]byte 20 | ) 21 | 22 | func usage() { 23 | progName := filepath.Base(os.Args[0]) 24 | fmt.Fprintf(os.Stderr, `%s version 1.0 25 | Usage: 26 | 27 | %s [-hk] [-s signer] [-v verifier] host port 28 | %s [-hkl] [-s signer] [-v verifier] port 29 | -h print this usage message and exit 30 | -k force the program to keep listening after the client 31 | disconnects. This must be used with -l. 32 | -l listen for an incoming connection 33 | -s signer specify the path to a signature key 34 | -v verifier specify the path to a verification key 35 | 36 | If a signature key is specified, it will be used to sign the key exchange. If a 37 | verification key is specified, it will be used to verify the signature on the 38 | key exchange. 39 | `, progName, progName, progName) 40 | } 41 | 42 | func zero(in []byte, n int) { 43 | if in == nil { 44 | return 45 | } 46 | 47 | stop := n 48 | if stop > len(in) || stop == 0 { 49 | stop = len(in) 50 | } 51 | 52 | for i := 0; i < stop; i++ { 53 | in[i] ^= in[i] 54 | } 55 | } 56 | 57 | func loadID(privName, pubName string) { 58 | if pubName != "" { 59 | pubFile, err := os.Open(pubName) 60 | die.If(err) 61 | defer pubFile.Close() 62 | 63 | idPub = new([32]byte) 64 | _, err = io.ReadFull(pubFile, idPub[:]) 65 | die.If(err) 66 | } 67 | 68 | if privName != "" { 69 | privFile, err := os.Open(privName) 70 | die.If(err) 71 | defer privFile.Close() 72 | 73 | idPriv = new([64]byte) 74 | _, err = io.ReadFull(privFile, idPriv[:]) 75 | die.If(err) 76 | } 77 | } 78 | 79 | func listener(stayOpen bool, port string) { 80 | ln, err := net.Listen("tcp", ":"+port) 81 | die.If(err) 82 | 83 | fmt.Println("Listening on", ":"+port) 84 | for { 85 | conn, err := ln.Accept() 86 | if err != nil { 87 | fmt.Printf("Connection error: %v\n", err) 88 | continue 89 | } 90 | 91 | newChannel(conn) 92 | if !stayOpen { 93 | break 94 | } 95 | } 96 | } 97 | 98 | func newChannel(conn net.Conn) { 99 | defer conn.Close() 100 | sch, ok := schannel.Listen(conn, idPriv, idPub) 101 | if !ok { 102 | log.Printf("failed to establish secure channel") 103 | return 104 | } 105 | 106 | var stop bool 107 | log.Printf("secure channel established") 108 | for { 109 | m, ok := sch.Receive() 110 | if !ok { 111 | log.Printf("receive failed") 112 | break 113 | } 114 | 115 | switch m.Type { 116 | case schannel.ShutdownMessage: 117 | log.Printf("peer is shutting down") 118 | stop = true 119 | break 120 | case schannel.KEXMessage: 121 | log.Printf("keys rotated") 122 | case schannel.NormalMessage: 123 | os.Stdout.Write(m.Contents) 124 | default: 125 | log.Printf("unknown message type received: %d", m.Type) 126 | } 127 | 128 | if stop { 129 | break 130 | } 131 | } 132 | 133 | rcvd := sch.RCtr() 134 | if stop { 135 | rcvd-- 136 | } 137 | log.Printf("received %d messages with %d bytes", rcvd, sch.RData) 138 | log.Print("zeroising secure channel") 139 | sch.Zero() 140 | log.Print("secure channel shutdown") 141 | } 142 | 143 | func sender(host string) { 144 | conn, err := net.Dial("tcp", host) 145 | die.If(err) 146 | defer conn.Close() 147 | 148 | sch, ok := schannel.Dial(conn, idPriv, idPub) 149 | if !ok { 150 | die.With("failed to set up secure channel") 151 | } 152 | fmt.Println("secure channel established") 153 | 154 | if !sch.Rekey() { 155 | die.With("rekey failed") 156 | } 157 | 158 | for { 159 | var p = make([]byte, 8192) 160 | n, err := os.Stdin.Read(p) 161 | if err == io.EOF { 162 | break 163 | } 164 | die.If(err) 165 | 166 | if !sch.Send(p[:n]) { 167 | die.With("failed to send message (sdata=%d, sctr=%d)", 168 | sch.SData, sch.SCtr) 169 | } 170 | } 171 | 172 | sctr := sch.SCtr() 173 | sdata := sch.SData 174 | if !sch.Close() { 175 | die.With("failed to shutdown channel properly") 176 | } 177 | fmt.Println("Secure channel tore down") 178 | fmt.Printf("\t%d messages totalling %d bytes sent\n", 179 | sctr, sdata) 180 | 181 | return 182 | } 183 | 184 | func main() { 185 | var pubFile, privFile string 186 | var help, listen, stayOpen bool 187 | flag.BoolVar(&help, "h", false, "display a short usage message") 188 | flag.BoolVar(&stayOpen, "k", false, "keep listening after client disconnects") 189 | flag.BoolVar(&listen, "l", false, "listen for incoming connections") 190 | flag.StringVar(&privFile, "s", "", "path to signature key") 191 | flag.StringVar(&pubFile, "v", "", "path to verification key") 192 | flag.Parse() 193 | 194 | if help { 195 | usage() 196 | os.Exit(1) 197 | } 198 | 199 | loadID(privFile, pubFile) 200 | defer func() { 201 | if idPriv != nil { 202 | zero(idPriv[:], 0) 203 | } 204 | }() 205 | 206 | if listen { 207 | if flag.NArg() != 1 { 208 | fmt.Println("A port is required (and should be the only argument) when listening.") 209 | } 210 | listener(stayOpen, flag.Arg(0)) 211 | return 212 | } 213 | 214 | if flag.NArg() != 2 { 215 | fmt.Println("An address and port are required (and should be the only arguments).") 216 | } 217 | sender(flag.Arg(0) + ":" + flag.Arg(1)) 218 | } 219 | -------------------------------------------------------------------------------- /schannel/schannel_test.go: -------------------------------------------------------------------------------- 1 | package schannel 2 | 3 | import ( 4 | "bytes" 5 | "crypto/rand" 6 | "fmt" 7 | "testing" 8 | 9 | "github.com/agl/ed25519" 10 | "github.com/kisom/testio" 11 | ) 12 | 13 | var ( 14 | clientSK *[IdentityPrivateSize]byte 15 | clientPK *[IdentityPublicSize]byte 16 | serverSK *[IdentityPrivateSize]byte 17 | serverPK *[IdentityPublicSize]byte 18 | ) 19 | 20 | func TestGenerateIDKeys(t *testing.T) { 21 | var err error 22 | clientPK, clientSK, err = ed25519.GenerateKey(rand.Reader) 23 | if err != nil { 24 | t.Fatalf("%v", err) 25 | } 26 | 27 | serverPK, serverSK, err = ed25519.GenerateKey(rand.Reader) 28 | if err != nil { 29 | t.Fatalf("%v", err) 30 | } 31 | } 32 | 33 | var message = []byte(`do not go gentle into that good night 34 | old age should rave and burn at close of day 35 | rage, rage against the dying of the light 36 | 37 | though wise men at their end know dark is right 38 | because their words had forked no lightning, they 39 | do not go gentle into that good night 40 | 41 | good men, the last wave by, crying how bright 42 | their frail deeds might have danced in a green bay 43 | rage, rage against the dying of the light 44 | 45 | wild men, who caught, and sang, the sun in flight 46 | and learned, too late, they grieved it on its way 47 | do not go gentle into that good night 48 | 49 | grave men, near death, who see with blinding sight 50 | blind eyes could blaze like meteors and be gay 51 | rage, rage against the dying of the light 52 | 53 | and you, my father, there on that sad height 54 | curse, bless, me now, with your fierce tears, i pray 55 | do not go gentle into that good night 56 | rage, rage against the dying of the light 57 | `) 58 | 59 | func TestDialNoAuth(t *testing.T) { 60 | testDial(t, nil, nil, nil, nil, true) 61 | } 62 | 63 | func TestDialServerAuth(t *testing.T) { 64 | testDial(t, nil, nil, serverSK, serverPK, true) 65 | } 66 | 67 | func TestDialServerAuthFail(t *testing.T) { 68 | testDial(t, nil, nil, serverSK, clientPK, false) 69 | } 70 | 71 | func TestDialClientAuth(t *testing.T) { 72 | testDial(t, clientSK, clientPK, nil, nil, true) 73 | } 74 | 75 | func TestDialMutualAuth(t *testing.T) { 76 | testDial(t, clientSK, clientPK, serverSK, serverPK, true) 77 | } 78 | 79 | func testDial(t *testing.T, csk *[IdentityPrivateSize]byte, cpk *[IdentityPublicSize]byte, ssk *[IdentityPrivateSize]byte, spk *[IdentityPublicSize]byte, vok bool) { 80 | ch := testio.NewBufferConn() 81 | var pub [kexPubSize]byte 82 | var priv [kexPrvSize]byte 83 | 84 | if !generateKeypair(&priv, &pub) { 85 | t.Fatal("failed to generate keypair") 86 | } 87 | 88 | var kex [kexPubSize + SignatureSize]byte 89 | copy(kex[:], pub[:]) 90 | if !signKEX(&kex, ssk) { 91 | t.Fatal("failed to sign key exchange") 92 | } 93 | ch.WritePeer(kex[:]) 94 | 95 | alice, ok := Dial(ch, csk, spk) 96 | if !ok && vok { 97 | t.Fatal("failed to set up secure session") 98 | } else if ok && !vok { 99 | t.Fatal("secure session shouldn't have been set up") 100 | } 101 | 102 | if !vok { 103 | return 104 | } 105 | 106 | if !alice.Ready() { 107 | t.Fatal("alice is not ready") 108 | } 109 | 110 | if alice.RCtr() != 0 || alice.SCtr() != 0 { 111 | t.Fatal("alice's counters were not reset") 112 | } 113 | 114 | var peer [kexPubSize + SignatureSize]byte 115 | _, err := ch.ReadClient(peer[:]) 116 | if err != nil { 117 | t.Fatalf("%v", err) 118 | } 119 | 120 | ok = verifyKEX(&peer, cpk) 121 | if !ok && vok { 122 | t.Fatal("failed to verify key exchange") 123 | } else if ok && !vok { 124 | t.Fatal("key exchange verification should fail") 125 | } 126 | 127 | bob := &SChannel{} 128 | bob.reset() 129 | if !bob.doKEX(priv[:], peer[:kexPubSize], false) { 130 | t.Fatal("doKEX failed") 131 | } 132 | bob.ready = true 133 | buf := &bytes.Buffer{} 134 | 135 | if !bytes.Equal(alice.skey[:], bob.rkey[:]) { 136 | fmt.Printf("alice send key: %x\n", alice.skey) 137 | fmt.Printf(" bob recv key: %x\n", bob.rkey) 138 | t.Fatal("alice and bob have mismatched keys") 139 | } 140 | 141 | if !bytes.Equal(alice.rkey[:], bob.skey[:]) { 142 | fmt.Printf("alice recv key: %x\n", alice.rkey) 143 | fmt.Printf(" bob send key: %x\n", bob.skey) 144 | t.Fatal("alice and bob have mismatched keys") 145 | } 146 | 147 | bob.Channel = buf 148 | alice.Channel = buf 149 | 150 | if !alice.Send(message) { 151 | t.Fatal("alice failed to send a message") 152 | } 153 | 154 | // The dread TLA has captured our heroes' secure message! 155 | tlaCapture := buf.Bytes() 156 | 157 | m, ok := bob.Receive() 158 | if !ok { 159 | t.Fatal("bob couldn't receive the message") 160 | } else if m.Type != NormalMessage { 161 | t.Fatal("bob got an invalid message") 162 | } else if !bytes.Equal(m.Contents, message) { 163 | t.Fatal("bob didn't get the message alice sent") 164 | } 165 | 166 | for i := 0; i < 32; i++ { 167 | if !alice.Send(message) { 168 | t.Fatal("alice failed to send a message") 169 | } 170 | 171 | m, ok := bob.Receive() 172 | if !ok { 173 | t.Fatal("bob couldn't receive the message") 174 | } else if m.Type != NormalMessage { 175 | t.Fatal("bob got an invalid message") 176 | } else if !bytes.Equal(m.Contents, message) { 177 | t.Fatal("bob didn't get the message alice sent") 178 | } 179 | } 180 | 181 | buf.Write(tlaCapture) 182 | _, ok = bob.Receive() 183 | if ok { 184 | t.Fatal("the TLA won!") 185 | } 186 | // \o/ 187 | 188 | if !alice.Close() { 189 | t.Fatal("alice couldn't shutdown the channel") 190 | } 191 | 192 | if m, ok := bob.Receive(); !ok { 193 | t.Fatal("bob couldn't receive the message") 194 | } else if m.Type != ShutdownMessage { 195 | t.Fatal("bob expected a shutdown message") 196 | } 197 | bob.Zero() 198 | } 199 | 200 | func TestListenNoAuth(t *testing.T) { 201 | testListen(t, nil, nil, nil, nil, true) 202 | } 203 | 204 | func TestListenServerAuth(t *testing.T) { 205 | testListen(t, nil, nil, serverSK, serverPK, true) 206 | } 207 | 208 | func TestListenServerAuthFail(t *testing.T) { 209 | testListen(t, serverSK, clientPK, nil, nil, false) 210 | } 211 | 212 | func TestListenClientAuth(t *testing.T) { 213 | testListen(t, clientSK, clientPK, nil, nil, true) 214 | } 215 | 216 | func TestListenClientAuthFail(t *testing.T) { 217 | testListen(t, clientSK, serverPK, nil, nil, false) 218 | } 219 | 220 | func TestListenMutualAuth(t *testing.T) { 221 | testListen(t, clientSK, clientPK, serverSK, serverPK, true) 222 | } 223 | 224 | func testListen(t *testing.T, csk *[IdentityPrivateSize]byte, cpk *[IdentityPublicSize]byte, ssk *[IdentityPrivateSize]byte, spk *[IdentityPublicSize]byte, vok bool) { 225 | ch := testio.NewBufferConn() 226 | var pub [kexPubSize]byte 227 | var priv [kexPrvSize]byte 228 | 229 | if !generateKeypair(&priv, &pub) { 230 | t.Fatal("failed to generate keypair") 231 | } 232 | 233 | var kex [kexPubSize + SignatureSize]byte 234 | copy(kex[:], pub[:]) 235 | if !signKEX(&kex, csk) { 236 | t.Fatal("signKEX failed") 237 | } 238 | ch.WritePeer(kex[:]) 239 | 240 | alice, ok := Listen(ch, ssk, cpk) 241 | if !ok && vok { 242 | t.Fatal("failed to set up secure session") 243 | } else if ok && !vok { 244 | t.Fatal("secure session shouldn't have been set up") 245 | } 246 | 247 | if !vok { 248 | return 249 | } 250 | 251 | if !alice.Ready() { 252 | t.Fatal("alice is not ready") 253 | } 254 | 255 | if alice.RCtr() != 0 || alice.SCtr() != 0 { 256 | t.Fatal("alice's counters were not reset") 257 | } 258 | 259 | var peer [kexPubSize + SignatureSize]byte 260 | _, err := ch.ReadClient(peer[:]) 261 | if err != nil { 262 | t.Fatalf("%v", err) 263 | } 264 | 265 | ok = verifyKEX(&peer, spk) 266 | if !ok && vok { 267 | t.Fatal("verifyKEX failed") 268 | } else if ok && !vok { 269 | t.Fatal("verifyKEX should have failed") 270 | } 271 | 272 | bob := &SChannel{} 273 | bob.reset() 274 | if !bob.doKEX(priv[:], peer[:kexPubSize], true) { 275 | t.Fatal("doKEX failed") 276 | } 277 | bob.ready = true 278 | buf := &bytes.Buffer{} 279 | 280 | if !bytes.Equal(alice.skey[:], bob.rkey[:]) { 281 | fmt.Printf("alice send key: %x\n", alice.skey) 282 | fmt.Printf(" bob recv key: %x\n", bob.rkey) 283 | t.Fatal("alice and bob have mismatched keys") 284 | } 285 | 286 | if !bytes.Equal(alice.rkey[:], bob.skey[:]) { 287 | fmt.Printf("alice recv key: %x\n", alice.rkey) 288 | fmt.Printf(" bob send key: %x\n", bob.skey) 289 | t.Fatal("alice and bob have mismatched keys") 290 | } 291 | 292 | bob.Channel = buf 293 | alice.Channel = buf 294 | 295 | if !alice.Send(message) { 296 | t.Fatal("alice failed to send a message") 297 | } 298 | 299 | // The dread TLA has captured our heroes' secure message! 300 | tlaCapture := buf.Bytes() 301 | 302 | m, ok := bob.Receive() 303 | if !ok { 304 | t.Fatal("bob couldn't receive the message") 305 | } else if m.Type != NormalMessage { 306 | t.Fatal("bob got an invalid message") 307 | } else if !bytes.Equal(m.Contents, message) { 308 | t.Fatal("bob didn't get the message alice sent") 309 | } 310 | 311 | for i := 0; i < 32; i++ { 312 | if !alice.Send(message) { 313 | t.Fatal("alice failed to send a message") 314 | } 315 | 316 | m, ok := bob.Receive() 317 | if !ok { 318 | t.Fatal("bob couldn't receive the message") 319 | } else if m.Type != NormalMessage { 320 | t.Fatal("bob got an invalid message") 321 | } else if !bytes.Equal(m.Contents, message) { 322 | t.Fatal("bob didn't get the message alice sent") 323 | } 324 | } 325 | 326 | buf.Write(tlaCapture) 327 | _, ok = bob.Receive() 328 | if ok { 329 | t.Fatal("the TLA won!") 330 | } 331 | // \o/ 332 | 333 | if !alice.Close() { 334 | t.Fatal("alice couldn't shutdown the channel") 335 | } 336 | 337 | if m, ok := bob.Receive(); !ok { 338 | t.Fatal("bob couldn't receive the message") 339 | } else if m.Type != ShutdownMessage { 340 | t.Fatal("bob expected a shutdown message") 341 | } 342 | bob.Zero() 343 | } 344 | -------------------------------------------------------------------------------- /schannel/schannel.go: -------------------------------------------------------------------------------- 1 | package schannel 2 | 3 | import ( 4 | "crypto/rand" 5 | "encoding/binary" 6 | "io" 7 | 8 | "github.com/agl/ed25519" 9 | "golang.org/x/crypto/nacl/box" 10 | "golang.org/x/crypto/nacl/secretbox" 11 | ) 12 | 13 | const ( 14 | // nonceSize is the size of a NaCl nonce. 15 | nonceSize = 24 16 | kexPubSize = 64 17 | kexPrvSize = 64 18 | ) 19 | 20 | var prng = rand.Reader 21 | 22 | const ( 23 | // BufSize is the maximum size of an encrypted message. 24 | BufSize = 2097152 // 2MiB: 2 * 1024 * 1024B 25 | 26 | // IdentityPrivateSize is the size of an identity private key. 27 | IdentityPrivateSize = ed25519.PrivateKeySize 28 | 29 | // IdentityPublicSize is the size of an identity public key. 30 | IdentityPublicSize = ed25519.PublicKeySize 31 | 32 | // KeySize is the size of a shared encryption key. 33 | KeySize = 32 34 | 35 | // Overhead is the amount of overhead added to a message 36 | // when it is encrypted. This is the size of a nonce, MAC, 37 | // and message envelope. 38 | Overhead = 106 39 | 40 | // SignatureSize is the length of an identity signature. 41 | SignatureSize = ed25519.SignatureSize 42 | ) 43 | 44 | // A Channel is an insecure channel; a secure channel is overlaid on top 45 | // of this channel. 46 | type Channel io.ReadWriter 47 | 48 | // An SChannel is a secure channel. It contains separate encryption keys 49 | // for receiving and sending messages, and tracks message numbers to 50 | // prevent forgeries. 51 | type SChannel struct { 52 | // RData and SData store the amount of data decrypted (received) 53 | // and encrypted (sent), respectively. 54 | RData uint64 55 | SData uint64 56 | 57 | // rctr and sctr store message sequence numbers. rctr stores the 58 | // last received message number, and sctr stores the last sent 59 | // message number. 60 | rctr uint32 61 | sctr uint32 62 | 63 | // rkey and skey contain the shared receive and send encryption 64 | // keys, computed via an ECDH exchange. 65 | rkey [KeySize]byte 66 | skey [KeySize]byte 67 | 68 | // buf stores the internal buffer used for incoming messages. 69 | buf [BufSize + Overhead]byte 70 | 71 | // Channel is the insecure channel the SChannel is built on. 72 | Channel Channel 73 | 74 | // ready is set to true when the SChannel is established and 75 | // fully set up. 76 | ready bool 77 | 78 | // kexip is used to track when a key exchange is in progress. 79 | kexip bool 80 | } 81 | 82 | // RCtr returns the last received message counter. 83 | func (sch *SChannel) RCtr() uint32 { 84 | return sch.rctr 85 | } 86 | 87 | // SCtr returns the last sent message counter. 88 | func (sch *SChannel) SCtr() uint32 { 89 | return sch.sctr 90 | } 91 | 92 | // Ready returns true if the secure channel is ready to send or receive 93 | // messages. If it returns false, the secure channel should be zeroised 94 | // and discarded. 95 | func (sch *SChannel) Ready() bool { 96 | return sch.ready 97 | } 98 | 99 | func (sch *SChannel) resetCounters() { 100 | if sch == nil { 101 | return 102 | } 103 | 104 | sch.RData = 0 105 | sch.SData = 0 106 | sch.rctr = 0 107 | sch.sctr = 0 108 | } 109 | 110 | func (sch *SChannel) reset() { 111 | if sch == nil { 112 | return 113 | } 114 | 115 | sch.resetCounters() 116 | sch.ready = false 117 | sch.kexip = false 118 | sch.Channel = nil 119 | zero(sch.buf[:], 0) 120 | zero(sch.rkey[:], 0) 121 | zero(sch.skey[:], 0) 122 | } 123 | 124 | func generateKeypair(sk *[kexPrvSize]byte, pk *[kexPubSize]byte) bool { 125 | if sk == nil || pk == nil { 126 | return false 127 | } 128 | 129 | pub, priv, err := box.GenerateKey(prng) 130 | if err != nil { 131 | return false 132 | } 133 | 134 | copy(sk[:], priv[:]) 135 | zero(priv[:], 0) 136 | copy(pk[:], pub[:]) 137 | 138 | pub, priv, err = box.GenerateKey(prng) 139 | if err != nil { 140 | zero(sk[:], 0) 141 | return false 142 | } 143 | 144 | copy(sk[32:], priv[:]) 145 | zero(priv[:], 0) 146 | copy(pk[32:], pub[:]) 147 | return true 148 | } 149 | 150 | func signKEX(kex *[kexPubSize + SignatureSize]byte, signer *[IdentityPrivateSize]byte) bool { 151 | if kex == nil { 152 | return false 153 | } 154 | 155 | if signer == nil { 156 | return true 157 | } 158 | 159 | sig := ed25519.Sign(signer, kex[:kexPubSize]) 160 | copy(kex[kexPubSize:], sig[:]) 161 | return true 162 | } 163 | 164 | func verifyKEX(kex *[kexPubSize + SignatureSize]byte, peer *[IdentityPublicSize]byte) bool { 165 | if kex == nil { 166 | return false 167 | } 168 | 169 | if peer == nil { 170 | return true 171 | } 172 | 173 | var sig = new([SignatureSize]byte) 174 | copy(sig[:], kex[kexPubSize:]) 175 | return ed25519.Verify(peer, kex[:kexPubSize], sig) 176 | } 177 | 178 | // keyExchange is a convenience function that takes keys as byte slices, 179 | // copying them into the appropriate arrays. 180 | func keyExchange(shared *[32]byte, priv, pub []byte) { 181 | // Copy the private key and wipe it, as it will no longer be needed. 182 | var kexPriv [32]byte 183 | copy(kexPriv[:], priv) 184 | zero(priv, 0) 185 | 186 | var kexPub [32]byte 187 | copy(kexPub[:], pub) 188 | box.Precompute(shared, &kexPub, &kexPriv) 189 | 190 | zero(kexPriv[:], 0) 191 | } 192 | 193 | func (sch *SChannel) doKEX(sk []byte, pk []byte, dialer bool) bool { 194 | if sk == nil || pk == nil { 195 | return false 196 | } else if len(sk) != kexPrvSize || len(pk) != kexPubSize { 197 | return false 198 | } 199 | 200 | // This function denotes the dialer, who initiates the session, 201 | // as A. The listener is denoted as B. A is started using Dial, 202 | // and B is started using Listen. 203 | if dialer { 204 | // The first 32 bytes are the A->B link, where A is the 205 | // dialer. This key material should be used to set up the 206 | // A send key. 207 | keyExchange(&sch.skey, sk[:32], pk[:32]) 208 | // The last 32 bytes are the B->A link, where A is the 209 | // dialer. This key material should be used to set up the A 210 | // receive key. 211 | keyExchange(&sch.rkey, sk[32:], pk[32:]) 212 | } else { 213 | // The first 32 bytes are the A->B link, where A is the 214 | // dialer. This key material should be used to set up the 215 | // B receive key. 216 | keyExchange(&sch.rkey, sk[:32], pk[:32]) 217 | // The last 32 bytes are the B->A link, where A is the 218 | // dialer. This key material should be used to set up the 219 | // B send key. 220 | keyExchange(&sch.skey, sk[32:], pk[32:]) 221 | } 222 | 223 | return true 224 | } 225 | 226 | // dialKEX handles the initial dialing key exchange. 227 | func (sch *SChannel) dialKEX(ch Channel, signer *[IdentityPrivateSize]byte, peer *[IdentityPublicSize]byte) bool { 228 | var sk [kexPrvSize]byte 229 | var pk [kexPubSize]byte 230 | 231 | if !generateKeypair(&sk, &pk) { 232 | return false 233 | } 234 | 235 | var kex [kexPubSize + SignatureSize]byte 236 | copy(kex[:], pk[:]) 237 | 238 | if !signKEX(&kex, signer) { 239 | return false 240 | } 241 | 242 | n, err := ch.Write(kex[:]) 243 | if err != nil || n != len(kex) { 244 | return false 245 | } 246 | 247 | zero(kex[:], 0) 248 | _, err = io.ReadFull(ch, kex[:]) 249 | if err != nil { 250 | return false 251 | } 252 | 253 | if !verifyKEX(&kex, peer) { 254 | return false 255 | } 256 | 257 | if !sch.doKEX(sk[:], kex[:kexPubSize], true) { 258 | return false 259 | } 260 | 261 | return true 262 | } 263 | 264 | // Dial initialise the SChannel and initiate a key exchange over the 265 | // Channel. If this returns true, an authenticated secure channel has 266 | // been established. If signer is not nil, the key exchange will be 267 | // signed with the key it contains. If peer is not nil, the key exchange 268 | // will be verified using the public key it contains. 269 | func Dial(ch Channel, signer *[IdentityPrivateSize]byte, peer *[IdentityPublicSize]byte) (*SChannel, bool) { 270 | var sch = &SChannel{} 271 | sch.reset() 272 | 273 | if ch == nil { 274 | return nil, false 275 | } 276 | 277 | if !sch.dialKEX(ch, signer, peer) { 278 | return nil, false 279 | } 280 | 281 | sch.Channel = ch 282 | sch.ready = true 283 | return sch, true 284 | } 285 | 286 | func (sch *SChannel) listenKEX(ch Channel, signer *[IdentityPrivateSize]byte, peer *[IdentityPublicSize]byte) bool { 287 | var sk [kexPrvSize]byte 288 | var pk [kexPubSize]byte 289 | 290 | if !generateKeypair(&sk, &pk) { 291 | return false 292 | } 293 | 294 | var kex [kexPubSize + SignatureSize]byte 295 | _, err := io.ReadFull(ch, kex[:]) 296 | if err != nil { 297 | return false 298 | } 299 | 300 | if !verifyKEX(&kex, peer) { 301 | return false 302 | } 303 | 304 | if !sch.doKEX(sk[:], kex[:kexPubSize], false) { 305 | return false 306 | } 307 | 308 | copy(kex[:], pk[:]) 309 | if !signKEX(&kex, signer) { 310 | return false 311 | } 312 | 313 | n, err := ch.Write(kex[:]) 314 | if err != nil || n != len(kex) { 315 | return false 316 | } 317 | zero(kex[:], 0) 318 | 319 | return true 320 | } 321 | 322 | // Listen initialises the SChannel and complete a key exchange over 323 | // the Channel. If this returns true, an authenticated secure channel has 324 | // been established. If signer is not nil, the key exchange will be 325 | // signed with the key it contains. If peer is not nil, the key exchange 326 | // will be verified using the public key it contains. 327 | func Listen(ch Channel, signer *[IdentityPrivateSize]byte, peer *[IdentityPublicSize]byte) (*SChannel, bool) { 328 | var sch = &SChannel{} 329 | sch.reset() 330 | 331 | if ch == nil { 332 | return nil, false 333 | } 334 | 335 | if !sch.listenKEX(ch, signer, peer) { 336 | return nil, false 337 | } 338 | 339 | sch.Channel = ch 340 | sch.ready = true 341 | return sch, true 342 | } 343 | 344 | func (sch *SChannel) encrypt(m []byte) ([]byte, bool) { 345 | out := make([]byte, nonceSize, nonceSize+len(m)) 346 | var nonce [nonceSize]byte 347 | _, err := io.ReadFull(prng, nonce[:]) 348 | if err != nil { 349 | return nil, false 350 | } 351 | 352 | copy(out, nonce[:]) 353 | return secretbox.Seal(out, m, &nonce, &sch.skey), true 354 | } 355 | 356 | func (sch *SChannel) send(t MessageType, m []byte) bool { 357 | sch.sctr++ 358 | out, ok := packMessage(sch.sctr, t, m) 359 | if !ok { 360 | return false 361 | } 362 | 363 | enc, ok := sch.encrypt(out) 364 | zero(out, 0) 365 | if !ok { 366 | return false 367 | } 368 | sch.SData += uint64(len(out)) 369 | 370 | err := binary.Write(sch.Channel, binary.BigEndian, uint32(len(enc))) 371 | if err != nil { 372 | return false 373 | } 374 | 375 | _, err = sch.Channel.Write(enc) 376 | if err != nil { 377 | return false 378 | } 379 | 380 | return true 381 | } 382 | 383 | // Send seals the message and sends it over the secure channel. 384 | func (sch *SChannel) Send(m []byte) bool { 385 | if !sch.ready { 386 | return false 387 | } 388 | return sch.send(NormalMessage, m) 389 | } 390 | 391 | // Message pairs a message type with contents. 392 | type Message struct { 393 | Type MessageType 394 | Contents []byte 395 | } 396 | 397 | func (sch *SChannel) decrypt(in []byte) ([]byte, bool) { 398 | if len(in) <= nonceSize { 399 | return nil, false 400 | } 401 | 402 | var nonce [nonceSize]byte 403 | copy(nonce[:], in[:nonceSize]) 404 | return secretbox.Open(nil, in[nonceSize:], &nonce, &sch.rkey) 405 | } 406 | 407 | func (sch *SChannel) getMessage() ([]byte, bool) { 408 | var mlen uint32 409 | err := binary.Read(sch.Channel, binary.BigEndian, &mlen) 410 | if err != nil { 411 | return nil, false 412 | } 413 | 414 | if mlen > BufSize+Overhead { 415 | return nil, false 416 | } 417 | 418 | _, err = io.ReadFull(sch.Channel, sch.buf[:int(mlen)]) 419 | if err != nil { 420 | return nil, false 421 | } 422 | 423 | out, ok := sch.decrypt(sch.buf[:int(mlen)]) 424 | if !ok { 425 | return nil, false 426 | } 427 | 428 | zero(sch.buf[:], int(mlen)) 429 | sch.RData += uint64(len(out)) 430 | return out, true 431 | } 432 | 433 | func (sch *SChannel) extractMessage(in []byte) (*Message, bool) { 434 | e, ok := unpackMessage(in) 435 | if !ok { 436 | return nil, false 437 | } 438 | 439 | if e.Sequence <= sch.rctr { 440 | return nil, false 441 | } 442 | sch.rctr = e.Sequence 443 | 444 | switch e.Type { 445 | case NormalMessage: 446 | // Do nothing 447 | case KEXMessage: 448 | if sch.kexip { 449 | break 450 | } 451 | 452 | if !(sch.receiveKEX(e)) { 453 | return nil, false 454 | } 455 | 456 | return &Message{Type: KEXMessage}, true 457 | case ShutdownMessage: 458 | // The contents of this message are irrelevant. 459 | return &Message{Type: ShutdownMessage}, true 460 | default: 461 | return nil, false 462 | } 463 | 464 | m := &Message{ 465 | Type: e.Type, 466 | } 467 | m.Contents = make([]byte, int(e.PayloadLength)) 468 | copy(m.Contents, e.Payload[:]) 469 | zero(e.Payload[:], int(e.PayloadLength)) 470 | return m, true 471 | } 472 | 473 | // Receive reads a new message from the secure channel. 474 | func (sch *SChannel) Receive() (*Message, bool) { 475 | if !sch.ready { 476 | return nil, false 477 | } 478 | 479 | out, ok := sch.getMessage() 480 | if !ok { 481 | return nil, false 482 | } 483 | 484 | return sch.extractMessage(out) 485 | } 486 | 487 | func (sch *SChannel) receiveKEX(e *envelope) bool { 488 | if e == nil || sch.kexip || !sch.ready { 489 | return false 490 | } 491 | 492 | if kexPubSize != int(e.PayloadLength) { 493 | return false 494 | } 495 | 496 | var sk [kexPrvSize]byte 497 | var pk [kexPubSize]byte 498 | 499 | if !generateKeypair(&sk, &pk) { 500 | return false 501 | } 502 | 503 | if !sch.send(KEXMessage, pk[:]) { 504 | return false 505 | } 506 | 507 | if !sch.doKEX(sk[:], e.Payload[:kexPubSize], false) { 508 | return false 509 | } 510 | 511 | return true 512 | } 513 | 514 | // Close signals the other end of the secure channel that the channel 515 | // is being closed, and calls Zero to zeroise the secure channel. After 516 | // this, the caller should close the underlying channel as appropriate. 517 | func (sch *SChannel) Close() bool { 518 | if sch == nil { 519 | return false 520 | } else if !sch.ready { 521 | return false 522 | } 523 | 524 | defer sch.Zero() 525 | return sch.send(ShutdownMessage, []byte{0}) 526 | } 527 | 528 | // Zero zeroises the channel, wiping the shared keys from memory and resetting 529 | // the channel. After this is called, the secure channel cannot be used for 530 | // anything else. 531 | func (sch *SChannel) Zero() { 532 | if sch == nil { 533 | return 534 | } 535 | 536 | zero(sch.skey[:], 0) 537 | zero(sch.rkey[:], 0) 538 | sch.reset() 539 | } 540 | 541 | // Rekey initiates a key rotation with the other side. Both sides will 542 | // generate new session private keys, and exchange their public 543 | // halves. These session keys will not be signed, as the channel is 544 | // assumed to be authenticated and secure at this point. Generally, 545 | // key rotation will not be an issue. However, peers may elect to 546 | // rekey after a certain time period, a certain number of messages 547 | // have been sent, or a certain amount of data will be sent. 548 | func (sch *SChannel) Rekey() bool { 549 | if !sch.ready { 550 | return false 551 | } 552 | 553 | var sk [kexPrvSize]byte 554 | var pk [kexPubSize]byte 555 | 556 | if !generateKeypair(&sk, &pk) { 557 | return false 558 | } 559 | 560 | if !sch.send(KEXMessage, pk[:]) { 561 | return false 562 | } 563 | 564 | sch.kexip = true 565 | m, ok := sch.Receive() 566 | if !ok || m.Type != KEXMessage { 567 | return false 568 | } 569 | sch.kexip = false 570 | 571 | if !sch.doKEX(sk[:], m.Contents, true) { 572 | return false 573 | } 574 | 575 | return true 576 | } 577 | --------------------------------------------------------------------------------