From 8035b360428441edf2c141a54fc186494fbcaa5e Mon Sep 17 00:00:00 2001 From: Jonathan Rudenberg Date: Tue, 12 Jul 2016 22:20:06 -0400 Subject: [PATCH] Add docs Signed-off-by: Jonathan Rudenberg --- cipher_suite.go | 41 ++++++++++++++++++++++ hkdf.go | 2 +- state.go | 90 ++++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 120 insertions(+), 13 deletions(-) diff --git a/cipher_suite.go b/cipher_suite.go index 8a7f24d..76deea5 100644 --- a/cipher_suite.go +++ b/cipher_suite.go @@ -16,33 +16,62 @@ import ( "golang.org/x/crypto/curve25519" ) +// A DHKey is a keypair used for Diffie-Hellman key agreement. type DHKey struct { Private []byte Public []byte } +// A DHFunc implements Diffie-Hellman key agreement. type DHFunc interface { + // GenerateKeypair generates a new keypair using random as a source of + // entropy. GenerateKeypair(random io.Reader) DHKey + + // DH performs a Diffie-Hellman calculation between the provided private and + // public keys and returns the result. DH(privkey, pubkey []byte) []byte + + // DHLen is the number of bytes returned by DH. DHLen() int + + // DHName is the name of the DH function. DHName() string } +// A HashFunc implements a cryptographic hash function. type HashFunc interface { + // Hash returns a hash state. Hash() hash.Hash + + // HashName is the name of the hash function. HashName() string } +// A CipherFunc implements an AEAD symmetric cipher. type CipherFunc interface { + // Cipher initializes the algorithm with the provided key and returns a Cipher. Cipher(k [32]byte) Cipher + + // CipherName is the name of the cipher. CipherName() string } +// A Cipher is a AEAD cipher that has been initialized with a key. type Cipher interface { + // Encrypt encrypts the provided plaintext with a nonce and then appends the + // ciphertext to out along with an authentication tag over the ciphertext + // and optional authenticated data. Encrypt(out []byte, n uint64, ad, plaintext []byte) []byte + + // Decrypt authenticates the ciphertext and optional authenticated data and + // then decrypts the provided ciphertext using the provided nonce and + // appends it to out. Decrypt(out []byte, n uint64, ad, ciphertext []byte) ([]byte, error) } +// A CipherSuite is a set of cryptographic primitives used in a Noise protocol. +// It should be constructed with NewCipherSuite. type CipherSuite interface { DHFunc CipherFunc @@ -50,6 +79,8 @@ type CipherSuite interface { Name() []byte } +// NewCipherSuite returns a CipherSuite constructed from the specified +// primitives. func NewCipherSuite(dh DHFunc, c CipherFunc, h HashFunc) CipherSuite { return ciphersuite{ DHFunc: dh, @@ -68,6 +99,7 @@ type ciphersuite struct { func (s ciphersuite) Name() []byte { return s.name } +// DH25519 is the Curve25519 ECDH function. var DH25519 DHFunc = dh25519{} type dh25519 struct{} @@ -103,6 +135,7 @@ type cipherFn struct { func (c cipherFn) Cipher(k [32]byte) Cipher { return c.fn(k) } func (c cipherFn) CipherName() string { return c.name } +// CipherAESGCM is the AES256-GCM AEAD cipher. var CipherAESGCM CipherFunc = cipherFn{ func(k [32]byte) Cipher { c, err := aes.NewCipher(k[:]) @@ -125,6 +158,7 @@ var CipherAESGCM CipherFunc = cipherFn{ "AESGCM", } +// CipherChaChaPoly is the ChaCha20-Poly1305 AEAD cipher construction. var CipherChaChaPoly CipherFunc = cipherFn{ func(k [32]byte) Cipher { return aeadCipher{ @@ -160,7 +194,14 @@ type hashFn struct { func (h hashFn) Hash() hash.Hash { return h.fn() } func (h hashFn) HashName() string { return h.name } +// HashSHA256 is the SHA-256 hash function. var HashSHA256 HashFunc = hashFn{sha256.New, "SHA256"} + +// HashSHA512 is the SHA-512 hash function. var HashSHA512 HashFunc = hashFn{sha512.New, "SHA512"} + +// HashBLAKE2b is the BLAKE2b hash function. var HashBLAKE2b HashFunc = hashFn{blake2b.New, "BLAKE2b"} + +// HashBLAKE2s is the BLAKE2s hash function. var HashBLAKE2s HashFunc = hashFn{blake2s.New, "BLAKE2s"} diff --git a/hkdf.go b/hkdf.go index f318bcf..94aa746 100644 --- a/hkdf.go +++ b/hkdf.go @@ -5,7 +5,7 @@ import ( "hash" ) -func HKDF(h func() hash.Hash, out1, out2, chainingKey, inputKeyMaterial []byte) ([]byte, []byte) { +func hkdf(h func() hash.Hash, out1, out2, chainingKey, inputKeyMaterial []byte) ([]byte, []byte) { if len(out1) > 0 { panic("len(out1) > 0") } diff --git a/state.go b/state.go index c648ad1..73506ff 100644 --- a/state.go +++ b/state.go @@ -1,10 +1,19 @@ +// Package noise implements the Noise Protocol Framework. +// +// Noise is a low-level framework for building crypto protocols. Noise protocols +// support mutual and optional authentication, identity hiding, forward secrecy, +// zero round-trip encryption, and other advanced features. For more details, +// visit http://noiseprotocol.org package noise import ( + "crypto/rand" "errors" "io" ) +// A CipherState provides symmetric encryption and decryption after a successful +// handshake. type CipherState struct { cs CipherSuite c Cipher @@ -14,6 +23,10 @@ type CipherState struct { invalid bool } +// Encrypt encrypts the plaintext and then appends the ciphertext and an +// authentication tag across the ciphertext and optional authenticated data to +// out. This method automatically increments the nonce after every call, so +// messages must be decrypted in the same order. func (s *CipherState) Encrypt(out, ad, plaintext []byte) []byte { if s.invalid { panic("noise: CipherSuite has been copied, state is invalid") @@ -23,6 +36,10 @@ func (s *CipherState) Encrypt(out, ad, plaintext []byte) []byte { return out } +// Decrypt checks the authenticity of the ciphertext and authenticated data and +// then decrypts and appends the plaintext to out. This method automatically +// increments the nonce after every call, messages must be provided in the same +// order that they were encrypted with no missing messages. func (s *CipherState) Decrypt(out, ad, ciphertext []byte) ([]byte, error) { if s.invalid { panic("noise: CipherSuite has been copied, state is invalid") @@ -68,7 +85,7 @@ func (s *symmetricState) MixKey(dhOutput []byte) { s.n = 0 s.hasK = true var hk []byte - s.ck, hk = HKDF(s.cs.Hash, s.ck[:0], s.k[:0], s.ck, dhOutput) + s.ck, hk = hkdf(s.cs.Hash, s.ck[:0], s.k[:0], s.ck, dhOutput) copy(s.k[:], hk) s.c = s.cs.Cipher(s.k) } @@ -82,7 +99,7 @@ func (s *symmetricState) MixHash(data []byte) { func (s *symmetricState) MixPresharedKey(presharedKey []byte) { var temp []byte - s.ck, temp = HKDF(s.cs.Hash, s.ck[:0], nil, s.ck, presharedKey) + s.ck, temp = hkdf(s.cs.Hash, s.ck[:0], nil, s.ck, presharedKey) s.MixHash(temp) s.hasPSK = true } @@ -112,7 +129,7 @@ func (s *symmetricState) DecryptAndHash(out, data []byte) ([]byte, error) { func (s *symmetricState) Split() (*CipherState, *CipherState) { s1, s2 := &CipherState{cs: s.cs}, &CipherState{cs: s.cs} - hk1, hk2 := HKDF(s.cs.Hash, s1.k[:0], s2.k[:0], s.ck, nil) + hk1, hk2 := hkdf(s.cs.Hash, s1.k[:0], s2.k[:0], s.ck, nil) copy(s1.k[:], hk1) copy(s2.k[:], hk2) s1.c = s.cs.Cipher(s1.k) @@ -120,8 +137,11 @@ func (s *symmetricState) Split() (*CipherState, *CipherState) { return s1, s2 } +// A MessagePattern is a single message or operation used in a Noise handshake. type MessagePattern int +// A HandshakePattern is a list of messages and operations that are used to +// perform a specific Noise handshake. type HandshakePattern struct { Name string InitiatorPreMessages []MessagePattern @@ -138,8 +158,12 @@ const ( MessagePatternDHSS ) +// MaxMsgLen is the maximum number of bytes that can be sent in a single Noise +// message. const MaxMsgLen = 65535 +// A HandshakeState tracks the state of a Noise handshake. It may be discarded +// after the handshake is complete. type HandshakeState struct { ss symmetricState s DHKey // local static keypair @@ -152,19 +176,46 @@ type HandshakeState struct { rng io.Reader } +// A Config provides the details necessary to process a Noise handshake. It is +// never modified by this package, and can be reused, but care must be taken to +// generate a new ephemeral key for each handshake if they are used in the +// pattern. type Config struct { - CipherSuite CipherSuite - Random io.Reader - Pattern HandshakePattern - Initiator bool - Prologue []byte - PresharedKey []byte - StaticKeypair DHKey + // CipherSuite is the set of cryptographic primitives that will be used. + CipherSuite CipherSuite + + // Random is the source for cryptographically appropriate random bytes. If + // zero, it is automtically configed. + Random io.Reader + + // Pattern is the pattern for the handshake. + Pattern HandshakePattern + + // Initiator must be true if the first message in the handshake will be sent + // by this peer. + Initiator bool + + // Prologue is an optional message that has already be communicated and must + // be identical on both sides for the handshake to succeed. + Prologue []byte + + // PresharedKey is the optional preshared key for the handshake. + PresharedKey []byte + + // StaticKeypair is this peer's static keypair. + StaticKeypair DHKey + + // EphemeralKeypair is this peer's static keypair. EphemeralKeypair DHKey - PeerStatic []byte - PeerEphemeral []byte + + // PeerStatic is the static public key of the remote peer. + PeerStatic []byte + + // PeerEphemeral is the ephemeral public key of the remote peer. + PeerEphemeral []byte } +// NewHandshakeState starts a new handshake using the provided configuration. func NewHandshakeState(c Config) *HandshakeState { hs := &HandshakeState{ s: c.StaticKeypair, @@ -174,6 +225,9 @@ func NewHandshakeState(c Config) *HandshakeState { shouldWrite: c.Initiator, rng: c.Random, } + if hs.rng == nil { + hs.rng = rand.Reader + } if len(c.PeerEphemeral) > 0 { hs.re = make([]byte, len(c.PeerEphemeral)) copy(hs.re, c.PeerEphemeral) @@ -215,6 +269,12 @@ func NewHandshakeState(c Config) *HandshakeState { return hs } +// WriteMessage appends a handshake message to out. The message will include the +// optional payload if provided. If the handshake is completed by the call, two +// CipherStates will be returned, one is used for encryption of messages to the +// remote peer, the other is used for decryption of messages from the remote +// peer. It is an error to call this method out of sync with the handshake +// pattern. func (s *HandshakeState) WriteMessage(out, payload []byte) ([]byte, *CipherState, *CipherState) { if !s.shouldWrite { panic("noise: unexpected call to WriteMessage should be ReadMessage") @@ -262,8 +322,14 @@ func (s *HandshakeState) WriteMessage(out, payload []byte) ([]byte, *CipherState return out, nil, nil } +// ErrShortMessage is returned by ReadMessage if a message is not as long as it should be. var ErrShortMessage = errors.New("noise: message is too short") +// ReadMessage processes a received handshake message and appends the payload, +// if any to out. If the handshake is completed by the call, two CipherStates +// will be returned, one is used for encryption of messages to the remote peer, +// the other is used for decryption of messages from the remote peer. It is an +// error to call this method out of sync with the handshake pattern. func (s *HandshakeState) ReadMessage(out, message []byte) ([]byte, *CipherState, *CipherState, error) { if s.shouldWrite { panic("noise: unexpected call to ReadMessage should be WriteMessage")