watchtower/blob/justice_kit: use randomized 192-bit nonce

This commit modifies the blob encryption scheme to
use chacha20-poly1305 with a randomized 192-bit nonce.
The previous approach used a deterministic nonce scheme,
which is being replaced to simplify the requirements of
a correct implementation.  As a result, each payload
gains an addtional 24-bytes prepended to the ciphertext.
This commit is contained in:
Conner Fromknecht 2018-10-31 20:42:01 -07:00
parent 2dd399ca52
commit c5eba3b608
No known key found for this signature in database
GPG Key ID: E7D737B67FA592C7

View File

@ -2,6 +2,7 @@ package blob
import (
"bytes"
"crypto/rand"
"encoding/binary"
"errors"
"fmt"
@ -22,8 +23,8 @@ const (
// MaxVersion is the maximumm blob version supported by this package.
MaxVersion = 0
// NonceSize is the length of a chacha20poly1305 nonce, 12 bytes.
NonceSize = chacha20poly1305.NonceSize
// NonceSize is the length of a chacha20poly1305 nonce, 24 bytes.
NonceSize = chacha20poly1305.NonceSizeX
// KeySize is the length of a chacha20poly1305 key, 32 bytes.
KeySize = chacha20poly1305.KeySize
@ -49,10 +50,11 @@ const (
)
// Size returns the size of the encoded-and-encrypted blob in bytes.
// enciphered plaintext: n bytes
// MAC: 16 bytes
// nonce: 24 bytes
// enciphered plaintext: n bytes
// MAC: 16 bytes
func Size(ver uint16) int {
return PlaintextSize(ver) + CiphertextExpansion
return NonceSize + PlaintextSize(ver) + CiphertextExpansion
}
// PlaintextSize returns the size of the encoded-but-unencrypted blob in bytes.
@ -79,11 +81,6 @@ var (
"ciphertext is too small for chacha20poly1305",
)
// ErrNonceSize signals that the provided nonce is improperly sized.
ErrNonceSize = fmt.Errorf(
"chacha20poly1305 nonce must be %d bytes", NonceSize,
)
// ErrKeySize signals that the provided key is improperly sized.
ErrKeySize = fmt.Errorf(
"chacha20poly1305 key size must be %d bytes", KeySize,
@ -232,15 +229,9 @@ func (b *JusticeKit) CommitToRemoteWitnessStack() ([][]byte, error) {
//
// NOTE: It is the caller's responsibility to ensure that this method is only
// called once for a given (nonce, key) pair.
func (b *JusticeKit) Encrypt(nonce, key []byte, version uint16) ([]byte, error) {
switch {
// Fail if the nonce is not 12-bytes.
case len(nonce) != NonceSize:
return nil, ErrNonceSize
func (b *JusticeKit) Encrypt(key []byte, version uint16) ([]byte, error) {
// Fail if the nonce is not 32-bytes.
case len(key) != KeySize:
if len(key) != KeySize {
return nil, ErrKeySize
}
@ -253,19 +244,25 @@ func (b *JusticeKit) Encrypt(nonce, key []byte, version uint16) ([]byte, error)
}
// Create a new chacha20poly1305 cipher, using a 32-byte key.
cipher, err := chacha20poly1305.New(key)
cipher, err := chacha20poly1305.NewX(key)
if err != nil {
return nil, err
}
// Allocate the ciphertext, which will contain the encrypted plaintext
// and MAC.
// Allocate the ciphertext, which will contain the nonce, encrypted
// plaintext and MAC.
plaintext := ptxtBuf.Bytes()
ciphertext := make([]byte, len(plaintext)+CiphertextExpansion)
ciphertext := make([]byte, Size(version))
// Generate a random 24-byte nonce in the ciphertext's prefix.
nonce := ciphertext[:NonceSize]
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return nil, err
}
// Finally, encrypt the plaintext using the given nonce, storing the
// result in the ciphertext buffer.
cipher.Seal(ciphertext[:0], nonce, plaintext, nil)
cipher.Seal(ciphertext[NonceSize:NonceSize], nonce, plaintext, nil)
return ciphertext, nil
}
@ -273,24 +270,21 @@ func (b *JusticeKit) Encrypt(nonce, key []byte, version uint16) ([]byte, error)
// Decrypt unenciphers a blob of justice by decrypting the ciphertext using
// chacha20poly1305 with the chosen (nonce, key) pair. The internal plaintext is
// then deserialized using the given encoding version.
func Decrypt(nonce, key, ciphertext []byte, version uint16) (*JusticeKit, error) {
func Decrypt(key, ciphertext []byte, version uint16) (*JusticeKit, error) {
switch {
// Fail if the blob's overall length is less than the expansion factor.
case len(ciphertext) < CiphertextExpansion:
// Fail if the blob's overall length is less than required for the nonce
// and expansion factor.
case len(ciphertext) < NonceSize+CiphertextExpansion:
return nil, ErrCiphertextTooSmall
// Fail if the nonce is not 12-bytes.
case len(nonce) != NonceSize:
return nil, ErrNonceSize
// Fail if the key is not 32-bytes.
case len(key) != KeySize:
return nil, ErrKeySize
}
// Create a new chacha20poly1305 cipher, using a 32-byte key.
cipher, err := chacha20poly1305.New(key)
cipher, err := chacha20poly1305.NewX(key)
if err != nil {
return nil, err
}
@ -302,7 +296,8 @@ func Decrypt(nonce, key, ciphertext []byte, version uint16) (*JusticeKit, error)
// Decrypt the ciphertext, placing the resulting plaintext in our
// plaintext buffer.
_, err = cipher.Open(plaintext[:0], nonce, ciphertext, nil)
nonce := ciphertext[:NonceSize]
_, err = cipher.Open(plaintext[:0], nonce, ciphertext[NonceSize:], nil)
if err != nil {
return nil, err
}