// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

// 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 ( ) // 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 ( cipher.Block, , int) (CCM, error) { if .BlockSize() != ccmBlockSize { return nil, errInvalidBlockSize } if < 4 || > 16 || &1 != 0 { return nil, errInvalidTagSize } := 15 - if < 2 || > 8 { return nil, errInvalidNonceSize } := &ccm{b: , M: uint8(), L: uint8()} //nolint:gosec // G114 return , nil } func ( *ccm) () int { return 15 - int(.L) } func ( *ccm) () int { return int(.M) } func ( *ccm) () int { return maxlen(.L, .Overhead()) } func maxlen( uint8, int) int { := (uint64(1) << (8 * )) - 1 if := uint64(math.MaxInt64) - uint64(); > 8 || > { //nolint:gosec // G114 = // The maximum lentgh on a 64bit arch } if != uint64(int()) { //nolint:gosec // G114 return math.MaxInt32 - // We have only 32bit int's } return int() //nolint:gosec // G114 } // 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 ( int) int { const = 16 for := 2; <= 8; ++ { if maxlen(uint8(), ) >= { //nolint:gosec // G115 return 15 - } } return 0 } func ( *ccm) (, []byte) { for := 0; < ccmBlockSize; ++ { [] ^= [] } .b.Encrypt(, ) } func ( *ccm) (, []byte) { for len() >= ccmBlockSize { .cbcRound(, [:ccmBlockSize]) = [ccmBlockSize:] } if len() > 0 { var [ccmBlockSize]byte copy([:], ) .cbcRound(, [:]) } } var errPlaintextTooLong = errors.New("ccm: plaintext too large") func ( *ccm) (, , []byte) ([]byte, error) { var [ccmBlockSize]byte if len() > 0 { [0] |= 1 << 6 } [0] |= (.M - 2) << 2 [0] |= .L - 1 if len() != .NonceSize() { return nil, errInvalidNonceSize } if len() > .MaxLength() { return nil, errPlaintextTooLong } binary.BigEndian.PutUint64([ccmBlockSize-8:], uint64(len())) copy([1:ccmBlockSize-.L], ) .b.Encrypt([:], [:]) var [ccmBlockSize]byte if := uint64(len()); > 0 { //nolint:nestif // First adata block includes adata length := 2 if <= 0xfeff { binary.BigEndian.PutUint16([:], uint16()) } else { [0] = 0xfe [1] = 0xff if < uint64(1<<32) { = 2 + 4 binary.BigEndian.PutUint32([2:], uint32()) //nolint:gosec // G115 } else { = 2 + 8 binary.BigEndian.PutUint64([2:], ) } } = copy([:], ) .cbcRound([:], [:]) .cbcData([:], [:]) } if len() > 0 { .cbcData([:], ) } return [:.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( []byte, int) (, []byte) { if := len() + ; cap() >= { = [:] } else { = make([]byte, ) copy(, ) } = [len():] 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 ( *ccm) (, , , []byte) []byte { , := .tag(, , ) if != nil { // The cipher.AEAD interface doesn't allow for an error return. panic() // nolint } var , [ccmBlockSize]byte [0] = .L - 1 copy([1:ccmBlockSize-.L], ) .b.Encrypt([:], [:]) for := 0; < int(.M); ++ { [] ^= [] } [len()-1] |= 1 := cipher.NewCTR(.b, [:]) , := sliceForAppend(, len()+int(.M)) .XORKeyStream(, ) copy([len():], ) return } var ( errOpen = errors.New("ccm: message authentication failed") errCiphertextTooShort = errors.New("ccm: ciphertext too short") errCiphertextTooLong = errors.New("ccm: ciphertext too long") ) func ( *ccm) (, , , []byte) ([]byte, error) { if len() < int(.M) { return nil, errCiphertextTooShort } if len() > .MaxLength()+.Overhead() { return nil, errCiphertextTooLong } := make([]byte, int(.M)) copy(, [len()-int(.M):]) := [:len()-int(.M)] var , [ccmBlockSize]byte [0] = .L - 1 copy([1:ccmBlockSize-.L], ) .b.Encrypt([:], [:]) for := 0; < int(.M); ++ { [] ^= [] } [len()-1] |= 1 := cipher.NewCTR(.b, [:]) // Cannot decrypt directly to dst since we're not supposed to // reveal the plaintext to the caller if authentication fails. := make([]byte, len()) .XORKeyStream(, ) , := .tag(, , ) if != nil { return nil, } if subtle.ConstantTimeCompare(, ) != 1 { return nil, errOpen } return append(, ...), nil }