containerd/vendor/github.com/miscreant/miscreant-go/siv.go
Stefan Berger 30c3443947 Add crypto dependencies/vendoring
Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com>
2019-07-17 15:19:28 -04:00

244 lines
6.0 KiB
Go

// Originally written in 2015 by Dmitry Chestnykh.
// Modified in 2017 by Tony Arcieri.
//
// Miscreant implements Synthetic Initialization Vector (SIV)-based
// authenticated encryption using the AES block cipher (RFC 5297).
package miscreant
import (
"crypto/aes"
"crypto/cipher"
"crypto/subtle"
"errors"
"github.com/miscreant/miscreant-go/block"
"github.com/miscreant/miscreant-go/cmac"
"github.com/miscreant/miscreant-go/pmac"
"hash"
)
// MaxAssociatedDataItems is the maximum number of associated data items
const MaxAssociatedDataItems = 126
var (
// ErrKeySize indicates the given key size is not supported
ErrKeySize = errors.New("siv: bad key size")
// ErrNotAuthentic indicates a ciphertext is malformed or corrupt
ErrNotAuthentic = errors.New("siv: authentication failed")
// ErrTooManyAssociatedDataItems indicates more than MaxAssociatedDataItems were given
ErrTooManyAssociatedDataItems = errors.New("siv: too many associated data items")
)
// Cipher is an instance of AES-SIV, configured with either AES-CMAC or
// AES-PMAC as a message authentication code.
type Cipher struct {
// MAC function used to derive a synthetic IV and authenticate the message
h hash.Hash
// Block cipher function used to encrypt the message
b cipher.Block
// Internal buffers
tmp1, tmp2 block.Block
}
// NewAESCMACSIV returns a new AES-SIV cipher with the given key, which must be
// twice as long as an AES key, either 32 or 64 bytes to select AES-128
// (AES-CMAC-SIV-256), or AES-256 (AES-CMAC-SIV-512).
func NewAESCMACSIV(key []byte) (c *Cipher, err error) {
n := len(key)
if n != 32 && n != 64 {
return nil, ErrKeySize
}
macBlock, err := aes.NewCipher(key[:n/2])
if err != nil {
return nil, err
}
ctrBlock, err := aes.NewCipher(key[n/2:])
if err != nil {
return nil, err
}
c = new(Cipher)
c.h = cmac.New(macBlock)
c.b = ctrBlock
return c, nil
}
// NewAESPMACSIV returns a new AES-SIV cipher with the given key, which must be
// twice as long as an AES key, either 32 or 64 bytes to select AES-128
// (AES-PMAC-SIV-256), or AES-256 (AES-PMAC-SIV-512).
func NewAESPMACSIV(key []byte) (c *Cipher, err error) {
n := len(key)
if n != 32 && n != 64 {
return nil, ErrKeySize
}
macBlock, err := aes.NewCipher(key[:n/2])
if err != nil {
return nil, err
}
ctrBlock, err := aes.NewCipher(key[n/2:])
if err != nil {
return nil, err
}
c = new(Cipher)
c.h = pmac.New(macBlock)
c.b = ctrBlock
return c, nil
}
// Overhead returns the difference between plaintext and ciphertext lengths.
func (c *Cipher) Overhead() int {
return c.h.Size()
}
// Seal encrypts and authenticates plaintext, authenticates the given
// associated data items, and appends the result to dst, returning the updated
// slice.
//
// The plaintext and dst may alias exactly or not at all.
//
// For nonce-based encryption, the nonce should be the last associated data item.
func (c *Cipher) Seal(dst []byte, plaintext []byte, data ...[]byte) ([]byte, error) {
if len(data) > MaxAssociatedDataItems {
return nil, ErrTooManyAssociatedDataItems
}
// Authenticate
iv := c.s2v(data, plaintext)
ret, out := sliceForAppend(dst, len(iv)+len(plaintext))
copy(out, iv)
// Encrypt
zeroIVBits(iv)
ctr := cipher.NewCTR(c.b, iv)
ctr.XORKeyStream(out[len(iv):], plaintext)
return ret, nil
}
// Open decrypts ciphertext, authenticates the decrypted plaintext and the given
// associated data items and, if successful, appends the resulting plaintext
// to dst, returning the updated slice. The additional data items must match the
// items passed to Seal.
//
// The ciphertext and dst may alias exactly or not at all.
//
// For nonce-based encryption, the nonce should be the last associated data item.
func (c *Cipher) Open(dst []byte, ciphertext []byte, data ...[]byte) ([]byte, error) {
if len(data) > MaxAssociatedDataItems {
return nil, ErrTooManyAssociatedDataItems
}
if len(ciphertext) < c.Overhead() {
return nil, ErrNotAuthentic
}
// Decrypt
iv := c.tmp1[:c.Overhead()]
copy(iv, ciphertext)
zeroIVBits(iv)
ctr := cipher.NewCTR(c.b, iv)
ret, out := sliceForAppend(dst, len(ciphertext)-len(iv))
ctr.XORKeyStream(out, ciphertext[len(iv):])
// Authenticate
expected := c.s2v(data, out)
if subtle.ConstantTimeCompare(ciphertext[:len(iv)], expected) != 1 {
return nil, ErrNotAuthentic
}
return ret, nil
}
func (c *Cipher) s2v(s [][]byte, sn []byte) []byte {
h := c.h
h.Reset()
tmp, d := c.tmp1, c.tmp2
tmp.Clear()
// NOTE(dchest): The standalone S2V returns CMAC(1) if the number of
// passed vectors is zero, however in SIV construction this case is
// never triggered, since we always pass plaintext as the last vector
// (even if it's zero-length), so we omit this case.
_, err := h.Write(tmp[:])
if err != nil {
panic(err)
}
copy(d[:], h.Sum(d[:0]))
h.Reset()
for _, v := range s {
_, err := h.Write(v)
if err != nil {
panic(err)
}
copy(tmp[:], h.Sum(tmp[:0]))
h.Reset()
d.Dbl()
xor(d[:], tmp[:])
}
tmp.Clear()
if len(sn) >= h.BlockSize() {
n := len(sn) - len(d)
copy(tmp[:], sn[n:])
_, err = h.Write(sn[:n])
if err != nil {
panic(err)
}
} else {
copy(tmp[:], sn)
tmp[len(sn)] = 0x80
d.Dbl()
}
xor(tmp[:], d[:])
_, err = h.Write(tmp[:])
if err != nil {
panic(err)
}
return h.Sum(tmp[:0])
}
func xor(a, b []byte) {
for i, v := range b {
a[i] ^= v
}
}
func zeroIVBits(iv []byte) {
// "We zero-out the top bit in each of the last two 32-bit words
// of the IV before assigning it to Ctr"
// — http://web.cs.ucdavis.edu/~rogaway/papers/siv.pdf
iv[len(iv)-8] &= 0x7f
iv[len(iv)-4] &= 0x7f
}
// sliceForAppend takes a slice and a requested number of bytes. It returns a
// slice with the contents of the given slice followed by that many bytes and a
// second slice that aliases into it and contains only the extra bytes. If the
// original slice has sufficient capacity then no allocation is performed.
func sliceForAppend(in []byte, n int) (head, tail []byte) {
if total := len(in) + n; cap(in) >= total {
head = in[:total]
} else {
head = make([]byte, total)
copy(head, in)
}
tail = head[len(in):]
return
}