mirror of
https://github.com/logos-messaging/noise.git
synced 2026-01-07 16:43:05 +00:00
Add docs
Signed-off-by: Jonathan Rudenberg <jonathan@titanous.com>
This commit is contained in:
parent
d4248be25d
commit
8035b36042
@ -16,33 +16,62 @@ import (
|
|||||||
"golang.org/x/crypto/curve25519"
|
"golang.org/x/crypto/curve25519"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// A DHKey is a keypair used for Diffie-Hellman key agreement.
|
||||||
type DHKey struct {
|
type DHKey struct {
|
||||||
Private []byte
|
Private []byte
|
||||||
Public []byte
|
Public []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A DHFunc implements Diffie-Hellman key agreement.
|
||||||
type DHFunc interface {
|
type DHFunc interface {
|
||||||
|
// GenerateKeypair generates a new keypair using random as a source of
|
||||||
|
// entropy.
|
||||||
GenerateKeypair(random io.Reader) DHKey
|
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
|
DH(privkey, pubkey []byte) []byte
|
||||||
|
|
||||||
|
// DHLen is the number of bytes returned by DH.
|
||||||
DHLen() int
|
DHLen() int
|
||||||
|
|
||||||
|
// DHName is the name of the DH function.
|
||||||
DHName() string
|
DHName() string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A HashFunc implements a cryptographic hash function.
|
||||||
type HashFunc interface {
|
type HashFunc interface {
|
||||||
|
// Hash returns a hash state.
|
||||||
Hash() hash.Hash
|
Hash() hash.Hash
|
||||||
|
|
||||||
|
// HashName is the name of the hash function.
|
||||||
HashName() string
|
HashName() string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A CipherFunc implements an AEAD symmetric cipher.
|
||||||
type CipherFunc interface {
|
type CipherFunc interface {
|
||||||
|
// Cipher initializes the algorithm with the provided key and returns a Cipher.
|
||||||
Cipher(k [32]byte) Cipher
|
Cipher(k [32]byte) Cipher
|
||||||
|
|
||||||
|
// CipherName is the name of the cipher.
|
||||||
CipherName() string
|
CipherName() string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A Cipher is a AEAD cipher that has been initialized with a key.
|
||||||
type Cipher interface {
|
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
|
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)
|
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 {
|
type CipherSuite interface {
|
||||||
DHFunc
|
DHFunc
|
||||||
CipherFunc
|
CipherFunc
|
||||||
@ -50,6 +79,8 @@ type CipherSuite interface {
|
|||||||
Name() []byte
|
Name() []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewCipherSuite returns a CipherSuite constructed from the specified
|
||||||
|
// primitives.
|
||||||
func NewCipherSuite(dh DHFunc, c CipherFunc, h HashFunc) CipherSuite {
|
func NewCipherSuite(dh DHFunc, c CipherFunc, h HashFunc) CipherSuite {
|
||||||
return ciphersuite{
|
return ciphersuite{
|
||||||
DHFunc: dh,
|
DHFunc: dh,
|
||||||
@ -68,6 +99,7 @@ type ciphersuite struct {
|
|||||||
|
|
||||||
func (s ciphersuite) Name() []byte { return s.name }
|
func (s ciphersuite) Name() []byte { return s.name }
|
||||||
|
|
||||||
|
// DH25519 is the Curve25519 ECDH function.
|
||||||
var DH25519 DHFunc = dh25519{}
|
var DH25519 DHFunc = dh25519{}
|
||||||
|
|
||||||
type dh25519 struct{}
|
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) Cipher(k [32]byte) Cipher { return c.fn(k) }
|
||||||
func (c cipherFn) CipherName() string { return c.name }
|
func (c cipherFn) CipherName() string { return c.name }
|
||||||
|
|
||||||
|
// CipherAESGCM is the AES256-GCM AEAD cipher.
|
||||||
var CipherAESGCM CipherFunc = cipherFn{
|
var CipherAESGCM CipherFunc = cipherFn{
|
||||||
func(k [32]byte) Cipher {
|
func(k [32]byte) Cipher {
|
||||||
c, err := aes.NewCipher(k[:])
|
c, err := aes.NewCipher(k[:])
|
||||||
@ -125,6 +158,7 @@ var CipherAESGCM CipherFunc = cipherFn{
|
|||||||
"AESGCM",
|
"AESGCM",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CipherChaChaPoly is the ChaCha20-Poly1305 AEAD cipher construction.
|
||||||
var CipherChaChaPoly CipherFunc = cipherFn{
|
var CipherChaChaPoly CipherFunc = cipherFn{
|
||||||
func(k [32]byte) Cipher {
|
func(k [32]byte) Cipher {
|
||||||
return aeadCipher{
|
return aeadCipher{
|
||||||
@ -160,7 +194,14 @@ type hashFn struct {
|
|||||||
func (h hashFn) Hash() hash.Hash { return h.fn() }
|
func (h hashFn) Hash() hash.Hash { return h.fn() }
|
||||||
func (h hashFn) HashName() string { return h.name }
|
func (h hashFn) HashName() string { return h.name }
|
||||||
|
|
||||||
|
// HashSHA256 is the SHA-256 hash function.
|
||||||
var HashSHA256 HashFunc = hashFn{sha256.New, "SHA256"}
|
var HashSHA256 HashFunc = hashFn{sha256.New, "SHA256"}
|
||||||
|
|
||||||
|
// HashSHA512 is the SHA-512 hash function.
|
||||||
var HashSHA512 HashFunc = hashFn{sha512.New, "SHA512"}
|
var HashSHA512 HashFunc = hashFn{sha512.New, "SHA512"}
|
||||||
|
|
||||||
|
// HashBLAKE2b is the BLAKE2b hash function.
|
||||||
var HashBLAKE2b HashFunc = hashFn{blake2b.New, "BLAKE2b"}
|
var HashBLAKE2b HashFunc = hashFn{blake2b.New, "BLAKE2b"}
|
||||||
|
|
||||||
|
// HashBLAKE2s is the BLAKE2s hash function.
|
||||||
var HashBLAKE2s HashFunc = hashFn{blake2s.New, "BLAKE2s"}
|
var HashBLAKE2s HashFunc = hashFn{blake2s.New, "BLAKE2s"}
|
||||||
|
|||||||
2
hkdf.go
2
hkdf.go
@ -5,7 +5,7 @@ import (
|
|||||||
"hash"
|
"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 {
|
if len(out1) > 0 {
|
||||||
panic("len(out1) > 0")
|
panic("len(out1) > 0")
|
||||||
}
|
}
|
||||||
|
|||||||
90
state.go
90
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
|
package noise
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rand"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// A CipherState provides symmetric encryption and decryption after a successful
|
||||||
|
// handshake.
|
||||||
type CipherState struct {
|
type CipherState struct {
|
||||||
cs CipherSuite
|
cs CipherSuite
|
||||||
c Cipher
|
c Cipher
|
||||||
@ -14,6 +23,10 @@ type CipherState struct {
|
|||||||
invalid bool
|
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 {
|
func (s *CipherState) Encrypt(out, ad, plaintext []byte) []byte {
|
||||||
if s.invalid {
|
if s.invalid {
|
||||||
panic("noise: CipherSuite has been copied, state is 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
|
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) {
|
func (s *CipherState) Decrypt(out, ad, ciphertext []byte) ([]byte, error) {
|
||||||
if s.invalid {
|
if s.invalid {
|
||||||
panic("noise: CipherSuite has been copied, state is invalid")
|
panic("noise: CipherSuite has been copied, state is invalid")
|
||||||
@ -68,7 +85,7 @@ func (s *symmetricState) MixKey(dhOutput []byte) {
|
|||||||
s.n = 0
|
s.n = 0
|
||||||
s.hasK = true
|
s.hasK = true
|
||||||
var hk []byte
|
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)
|
copy(s.k[:], hk)
|
||||||
s.c = s.cs.Cipher(s.k)
|
s.c = s.cs.Cipher(s.k)
|
||||||
}
|
}
|
||||||
@ -82,7 +99,7 @@ func (s *symmetricState) MixHash(data []byte) {
|
|||||||
|
|
||||||
func (s *symmetricState) MixPresharedKey(presharedKey []byte) {
|
func (s *symmetricState) MixPresharedKey(presharedKey []byte) {
|
||||||
var temp []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.MixHash(temp)
|
||||||
s.hasPSK = true
|
s.hasPSK = true
|
||||||
}
|
}
|
||||||
@ -112,7 +129,7 @@ func (s *symmetricState) DecryptAndHash(out, data []byte) ([]byte, error) {
|
|||||||
|
|
||||||
func (s *symmetricState) Split() (*CipherState, *CipherState) {
|
func (s *symmetricState) Split() (*CipherState, *CipherState) {
|
||||||
s1, s2 := &CipherState{cs: s.cs}, &CipherState{cs: s.cs}
|
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(s1.k[:], hk1)
|
||||||
copy(s2.k[:], hk2)
|
copy(s2.k[:], hk2)
|
||||||
s1.c = s.cs.Cipher(s1.k)
|
s1.c = s.cs.Cipher(s1.k)
|
||||||
@ -120,8 +137,11 @@ func (s *symmetricState) Split() (*CipherState, *CipherState) {
|
|||||||
return s1, s2
|
return s1, s2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A MessagePattern is a single message or operation used in a Noise handshake.
|
||||||
type MessagePattern int
|
type MessagePattern int
|
||||||
|
|
||||||
|
// A HandshakePattern is a list of messages and operations that are used to
|
||||||
|
// perform a specific Noise handshake.
|
||||||
type HandshakePattern struct {
|
type HandshakePattern struct {
|
||||||
Name string
|
Name string
|
||||||
InitiatorPreMessages []MessagePattern
|
InitiatorPreMessages []MessagePattern
|
||||||
@ -138,8 +158,12 @@ const (
|
|||||||
MessagePatternDHSS
|
MessagePatternDHSS
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// MaxMsgLen is the maximum number of bytes that can be sent in a single Noise
|
||||||
|
// message.
|
||||||
const MaxMsgLen = 65535
|
const MaxMsgLen = 65535
|
||||||
|
|
||||||
|
// A HandshakeState tracks the state of a Noise handshake. It may be discarded
|
||||||
|
// after the handshake is complete.
|
||||||
type HandshakeState struct {
|
type HandshakeState struct {
|
||||||
ss symmetricState
|
ss symmetricState
|
||||||
s DHKey // local static keypair
|
s DHKey // local static keypair
|
||||||
@ -152,19 +176,46 @@ type HandshakeState struct {
|
|||||||
rng io.Reader
|
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 {
|
type Config struct {
|
||||||
CipherSuite CipherSuite
|
// CipherSuite is the set of cryptographic primitives that will be used.
|
||||||
Random io.Reader
|
CipherSuite CipherSuite
|
||||||
Pattern HandshakePattern
|
|
||||||
Initiator bool
|
// Random is the source for cryptographically appropriate random bytes. If
|
||||||
Prologue []byte
|
// zero, it is automtically configed.
|
||||||
PresharedKey []byte
|
Random io.Reader
|
||||||
StaticKeypair DHKey
|
|
||||||
|
// 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
|
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 {
|
func NewHandshakeState(c Config) *HandshakeState {
|
||||||
hs := &HandshakeState{
|
hs := &HandshakeState{
|
||||||
s: c.StaticKeypair,
|
s: c.StaticKeypair,
|
||||||
@ -174,6 +225,9 @@ func NewHandshakeState(c Config) *HandshakeState {
|
|||||||
shouldWrite: c.Initiator,
|
shouldWrite: c.Initiator,
|
||||||
rng: c.Random,
|
rng: c.Random,
|
||||||
}
|
}
|
||||||
|
if hs.rng == nil {
|
||||||
|
hs.rng = rand.Reader
|
||||||
|
}
|
||||||
if len(c.PeerEphemeral) > 0 {
|
if len(c.PeerEphemeral) > 0 {
|
||||||
hs.re = make([]byte, len(c.PeerEphemeral))
|
hs.re = make([]byte, len(c.PeerEphemeral))
|
||||||
copy(hs.re, c.PeerEphemeral)
|
copy(hs.re, c.PeerEphemeral)
|
||||||
@ -215,6 +269,12 @@ func NewHandshakeState(c Config) *HandshakeState {
|
|||||||
return hs
|
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) {
|
func (s *HandshakeState) WriteMessage(out, payload []byte) ([]byte, *CipherState, *CipherState) {
|
||||||
if !s.shouldWrite {
|
if !s.shouldWrite {
|
||||||
panic("noise: unexpected call to WriteMessage should be ReadMessage")
|
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
|
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")
|
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) {
|
func (s *HandshakeState) ReadMessage(out, message []byte) ([]byte, *CipherState, *CipherState, error) {
|
||||||
if s.shouldWrite {
|
if s.shouldWrite {
|
||||||
panic("noise: unexpected call to ReadMessage should be WriteMessage")
|
panic("noise: unexpected call to ReadMessage should be WriteMessage")
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user