github.com/pion/dtls/v2@v2.2.12/pkg/crypto/ccm/ccm.go (about) 1 // SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly> 2 // SPDX-License-Identifier: MIT 3 4 // Package ccm implements a CCM, Counter with CBC-MAC 5 // as per RFC 3610. 6 // 7 // See https://tools.ietf.org/html/rfc3610 8 // 9 // This code was lifted from https://github.com/bocajim/dtls/blob/a3300364a283fcb490d28a93d7fcfa7ba437fbbe/ccm/ccm.go 10 // and as such was not written by the Pions authors. Like Pions this 11 // code is licensed under MIT. 12 // 13 // A request for including CCM into the Go standard library 14 // can be found as issue #27484 on the https://github.com/golang/go/ 15 // repository. 16 package ccm 17 18 import ( 19 "crypto/cipher" 20 "crypto/subtle" 21 "encoding/binary" 22 "errors" 23 "math" 24 ) 25 26 // ccm represents a Counter with CBC-MAC with a specific key. 27 type ccm struct { 28 b cipher.Block 29 M uint8 30 L uint8 31 } 32 33 const ccmBlockSize = 16 34 35 // CCM is a block cipher in Counter with CBC-MAC mode. 36 // Providing authenticated encryption with associated data via the cipher.AEAD interface. 37 type CCM interface { 38 cipher.AEAD 39 // MaxLength returns the maxium length of plaintext in calls to Seal. 40 // The maximum length of ciphertext in calls to Open is MaxLength()+Overhead(). 41 // The maximum length is related to CCM's `L` parameter (15-noncesize) and 42 // is 1<<(8*L) - 1 (but also limited by the maxium size of an int). 43 MaxLength() int 44 } 45 46 var ( 47 errInvalidBlockSize = errors.New("ccm: NewCCM requires 128-bit block cipher") 48 errInvalidTagSize = errors.New("ccm: tagsize must be 4, 6, 8, 10, 12, 14, or 16") 49 errInvalidNonceSize = errors.New("ccm: invalid nonce size") 50 ) 51 52 // NewCCM returns the given 128-bit block cipher wrapped in CCM. 53 // The tagsize must be an even integer between 4 and 16 inclusive 54 // and is used as CCM's `M` parameter. 55 // The noncesize must be an integer between 7 and 13 inclusive, 56 // 15-noncesize is used as CCM's `L` parameter. 57 func NewCCM(b cipher.Block, tagsize, noncesize int) (CCM, error) { 58 if b.BlockSize() != ccmBlockSize { 59 return nil, errInvalidBlockSize 60 } 61 if tagsize < 4 || tagsize > 16 || tagsize&1 != 0 { 62 return nil, errInvalidTagSize 63 } 64 lensize := 15 - noncesize 65 if lensize < 2 || lensize > 8 { 66 return nil, errInvalidNonceSize 67 } 68 c := &ccm{b: b, M: uint8(tagsize), L: uint8(lensize)} 69 return c, nil 70 } 71 72 func (c *ccm) NonceSize() int { return 15 - int(c.L) } 73 func (c *ccm) Overhead() int { return int(c.M) } 74 func (c *ccm) MaxLength() int { return maxlen(c.L, c.Overhead()) } 75 76 func maxlen(l uint8, tagsize int) int { 77 max := (uint64(1) << (8 * l)) - 1 78 if m64 := uint64(math.MaxInt64) - uint64(tagsize); l > 8 || max > m64 { 79 max = m64 // The maximum lentgh on a 64bit arch 80 } 81 if max != uint64(int(max)) { 82 return math.MaxInt32 - tagsize // We have only 32bit int's 83 } 84 return int(max) 85 } 86 87 // MaxNonceLength returns the maximum nonce length for a given plaintext length. 88 // A return value <= 0 indicates that plaintext length is too large for 89 // any nonce length. 90 func MaxNonceLength(pdatalen int) int { 91 const tagsize = 16 92 for L := 2; L <= 8; L++ { 93 if maxlen(uint8(L), tagsize) >= pdatalen { 94 return 15 - L 95 } 96 } 97 return 0 98 } 99 100 func (c *ccm) cbcRound(mac, data []byte) { 101 for i := 0; i < ccmBlockSize; i++ { 102 mac[i] ^= data[i] 103 } 104 c.b.Encrypt(mac, mac) 105 } 106 107 func (c *ccm) cbcData(mac, data []byte) { 108 for len(data) >= ccmBlockSize { 109 c.cbcRound(mac, data[:ccmBlockSize]) 110 data = data[ccmBlockSize:] 111 } 112 if len(data) > 0 { 113 var block [ccmBlockSize]byte 114 copy(block[:], data) 115 c.cbcRound(mac, block[:]) 116 } 117 } 118 119 var errPlaintextTooLong = errors.New("ccm: plaintext too large") 120 121 func (c *ccm) tag(nonce, plaintext, adata []byte) ([]byte, error) { 122 var mac [ccmBlockSize]byte 123 124 if len(adata) > 0 { 125 mac[0] |= 1 << 6 126 } 127 mac[0] |= (c.M - 2) << 2 128 mac[0] |= c.L - 1 129 if len(nonce) != c.NonceSize() { 130 return nil, errInvalidNonceSize 131 } 132 if len(plaintext) > c.MaxLength() { 133 return nil, errPlaintextTooLong 134 } 135 binary.BigEndian.PutUint64(mac[ccmBlockSize-8:], uint64(len(plaintext))) 136 copy(mac[1:ccmBlockSize-c.L], nonce) 137 c.b.Encrypt(mac[:], mac[:]) 138 139 var block [ccmBlockSize]byte 140 if n := uint64(len(adata)); n > 0 { 141 // First adata block includes adata length 142 i := 2 143 if n <= 0xfeff { 144 binary.BigEndian.PutUint16(block[:i], uint16(n)) 145 } else { 146 block[0] = 0xfe 147 block[1] = 0xff 148 if n < uint64(1<<32) { 149 i = 2 + 4 150 binary.BigEndian.PutUint32(block[2:i], uint32(n)) 151 } else { 152 i = 2 + 8 153 binary.BigEndian.PutUint64(block[2:i], n) 154 } 155 } 156 i = copy(block[i:], adata) 157 c.cbcRound(mac[:], block[:]) 158 c.cbcData(mac[:], adata[i:]) 159 } 160 161 if len(plaintext) > 0 { 162 c.cbcData(mac[:], plaintext) 163 } 164 165 return mac[:c.M], nil 166 } 167 168 // sliceForAppend takes a slice and a requested number of bytes. It returns a 169 // slice with the contents of the given slice followed by that many bytes and a 170 // second slice that aliases into it and contains only the extra bytes. If the 171 // original slice has sufficient capacity then no allocation is performed. 172 // From crypto/cipher/gcm.go 173 func sliceForAppend(in []byte, n int) (head, tail []byte) { 174 if total := len(in) + n; cap(in) >= total { 175 head = in[:total] 176 } else { 177 head = make([]byte, total) 178 copy(head, in) 179 } 180 tail = head[len(in):] 181 return 182 } 183 184 // Seal encrypts and authenticates plaintext, authenticates the 185 // additional data and appends the result to dst, returning the updated 186 // slice. The nonce must be NonceSize() bytes long and unique for all 187 // time, for a given key. 188 // The plaintext must be no longer than MaxLength() bytes long. 189 // 190 // The plaintext and dst may alias exactly or not at all. 191 func (c *ccm) Seal(dst, nonce, plaintext, adata []byte) []byte { 192 tag, err := c.tag(nonce, plaintext, adata) 193 if err != nil { 194 // The cipher.AEAD interface doesn't allow for an error return. 195 panic(err) // nolint 196 } 197 198 var iv, s0 [ccmBlockSize]byte 199 iv[0] = c.L - 1 200 copy(iv[1:ccmBlockSize-c.L], nonce) 201 c.b.Encrypt(s0[:], iv[:]) 202 for i := 0; i < int(c.M); i++ { 203 tag[i] ^= s0[i] 204 } 205 iv[len(iv)-1] |= 1 206 stream := cipher.NewCTR(c.b, iv[:]) 207 ret, out := sliceForAppend(dst, len(plaintext)+int(c.M)) 208 stream.XORKeyStream(out, plaintext) 209 copy(out[len(plaintext):], tag) 210 return ret 211 } 212 213 var ( 214 errOpen = errors.New("ccm: message authentication failed") 215 errCiphertextTooShort = errors.New("ccm: ciphertext too short") 216 errCiphertextTooLong = errors.New("ccm: ciphertext too long") 217 ) 218 219 func (c *ccm) Open(dst, nonce, ciphertext, adata []byte) ([]byte, error) { 220 if len(ciphertext) < int(c.M) { 221 return nil, errCiphertextTooShort 222 } 223 if len(ciphertext) > c.MaxLength()+c.Overhead() { 224 return nil, errCiphertextTooLong 225 } 226 227 tag := make([]byte, int(c.M)) 228 copy(tag, ciphertext[len(ciphertext)-int(c.M):]) 229 ciphertextWithoutTag := ciphertext[:len(ciphertext)-int(c.M)] 230 231 var iv, s0 [ccmBlockSize]byte 232 iv[0] = c.L - 1 233 copy(iv[1:ccmBlockSize-c.L], nonce) 234 c.b.Encrypt(s0[:], iv[:]) 235 for i := 0; i < int(c.M); i++ { 236 tag[i] ^= s0[i] 237 } 238 iv[len(iv)-1] |= 1 239 stream := cipher.NewCTR(c.b, iv[:]) 240 241 // Cannot decrypt directly to dst since we're not supposed to 242 // reveal the plaintext to the caller if authentication fails. 243 plaintext := make([]byte, len(ciphertextWithoutTag)) 244 stream.XORKeyStream(plaintext, ciphertextWithoutTag) 245 expectedTag, err := c.tag(nonce, plaintext, adata) 246 if err != nil { 247 return nil, err 248 } 249 250 if subtle.ConstantTimeCompare(tag, expectedTag) != 1 { 251 return nil, errOpen 252 } 253 return append(dst, plaintext...), nil 254 }