ccm.go 7.2 KB

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