mirror of https://github.com/bjdgyc/anylink.git
252 lines
7.1 KiB
Go
252 lines
7.1 KiB
Go
// Package ccm implements a CCM, Counter with CBC-MAC
|
|
// as per RFC 3610.
|
|
//
|
|
// See https://tools.ietf.org/html/rfc3610
|
|
//
|
|
// This code was lifted from https://github.com/bocajim/dtls/blob/a3300364a283fcb490d28a93d7fcfa7ba437fbbe/ccm/ccm.go
|
|
// and as such was not written by the Pions authors. Like Pions this
|
|
// code is licensed under MIT.
|
|
//
|
|
// A request for including CCM into the Go standard library
|
|
// can be found as issue #27484 on the https://github.com/golang/go/
|
|
// repository.
|
|
package ccm
|
|
|
|
import (
|
|
"crypto/cipher"
|
|
"crypto/subtle"
|
|
"encoding/binary"
|
|
"errors"
|
|
"math"
|
|
)
|
|
|
|
// ccm represents a Counter with CBC-MAC with a specific key.
|
|
type ccm struct {
|
|
b cipher.Block
|
|
M uint8
|
|
L uint8
|
|
}
|
|
|
|
const ccmBlockSize = 16
|
|
|
|
// CCM is a block cipher in Counter with CBC-MAC mode.
|
|
// Providing authenticated encryption with associated data via the cipher.AEAD interface.
|
|
type CCM interface {
|
|
cipher.AEAD
|
|
// MaxLength returns the maxium length of plaintext in calls to Seal.
|
|
// The maximum length of ciphertext in calls to Open is MaxLength()+Overhead().
|
|
// The maximum length is related to CCM's `L` parameter (15-noncesize) and
|
|
// is 1<<(8*L) - 1 (but also limited by the maxium size of an int).
|
|
MaxLength() int
|
|
}
|
|
|
|
var (
|
|
errInvalidBlockSize = errors.New("ccm: NewCCM requires 128-bit block cipher")
|
|
errInvalidTagSize = errors.New("ccm: tagsize must be 4, 6, 8, 10, 12, 14, or 16")
|
|
errInvalidNonceSize = errors.New("ccm: invalid nonce size")
|
|
)
|
|
|
|
// NewCCM returns the given 128-bit block cipher wrapped in CCM.
|
|
// The tagsize must be an even integer between 4 and 16 inclusive
|
|
// and is used as CCM's `M` parameter.
|
|
// The noncesize must be an integer between 7 and 13 inclusive,
|
|
// 15-noncesize is used as CCM's `L` parameter.
|
|
func NewCCM(b cipher.Block, tagsize, noncesize int) (CCM, error) {
|
|
if b.BlockSize() != ccmBlockSize {
|
|
return nil, errInvalidBlockSize
|
|
}
|
|
if tagsize < 4 || tagsize > 16 || tagsize&1 != 0 {
|
|
return nil, errInvalidTagSize
|
|
}
|
|
lensize := 15 - noncesize
|
|
if lensize < 2 || lensize > 8 {
|
|
return nil, errInvalidNonceSize
|
|
}
|
|
c := &ccm{b: b, M: uint8(tagsize), L: uint8(lensize)}
|
|
return c, nil
|
|
}
|
|
|
|
func (c *ccm) NonceSize() int { return 15 - int(c.L) }
|
|
func (c *ccm) Overhead() int { return int(c.M) }
|
|
func (c *ccm) MaxLength() int { return maxlen(c.L, c.Overhead()) }
|
|
|
|
func maxlen(l uint8, tagsize int) int {
|
|
max := (uint64(1) << (8 * l)) - 1
|
|
if m64 := uint64(math.MaxInt64) - uint64(tagsize); l > 8 || max > m64 {
|
|
max = m64 // The maximum lentgh on a 64bit arch
|
|
}
|
|
if max != uint64(int(max)) {
|
|
return math.MaxInt32 - tagsize // We have only 32bit int's
|
|
}
|
|
return int(max)
|
|
}
|
|
|
|
// MaxNonceLength returns the maximum nonce length for a given plaintext length.
|
|
// A return value <= 0 indicates that plaintext length is too large for
|
|
// any nonce length.
|
|
func MaxNonceLength(pdatalen int) int {
|
|
const tagsize = 16
|
|
for L := 2; L <= 8; L++ {
|
|
if maxlen(uint8(L), tagsize) >= pdatalen {
|
|
return 15 - L
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func (c *ccm) cbcRound(mac, data []byte) {
|
|
for i := 0; i < ccmBlockSize; i++ {
|
|
mac[i] ^= data[i]
|
|
}
|
|
c.b.Encrypt(mac, mac)
|
|
}
|
|
|
|
func (c *ccm) cbcData(mac, data []byte) {
|
|
for len(data) >= ccmBlockSize {
|
|
c.cbcRound(mac, data[:ccmBlockSize])
|
|
data = data[ccmBlockSize:]
|
|
}
|
|
if len(data) > 0 {
|
|
var block [ccmBlockSize]byte
|
|
copy(block[:], data)
|
|
c.cbcRound(mac, block[:])
|
|
}
|
|
}
|
|
|
|
var errPlaintextTooLong = errors.New("ccm: plaintext too large")
|
|
|
|
func (c *ccm) tag(nonce, plaintext, adata []byte) ([]byte, error) {
|
|
var mac [ccmBlockSize]byte
|
|
|
|
if len(adata) > 0 {
|
|
mac[0] |= 1 << 6
|
|
}
|
|
mac[0] |= (c.M - 2) << 2
|
|
mac[0] |= c.L - 1
|
|
if len(nonce) != c.NonceSize() {
|
|
return nil, errInvalidNonceSize
|
|
}
|
|
if len(plaintext) > c.MaxLength() {
|
|
return nil, errPlaintextTooLong
|
|
}
|
|
binary.BigEndian.PutUint64(mac[ccmBlockSize-8:], uint64(len(plaintext)))
|
|
copy(mac[1:ccmBlockSize-c.L], nonce)
|
|
c.b.Encrypt(mac[:], mac[:])
|
|
|
|
var block [ccmBlockSize]byte
|
|
if n := uint64(len(adata)); n > 0 {
|
|
// First adata block includes adata length
|
|
i := 2
|
|
if n <= 0xfeff {
|
|
binary.BigEndian.PutUint16(block[:i], uint16(n))
|
|
} else {
|
|
block[0] = 0xfe
|
|
block[1] = 0xff
|
|
if n < uint64(1<<32) {
|
|
i = 2 + 4
|
|
binary.BigEndian.PutUint32(block[2:i], uint32(n))
|
|
} else {
|
|
i = 2 + 8
|
|
binary.BigEndian.PutUint64(block[2:i], n)
|
|
}
|
|
}
|
|
i = copy(block[i:], adata)
|
|
c.cbcRound(mac[:], block[:])
|
|
c.cbcData(mac[:], adata[i:])
|
|
}
|
|
|
|
if len(plaintext) > 0 {
|
|
c.cbcData(mac[:], plaintext)
|
|
}
|
|
|
|
return mac[:c.M], nil
|
|
}
|
|
|
|
// 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.
|
|
// From crypto/cipher/gcm.go
|
|
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
|
|
}
|
|
|
|
// Seal encrypts and authenticates plaintext, authenticates the
|
|
// additional data and appends the result to dst, returning the updated
|
|
// slice. The nonce must be NonceSize() bytes long and unique for all
|
|
// time, for a given key.
|
|
// The plaintext must be no longer than MaxLength() bytes long.
|
|
//
|
|
// The plaintext and dst may alias exactly or not at all.
|
|
func (c *ccm) Seal(dst, nonce, plaintext, adata []byte) []byte {
|
|
tag, err := c.tag(nonce, plaintext, adata)
|
|
if err != nil {
|
|
// The cipher.AEAD interface doesn't allow for an error return.
|
|
panic(err) // nolint
|
|
}
|
|
|
|
var iv, s0 [ccmBlockSize]byte
|
|
iv[0] = c.L - 1
|
|
copy(iv[1:ccmBlockSize-c.L], nonce)
|
|
c.b.Encrypt(s0[:], iv[:])
|
|
for i := 0; i < int(c.M); i++ {
|
|
tag[i] ^= s0[i]
|
|
}
|
|
iv[len(iv)-1] |= 1
|
|
stream := cipher.NewCTR(c.b, iv[:])
|
|
ret, out := sliceForAppend(dst, len(plaintext)+int(c.M))
|
|
stream.XORKeyStream(out, plaintext)
|
|
copy(out[len(plaintext):], tag)
|
|
return ret
|
|
}
|
|
|
|
var (
|
|
errOpen = errors.New("ccm: message authentication failed")
|
|
errCiphertextTooShort = errors.New("ccm: ciphertext too short")
|
|
errCiphertextTooLong = errors.New("ccm: ciphertext too long")
|
|
)
|
|
|
|
func (c *ccm) Open(dst, nonce, ciphertext, adata []byte) ([]byte, error) {
|
|
if len(ciphertext) < int(c.M) {
|
|
return nil, errCiphertextTooShort
|
|
}
|
|
if len(ciphertext) > c.MaxLength()+c.Overhead() {
|
|
return nil, errCiphertextTooLong
|
|
}
|
|
|
|
tag := make([]byte, int(c.M))
|
|
copy(tag, ciphertext[len(ciphertext)-int(c.M):])
|
|
ciphertextWithoutTag := ciphertext[:len(ciphertext)-int(c.M)]
|
|
|
|
var iv, s0 [ccmBlockSize]byte
|
|
iv[0] = c.L - 1
|
|
copy(iv[1:ccmBlockSize-c.L], nonce)
|
|
c.b.Encrypt(s0[:], iv[:])
|
|
for i := 0; i < int(c.M); i++ {
|
|
tag[i] ^= s0[i]
|
|
}
|
|
iv[len(iv)-1] |= 1
|
|
stream := cipher.NewCTR(c.b, iv[:])
|
|
|
|
// Cannot decrypt directly to dst since we're not supposed to
|
|
// reveal the plaintext to the caller if authentication fails.
|
|
plaintext := make([]byte, len(ciphertextWithoutTag))
|
|
stream.XORKeyStream(plaintext, ciphertextWithoutTag)
|
|
expectedTag, err := c.tag(nonce, plaintext, adata)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if subtle.ConstantTimeCompare(tag, expectedTag) != 1 {
|
|
return nil, errOpen
|
|
}
|
|
return append(dst, plaintext...), nil
|
|
}
|