mirror of https://github.com/waku-org/noise.git
328 lines
8.2 KiB
Go
328 lines
8.2 KiB
Go
package box
|
|
|
|
import (
|
|
"crypto/cipher"
|
|
"crypto/hmac"
|
|
"crypto/rand"
|
|
"crypto/sha512"
|
|
"crypto/subtle"
|
|
"encoding/binary"
|
|
"errors"
|
|
"io"
|
|
|
|
"code.google.com/p/go.crypto/curve25519"
|
|
"code.google.com/p/go.crypto/poly1305"
|
|
"github.com/titanous/chacha20"
|
|
)
|
|
|
|
type Ciphersuite interface {
|
|
AppendName(dst []byte) []byte
|
|
DHLen() int
|
|
CCLen() int
|
|
MACLen() int
|
|
KeyLen() (int, int)
|
|
GenerateKey(io.Reader) (Key, error)
|
|
|
|
DH(privkey, pubkey []byte) []byte
|
|
NewCipher(cc []byte) CipherContext
|
|
}
|
|
|
|
type CipherContext interface {
|
|
Reset(cc []byte)
|
|
Encrypt(dst, plaintext, authtext []byte) []byte
|
|
Decrypt(ciphertext, authtext []byte) ([]byte, error)
|
|
}
|
|
|
|
const CVLen = 48
|
|
|
|
type Key struct {
|
|
Public []byte
|
|
Private []byte
|
|
}
|
|
|
|
type Crypter struct {
|
|
Cipher Ciphersuite
|
|
Key Key
|
|
PeerKey Key
|
|
ChainVar []byte
|
|
|
|
scratch [64]byte
|
|
cc CipherContext
|
|
}
|
|
|
|
func (c *Crypter) EncryptBody(dst, plaintext, authtext []byte, padLen int) []byte {
|
|
var p []byte
|
|
if plainLen := len(plaintext) + padLen + 4; len(c.scratch) >= plainLen {
|
|
p = c.scratch[:plainLen]
|
|
} else {
|
|
p = make([]byte, plainLen)
|
|
}
|
|
copy(p, plaintext)
|
|
if _, err := io.ReadFull(rand.Reader, p[len(plaintext):len(plaintext)+padLen]); err != nil {
|
|
panic(err)
|
|
}
|
|
binary.BigEndian.PutUint32(p[len(plaintext)+padLen:], uint32(padLen))
|
|
return c.cc.Encrypt(dst, p, authtext)
|
|
}
|
|
|
|
func (c *Crypter) EncryptBox(dst []byte, ephKey *Key, plaintext []byte, padLen int, kdfNum uint8) ([]byte, error) {
|
|
if len(c.ChainVar) == 0 {
|
|
c.ChainVar = make([]byte, CVLen)
|
|
}
|
|
if ephKey == nil {
|
|
k, err := c.Cipher.GenerateKey(rand.Reader)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ephKey = &k
|
|
}
|
|
dstPrefixLen := len(dst)
|
|
// Allocate a new slice that can fit the full encrypted box if the current dst doesn't fit
|
|
if encLen := c.BoxLen(len(plaintext) + padLen); cap(dst)-len(dst) < encLen {
|
|
newDst := make([]byte, len(dst), len(dst)+encLen)
|
|
copy(newDst, dst)
|
|
dst = newDst
|
|
}
|
|
|
|
dh1 := c.Cipher.DH(ephKey.Private, c.PeerKey.Public)
|
|
dh2 := c.Cipher.DH(c.Key.Private, c.PeerKey.Public)
|
|
|
|
cv1, cc1 := c.deriveKey(dh1, c.ChainVar, kdfNum)
|
|
cv2, cc2 := c.deriveKey(dh2, cv1, kdfNum+1)
|
|
c.ChainVar = cv2
|
|
|
|
dst = append(dst, ephKey.Public...)
|
|
dst = c.cipher(cc1).Encrypt(dst, c.Key.Public, append(c.PeerKey.Public, ephKey.Public...))
|
|
c.cc.Reset(cc2)
|
|
return c.EncryptBody(dst, plaintext, append(c.PeerKey.Public, dst[dstPrefixLen:]...), padLen), nil
|
|
}
|
|
|
|
func (c *Crypter) BoxLen(n int) int {
|
|
return n + (2 * c.Cipher.DHLen()) + (2 * c.Cipher.MACLen()) + 4
|
|
}
|
|
|
|
func (c *Crypter) BodyLen(n int) int {
|
|
return n + c.Cipher.MACLen() + 4
|
|
}
|
|
|
|
func (c *Crypter) SetContext(cc []byte) {
|
|
c.cipher(cc)
|
|
}
|
|
|
|
func (c *Crypter) cipher(cc []byte) CipherContext {
|
|
if c.cc == nil {
|
|
c.cc = c.Cipher.NewCipher(cc)
|
|
} else {
|
|
c.cc.Reset(cc)
|
|
}
|
|
return c.cc
|
|
}
|
|
|
|
func (c *Crypter) DecryptBox(ciphertext []byte, kdfNum uint8) ([]byte, error) {
|
|
if len(c.ChainVar) == 0 {
|
|
c.ChainVar = make([]byte, CVLen)
|
|
}
|
|
|
|
ephPubKey := ciphertext[:c.Cipher.DHLen()]
|
|
dh1 := c.Cipher.DH(c.Key.Private, ephPubKey)
|
|
cv1, cc1 := c.deriveKey(dh1, c.ChainVar, kdfNum)
|
|
|
|
header := ciphertext[:(2*c.Cipher.DHLen())+c.Cipher.MACLen()]
|
|
ciphertext = ciphertext[len(header):]
|
|
senderPubKey, err := c.cipher(cc1).Decrypt(header[c.Cipher.DHLen():], append(c.Key.Public, ephPubKey...))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(c.PeerKey.Public) > 0 {
|
|
if len(c.PeerKey.Public) != len(senderPubKey) || subtle.ConstantTimeCompare(senderPubKey, c.PeerKey.Public) != 1 {
|
|
return nil, errors.New("pipe: unexpected sender public key")
|
|
}
|
|
}
|
|
|
|
dh2 := c.Cipher.DH(c.Key.Private, senderPubKey)
|
|
cv2, cc2 := c.deriveKey(dh2, cv1, kdfNum+1)
|
|
c.ChainVar = cv2
|
|
body, err := c.cipher(cc2).Decrypt(ciphertext, append(c.Key.Public, header...))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
padLen := int(binary.BigEndian.Uint32(body[len(body)-4:]))
|
|
|
|
return body[:len(body)-(padLen+4)], nil
|
|
}
|
|
|
|
func (c *Crypter) DecryptBody(ciphertext, authtext []byte) ([]byte, error) {
|
|
if c.cc == nil {
|
|
return nil, errors.New("box: uninitialized cipher context")
|
|
}
|
|
return c.cc.Decrypt(ciphertext, authtext)
|
|
}
|
|
|
|
func (c *Crypter) deriveKey(dh, cv []byte, kdfNum uint8) ([]byte, []byte) {
|
|
extra := append(c.Cipher.AppendName(c.scratch[:0]), kdfNum)
|
|
k := DeriveKey(dh, cv, extra, CVLen+c.Cipher.CCLen())
|
|
return k[:CVLen], k[CVLen:]
|
|
}
|
|
|
|
func DeriveKey(secret, extra, info []byte, outputLen int) []byte {
|
|
buf := make([]byte, outputLen+sha512.Size)
|
|
output := buf[:0:outputLen]
|
|
t := buf[outputLen:]
|
|
h := hmac.New(sha512.New, secret)
|
|
var c byte
|
|
for len(output) < outputLen {
|
|
h.Write(info)
|
|
h.Write([]byte{c})
|
|
h.Write(t[:32])
|
|
h.Write(extra)
|
|
t = h.Sum(t[:0])
|
|
h.Reset()
|
|
c++
|
|
if outputLen-len(output) < len(t) {
|
|
output = append(output, t[:outputLen-len(output)]...)
|
|
} else {
|
|
output = append(output, t...)
|
|
}
|
|
}
|
|
return output
|
|
}
|
|
|
|
var Noise255 = noise255{}
|
|
|
|
type noise255 struct{}
|
|
|
|
func (noise255) AppendName(dst []byte) []byte {
|
|
return append(dst, "Noise255\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"...)
|
|
}
|
|
func (noise255) DHLen() int { return 32 }
|
|
func (noise255) CCLen() int { return 40 }
|
|
func (noise255) MACLen() int { return 16 }
|
|
|
|
func (noise255) GenerateKey(random io.Reader) (Key, error) {
|
|
var pubKey, privKey [32]byte
|
|
if _, err := io.ReadFull(random, privKey[:]); err != nil {
|
|
return Key{}, err
|
|
}
|
|
privKey[0] &= 248
|
|
privKey[31] &= 127
|
|
privKey[31] |= 64
|
|
curve25519.ScalarBaseMult(&pubKey, &privKey)
|
|
return Key{Private: privKey[:], Public: pubKey[:]}, nil
|
|
}
|
|
|
|
func (noise255) KeyLen() (int, int) {
|
|
return 32, 32
|
|
}
|
|
|
|
func (noise255) DH(privkey, pubkey []byte) []byte {
|
|
var dst, in, base [32]byte
|
|
copy(in[:], privkey)
|
|
copy(base[:], pubkey)
|
|
curve25519.ScalarMult(&dst, &in, &base)
|
|
return dst[:]
|
|
}
|
|
|
|
func (noise255) NewCipher(cc []byte) CipherContext {
|
|
return &noise255ctx{cc: cc}
|
|
}
|
|
|
|
type noise255ctx struct {
|
|
cc []byte
|
|
keystream [168]byte
|
|
cipher chacha20.Cipher
|
|
}
|
|
|
|
func (n *noise255ctx) Reset(cc []byte) {
|
|
n.cc = cc
|
|
}
|
|
|
|
func (n *noise255ctx) key() (cipher.Stream, []byte) {
|
|
cipherKey := n.cc[:32]
|
|
iv := n.cc[32:40]
|
|
|
|
if err := n.cipher.Initialize(cipherKey, iv); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
keystream := n.keystream[:64]
|
|
for i := range keystream {
|
|
n.keystream[i] = 0
|
|
}
|
|
n.cipher.XORKeyStream(keystream, keystream)
|
|
|
|
return &n.cipher, keystream
|
|
}
|
|
|
|
func (n *noise255ctx) rekey() {
|
|
cipherKey := n.cc[:32]
|
|
iv := n.cc[32:40]
|
|
for i := range iv {
|
|
iv[i] ^= 0xff
|
|
}
|
|
if err := n.cipher.Initialize(cipherKey, iv); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
ks := n.keystream[64:]
|
|
for i := range ks {
|
|
ks[i] = 0
|
|
}
|
|
n.cipher.XORKeyStream(ks, ks)
|
|
n.cc = ks[64:]
|
|
}
|
|
|
|
func (n *noise255ctx) mac(keystream, ciphertext, authtext []byte) [16]byte {
|
|
var macKey [32]byte
|
|
var tag [16]byte
|
|
copy(macKey[:], keystream)
|
|
poly1305.Sum(&tag, n.authData(ciphertext, authtext), &macKey)
|
|
return tag
|
|
}
|
|
|
|
func (n *noise255ctx) Encrypt(dst, plaintext, authtext []byte) []byte {
|
|
c, keystream := n.key()
|
|
ciphertext := make([]byte, len(plaintext), len(plaintext)+16)
|
|
c.XORKeyStream(ciphertext, plaintext)
|
|
n.rekey()
|
|
tag := n.mac(keystream, ciphertext, authtext)
|
|
return append(dst, append(ciphertext, tag[:]...)...)
|
|
}
|
|
|
|
var ErrAuthFailed = errors.New("box: message authentication failed")
|
|
|
|
func (n *noise255ctx) Decrypt(ciphertext, authtext []byte) ([]byte, error) {
|
|
if len(ciphertext) < 16 {
|
|
return nil, ErrAuthFailed
|
|
}
|
|
digest := ciphertext[len(ciphertext)-16:]
|
|
ciphertext = ciphertext[:len(ciphertext)-16]
|
|
c, keystream := n.key()
|
|
tag := n.mac(keystream, ciphertext, authtext)
|
|
|
|
if subtle.ConstantTimeCompare(digest, tag[:]) != 1 {
|
|
return nil, ErrAuthFailed
|
|
}
|
|
|
|
plaintext := make([]byte, len(ciphertext))
|
|
c.XORKeyStream(plaintext, ciphertext)
|
|
n.rekey()
|
|
return plaintext, nil
|
|
}
|
|
|
|
func (noise255ctx) authData(ciphertext, authtext []byte) []byte {
|
|
// PAD16(authtext) || PAD16(ciphertext) || (uint64_little_endian)len(authtext) || (uint64_little_endian)len(ciphertext)
|
|
authData := make([]byte, pad16len(len(authtext))+pad16len(len(ciphertext))+8+8)
|
|
copy(authData, authtext)
|
|
offset := pad16len(len(authtext))
|
|
copy(authData[offset:], ciphertext)
|
|
offset += pad16len(len(ciphertext))
|
|
binary.LittleEndian.PutUint64(authData[offset:], uint64(len(authtext)))
|
|
offset += 8
|
|
binary.LittleEndian.PutUint64(authData[offset:], uint64(len(ciphertext)))
|
|
return authData
|
|
}
|
|
|
|
func pad16len(l int) int {
|
|
return l + ((16 - (l % 16)) % 16)
|
|
}
|