github.com/emmansun/gmsm@v0.29.1/cipher/ccm.go (about) 1 // Package cipher provides several extra chipher modes. 2 package cipher 3 4 import ( 5 goCipher "crypto/cipher" 6 goSubtle "crypto/subtle" 7 "encoding/binary" 8 "math" 9 10 "errors" 11 12 "github.com/emmansun/gmsm/internal/alias" 13 "github.com/emmansun/gmsm/internal/subtle" 14 ) 15 16 const ( 17 ccmBlockSize = 16 18 ccmTagSize = 16 19 ccmMinimumTagSize = 4 20 ccmStandardNonceSize = 12 21 ) 22 23 // ccmAble is an interface implemented by ciphers that have a specific optimized 24 // implementation of CCM. 25 type ccmAble interface { 26 NewCCM(nonceSize, tagSize int) (goCipher.AEAD, error) 27 } 28 29 type ccm struct { 30 cipher goCipher.Block 31 nonceSize int 32 tagSize int 33 } 34 35 func (c *ccm) NonceSize() int { 36 return c.nonceSize 37 } 38 39 func (c *ccm) Overhead() int { 40 return c.tagSize 41 } 42 43 func (c *ccm) MaxLength() int { 44 return maxlen(15-c.NonceSize(), c.Overhead()) 45 } 46 47 func maxlen(L, tagsize int) int { 48 max := (uint64(1) << (8 * L)) - 1 49 if m64 := uint64(math.MaxInt64) - uint64(tagsize); L > 8 || max > m64 { 50 max = m64 // The maximum lentgh on a 64bit arch 51 } 52 if max != uint64(int(max)) { 53 return math.MaxInt32 - tagsize // We have only 32bit int's 54 } 55 return int(max) 56 } 57 58 // NewCCM returns the given 128-bit, block cipher wrapped in CCM 59 // with the standard nonce length. 60 func NewCCM(cipher goCipher.Block) (goCipher.AEAD, error) { 61 return NewCCMWithNonceAndTagSize(cipher, ccmStandardNonceSize, ccmTagSize) 62 } 63 64 // NewCCMWithNonceSize returns the given 128-bit, block cipher wrapped in CCM, 65 // which accepts nonces of the given length. The length must not 66 // be zero. 67 func NewCCMWithNonceSize(cipher goCipher.Block, size int) (goCipher.AEAD, error) { 68 return NewCCMWithNonceAndTagSize(cipher, size, ccmTagSize) 69 } 70 71 // NewCCMWithTagSize returns the given 128-bit, block cipher wrapped in CCM, 72 // which generates tags with the given length. 73 // 74 // Tag sizes between 8 and 16 bytes are allowed. 75 // 76 func NewCCMWithTagSize(cipher goCipher.Block, tagSize int) (goCipher.AEAD, error) { 77 return NewCCMWithNonceAndTagSize(cipher, ccmStandardNonceSize, tagSize) 78 } 79 80 // https://tools.ietf.org/html/rfc3610 81 func NewCCMWithNonceAndTagSize(cipher goCipher.Block, nonceSize, tagSize int) (goCipher.AEAD, error) { 82 if tagSize < ccmMinimumTagSize || tagSize > ccmBlockSize || tagSize&1 != 0 { 83 return nil, errors.New("cipher: incorrect tag size given to CCM") 84 } 85 86 if nonceSize <= 0 { 87 return nil, errors.New("cipher: the nonce can't have zero length, or the security of the key will be immediately compromised") 88 } 89 90 lenSize := 15 - nonceSize 91 if lenSize < 2 || lenSize > 8 { 92 return nil, errors.New("cipher: invalid ccm nounce size, should be in [7,13]") 93 } 94 95 if cipher, ok := cipher.(ccmAble); ok { 96 return cipher.NewCCM(nonceSize, tagSize) 97 } 98 99 if cipher.BlockSize() != ccmBlockSize { 100 return nil, errors.New("cipher: NewCCM requires 128-bit block cipher") 101 } 102 103 c := &ccm{cipher: cipher, nonceSize: nonceSize, tagSize: tagSize} 104 105 return c, nil 106 } 107 108 // https://tools.ietf.org/html/rfc3610 109 func (c *ccm) deriveCounter(counter *[ccmBlockSize]byte, nonce []byte) { 110 counter[0] = byte(14 - c.nonceSize) 111 copy(counter[1:], nonce) 112 } 113 114 func (c *ccm) cmac(out, data []byte) { 115 for len(data) >= ccmBlockSize { 116 subtle.XORBytes(out, out, data) 117 c.cipher.Encrypt(out, out) 118 data = data[ccmBlockSize:] 119 } 120 if len(data) > 0 { 121 var block [ccmBlockSize]byte 122 copy(block[:], data) 123 subtle.XORBytes(out, out, data) 124 c.cipher.Encrypt(out, out) 125 } 126 } 127 128 // https://tools.ietf.org/html/rfc3610 2.2. Authentication 129 func (c *ccm) auth(nonce, plaintext, additionalData []byte, tagMask *[ccmBlockSize]byte) []byte { 130 var out [ccmTagSize]byte 131 if len(additionalData) > 0 { 132 out[0] = 1 << 6 // 64*Adata 133 } 134 out[0] |= byte(c.tagSize-2) << 2 // M' = ((tagSize - 2) / 2)*8 135 out[0] |= byte(14 - c.nonceSize) // L' 136 binary.BigEndian.PutUint64(out[ccmBlockSize-8:], uint64(len(plaintext))) 137 copy(out[1:], nonce) 138 // B0 139 c.cipher.Encrypt(out[:], out[:]) 140 141 var block [ccmBlockSize]byte 142 if n := uint64(len(additionalData)); n > 0 { 143 // First adata block includes adata length 144 i := 2 145 if n <= 0xfeff { // l(a) < (2^16 - 2^8) 146 binary.BigEndian.PutUint16(block[:i], uint16(n)) 147 } else { 148 block[0] = 0xff 149 // If (2^16 - 2^8) <= l(a) < 2^32, then the length field is encoded as 150 // six octets consisting of the octets 0xff, 0xfe, and four octets 151 // encoding l(a) in most-significant-byte-first order. 152 if n < uint64(1<<32) { 153 block[1] = 0xfe 154 i = 2 + 4 155 binary.BigEndian.PutUint32(block[2:i], uint32(n)) 156 } else { 157 block[1] = 0xff 158 // If 2^32 <= l(a) < 2^64, then the length field is encoded as ten 159 // octets consisting of the octets 0xff, 0xff, and eight octets encoding 160 // l(a) in most-significant-byte-first order. 161 i = 2 + 8 162 binary.BigEndian.PutUint64(block[2:i], uint64(n)) 163 } 164 } 165 i = copy(block[i:], additionalData) // first block start with additional data length 166 c.cmac(out[:], block[:]) 167 c.cmac(out[:], additionalData[i:]) 168 } 169 if len(plaintext) > 0 { 170 c.cmac(out[:], plaintext) 171 } 172 subtle.XORBytes(out[:], out[:], tagMask[:]) 173 return out[:c.tagSize] 174 } 175 176 func (c *ccm) Seal(dst, nonce, plaintext, data []byte) []byte { 177 if len(nonce) != c.nonceSize { 178 panic("cipher: incorrect nonce length given to CCM") 179 } 180 if uint64(len(plaintext)) > uint64(c.MaxLength()) { 181 panic("cipher: message too large for CCM") 182 } 183 ret, out := alias.SliceForAppend(dst, len(plaintext)+c.tagSize) 184 if alias.InexactOverlap(out, plaintext) { 185 panic("cipher: invalid buffer overlap") 186 } 187 188 var counter, tagMask [ccmBlockSize]byte 189 c.deriveCounter(&counter, nonce) 190 c.cipher.Encrypt(tagMask[:], counter[:]) 191 192 counter[len(counter)-1] |= 1 193 ctr := goCipher.NewCTR(c.cipher, counter[:]) 194 ctr.XORKeyStream(out, plaintext) 195 196 tag := c.auth(nonce, plaintext, data, &tagMask) 197 copy(out[len(plaintext):], tag) 198 199 return ret 200 } 201 202 var errOpen = errors.New("cipher: message authentication failed") 203 204 func (c *ccm) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) { 205 if len(nonce) != c.nonceSize { 206 panic("cipher: incorrect nonce length given to CCM") 207 } 208 // Sanity check to prevent the authentication from always succeeding if an implementation 209 // leaves tagSize uninitialized, for example. 210 if c.tagSize < ccmMinimumTagSize { 211 panic("cipher: incorrect CCM tag size") 212 } 213 214 if len(ciphertext) < c.tagSize { 215 return nil, errOpen 216 } 217 218 if len(ciphertext) > c.MaxLength()+c.Overhead() { 219 return nil, errOpen 220 } 221 222 tag := ciphertext[len(ciphertext)-c.tagSize:] 223 ciphertext = ciphertext[:len(ciphertext)-c.tagSize] 224 225 var counter, tagMask [ccmBlockSize]byte 226 c.deriveCounter(&counter, nonce) 227 c.cipher.Encrypt(tagMask[:], counter[:]) 228 229 ret, out := alias.SliceForAppend(dst, len(ciphertext)) 230 if alias.InexactOverlap(out, ciphertext) { 231 panic("cipher: invalid buffer overlap") 232 } 233 234 counter[len(counter)-1] |= 1 235 ctr := goCipher.NewCTR(c.cipher, counter[:]) 236 ctr.XORKeyStream(out, ciphertext) 237 expectedTag := c.auth(nonce, out, data, &tagMask) 238 if goSubtle.ConstantTimeCompare(expectedTag, tag) != 1 { 239 // The AESNI code decrypts and authenticates concurrently, and 240 // so overwrites dst in the event of a tag mismatch. That 241 // behavior is mimicked here in order to be consistent across 242 // platforms. 243 for i := range out { 244 out[i] = 0 245 } 246 return nil, errOpen 247 } 248 return ret, nil 249 }