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