github.com/trustbloc/kms-go@v1.1.2/crypto/tinkcrypto/primitive/aead/subtle/gojose_aes_cbc_hmac_test.go (about) 1 /* 2 Copyright SecureKey Technologies Inc. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package subtle_test 8 9 import ( 10 "bytes" 11 "crypto/aes" 12 "crypto/cipher" 13 "fmt" 14 "testing" 15 16 josecipher "github.com/go-jose/go-jose/v3/cipher" 17 "github.com/stretchr/testify/require" 18 19 "github.com/trustbloc/kms-go/crypto/tinkcrypto/primitive/aead/subtle" 20 ) 21 22 func TestNewAESCBCHMAC(t *testing.T) { 23 key := make([]byte, 64) 24 25 // Test various key sizes. 26 for i := 0; i < 64; i++ { 27 k := key[:i] 28 keySize := len(k) 29 30 c, err := subtle.NewAESCBCHMAC(k) 31 32 switch keySize { 33 case 32, 48, 64: 34 // Valid key sizes. 35 require.NoError(t, err, "want: valid cipher (key size=%d), got: error %v", len(k), err) 36 37 // Verify that the struct contents are correctly set. 38 require.Equal(t, len(k), len(c.Key), "want: key size=%d, got: key size=%d", keySize, len(c.Key)) 39 default: 40 require.EqualError(t, err, fmt.Sprintf("aes_cbc_hmac: invalid AES CBC key size; want 32, 48 or 64, got %d", keySize)) 41 } 42 } 43 } 44 45 func TestIETFTestVector(t *testing.T) { 46 // Source: https://tools.ietf.org/html/draft-mcgrew-aead-aes-cbc-hmac-sha2-05#section-5 47 plaintext := []byte{ 48 0x41, 0x20, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x20, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x20, 49 0x6d, 0x75, 0x73, 0x74, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x62, 0x65, 0x20, 0x72, 0x65, 0x71, 0x75, 50 0x69, 0x72, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x62, 0x65, 0x20, 0x73, 0x65, 0x63, 0x72, 0x65, 51 0x74, 0x2c, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x69, 0x74, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x62, 52 0x65, 0x20, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x74, 0x6f, 0x20, 0x66, 0x61, 0x6c, 0x6c, 0x20, 0x69, 53 0x6e, 0x74, 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x68, 0x61, 0x6e, 0x64, 0x73, 0x20, 0x6f, 0x66, 54 0x20, 0x74, 0x68, 0x65, 0x20, 0x65, 0x6e, 0x65, 0x6d, 0x79, 0x20, 0x77, 0x69, 0x74, 0x68, 0x6f, 55 0x75, 0x74, 0x20, 0x69, 0x6e, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x6e, 0x69, 0x65, 0x6e, 0x63, 0x65, 56 } 57 58 aad := []byte{ 59 0x54, 0x68, 0x65, 0x20, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x20, 0x70, 0x72, 0x69, 0x6e, 0x63, 60 0x69, 0x70, 0x6c, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x41, 0x75, 0x67, 0x75, 0x73, 0x74, 0x65, 0x20, 61 0x4b, 0x65, 0x72, 0x63, 0x6b, 0x68, 0x6f, 0x66, 0x66, 0x73, 62 } 63 64 nonce := []byte{ 65 0x1a, 0xf3, 0x8c, 0x2d, 0xc2, 0xb9, 0x6f, 0xfd, 0xd8, 0x66, 0x94, 0x09, 0x23, 0x41, 0xbc, 0x04, 66 } 67 68 expectedCiphertext1 := []byte{ 69 0xc8, 0x0e, 0xdf, 0xa3, 0x2d, 0xdf, 0x39, 0xd5, 0xef, 0x00, 0xc0, 0xb4, 0x68, 0x83, 0x42, 0x79, 70 0xa2, 0xe4, 0x6a, 0x1b, 0x80, 0x49, 0xf7, 0x92, 0xf7, 0x6b, 0xfe, 0x54, 0xb9, 0x03, 0xa9, 0xc9, 71 0xa9, 0x4a, 0xc9, 0xb4, 0x7a, 0xd2, 0x65, 0x5c, 0x5f, 0x10, 0xf9, 0xae, 0xf7, 0x14, 0x27, 0xe2, 72 0xfc, 0x6f, 0x9b, 0x3f, 0x39, 0x9a, 0x22, 0x14, 0x89, 0xf1, 0x63, 0x62, 0xc7, 0x03, 0x23, 0x36, 73 0x09, 0xd4, 0x5a, 0xc6, 0x98, 0x64, 0xe3, 0x32, 0x1c, 0xf8, 0x29, 0x35, 0xac, 0x40, 0x96, 0xc8, 74 0x6e, 0x13, 0x33, 0x14, 0xc5, 0x40, 0x19, 0xe8, 0xca, 0x79, 0x80, 0xdf, 0xa4, 0xb9, 0xcf, 0x1b, 75 0x38, 0x4c, 0x48, 0x6f, 0x3a, 0x54, 0xc5, 0x10, 0x78, 0x15, 0x8e, 0xe5, 0xd7, 0x9d, 0xe5, 0x9f, 76 0xbd, 0x34, 0xd8, 0x48, 0xb3, 0xd6, 0x95, 0x50, 0xa6, 0x76, 0x46, 0x34, 0x44, 0x27, 0xad, 0xe5, 77 0x4b, 0x88, 0x51, 0xff, 0xb5, 0x98, 0xf7, 0xf8, 0x00, 0x74, 0xb9, 0x47, 0x3c, 0x82, 0xe2, 0xdb, 78 } 79 80 expectedAuthtag1 := []byte{ 81 0x65, 0x2c, 0x3f, 0xa3, 0x6b, 0x0a, 0x7c, 0x5b, 0x32, 0x19, 0xfa, 0xb3, 0xa3, 0x0b, 0xc1, 0xc4, 82 } 83 84 key1 := []byte{ 85 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 86 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 87 } 88 89 expectedCiphertext2 := []byte{ 90 0xea, 0x65, 0xda, 0x6b, 0x59, 0xe6, 0x1e, 0xdb, 0x41, 0x9b, 0xe6, 0x2d, 0x19, 0x71, 0x2a, 0xe5, 91 0xd3, 0x03, 0xee, 0xb5, 0x00, 0x52, 0xd0, 0xdf, 0xd6, 0x69, 0x7f, 0x77, 0x22, 0x4c, 0x8e, 0xdb, 92 0x00, 0x0d, 0x27, 0x9b, 0xdc, 0x14, 0xc1, 0x07, 0x26, 0x54, 0xbd, 0x30, 0x94, 0x42, 0x30, 0xc6, 93 0x57, 0xbe, 0xd4, 0xca, 0x0c, 0x9f, 0x4a, 0x84, 0x66, 0xf2, 0x2b, 0x22, 0x6d, 0x17, 0x46, 0x21, 94 0x4b, 0xf8, 0xcf, 0xc2, 0x40, 0x0a, 0xdd, 0x9f, 0x51, 0x26, 0xe4, 0x79, 0x66, 0x3f, 0xc9, 0x0b, 95 0x3b, 0xed, 0x78, 0x7a, 0x2f, 0x0f, 0xfc, 0xbf, 0x39, 0x04, 0xbe, 0x2a, 0x64, 0x1d, 0x5c, 0x21, 96 0x05, 0xbf, 0xe5, 0x91, 0xba, 0xe2, 0x3b, 0x1d, 0x74, 0x49, 0xe5, 0x32, 0xee, 0xf6, 0x0a, 0x9a, 97 0xc8, 0xbb, 0x6c, 0x6b, 0x01, 0xd3, 0x5d, 0x49, 0x78, 0x7b, 0xcd, 0x57, 0xef, 0x48, 0x49, 0x27, 98 0xf2, 0x80, 0xad, 0xc9, 0x1a, 0xc0, 0xc4, 0xe7, 0x9c, 0x7b, 0x11, 0xef, 0xc6, 0x00, 0x54, 0xe3, 99 } 100 101 expectedAuthtag2 := []byte{ 102 0x84, 0x90, 0xac, 0x0e, 0x58, 0x94, 0x9b, 0xfe, 0x51, 0x87, 0x5d, 0x73, 0x3f, 0x93, 0xac, 0x20, 103 0x75, 0x16, 0x80, 0x39, 0xcc, 0xc7, 0x33, 0xd7, 104 } 105 106 key2 := []byte{ 107 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 108 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 109 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 110 } 111 112 // Key3 is not 32, 48 or 64 in size and therefore not supported by go-jose. 113 // key3 := []byte{ 114 // 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 115 // 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 116 // 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 117 // 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37} 118 119 expectedCiphertext4 := []byte{ 120 0x4a, 0xff, 0xaa, 0xad, 0xb7, 0x8c, 0x31, 0xc5, 0xda, 0x4b, 0x1b, 0x59, 0x0d, 0x10, 0xff, 0xbd, 121 0x3d, 0xd8, 0xd5, 0xd3, 0x02, 0x42, 0x35, 0x26, 0x91, 0x2d, 0xa0, 0x37, 0xec, 0xbc, 0xc7, 0xbd, 122 0x82, 0x2c, 0x30, 0x1d, 0xd6, 0x7c, 0x37, 0x3b, 0xcc, 0xb5, 0x84, 0xad, 0x3e, 0x92, 0x79, 0xc2, 123 0xe6, 0xd1, 0x2a, 0x13, 0x74, 0xb7, 0x7f, 0x07, 0x75, 0x53, 0xdf, 0x82, 0x94, 0x10, 0x44, 0x6b, 124 0x36, 0xeb, 0xd9, 0x70, 0x66, 0x29, 0x6a, 0xe6, 0x42, 0x7e, 0xa7, 0x5c, 0x2e, 0x08, 0x46, 0xa1, 125 0x1a, 0x09, 0xcc, 0xf5, 0x37, 0x0d, 0xc8, 0x0b, 0xfe, 0xcb, 0xad, 0x28, 0xc7, 0x3f, 0x09, 0xb3, 126 0xa3, 0xb7, 0x5e, 0x66, 0x2a, 0x25, 0x94, 0x41, 0x0a, 0xe4, 0x96, 0xb2, 0xe2, 0xe6, 0x60, 0x9e, 127 0x31, 0xe6, 0xe0, 0x2c, 0xc8, 0x37, 0xf0, 0x53, 0xd2, 0x1f, 0x37, 0xff, 0x4f, 0x51, 0x95, 0x0b, 128 0xbe, 0x26, 0x38, 0xd0, 0x9d, 0xd7, 0xa4, 0x93, 0x09, 0x30, 0x80, 0x6d, 0x07, 0x03, 0xb1, 0xf6, 129 } 130 131 expectedAuthtag4 := []byte{ 132 0x4d, 0xd3, 0xb4, 0xc0, 0x88, 0xa7, 0xf4, 0x5c, 0x21, 0x68, 0x39, 0x64, 0x5b, 0x20, 0x12, 0xbf, 133 0x2e, 0x62, 0x69, 0xa8, 0xc5, 0x6a, 0x81, 0x6d, 0xbc, 0x1b, 0x26, 0x77, 0x61, 0x95, 0x5b, 0xc5, 134 } 135 136 key4 := []byte{ 137 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 138 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 139 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 140 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 141 } 142 143 tests := []struct { 144 name string 145 plaintext []byte 146 aad []byte 147 expectedCiphertext []byte 148 expectedAuthtag []byte 149 key []byte 150 nonce []byte 151 }{ 152 { 153 name: "AEAD_AES_128_CBC_HMAC_SHA256", 154 plaintext: plaintext, 155 aad: aad, 156 expectedCiphertext: expectedCiphertext1, 157 expectedAuthtag: expectedAuthtag1, 158 key: key1, 159 nonce: nonce, 160 }, 161 { 162 name: "AEAD_AES_192_CBC_HMAC_SHA384", 163 plaintext: plaintext, 164 aad: aad, 165 expectedCiphertext: expectedCiphertext2, 166 expectedAuthtag: expectedAuthtag2, 167 key: key2, 168 nonce: nonce, 169 }, 170 // { 171 // name: "AEAD_AES_256_CBC_HMAC_SHA384", 172 // plaintext: plaintext, 173 // aad: aad, 174 // expectedCiphertext: expectedCiphertext3, 175 // expectedAuthtag: expectedAuthtag3, 176 // Key3 is not supported by Go-Jose (key length=56 not supported). This is why this test is commented out. 177 // key: key3, 178 // nonce: nonce, 179 // }, 180 { 181 name: "AEAD_AES_256_CBC_HMAC_SHA512", 182 plaintext: plaintext, 183 aad: aad, 184 expectedCiphertext: expectedCiphertext4, 185 expectedAuthtag: expectedAuthtag4, 186 key: key4, 187 nonce: nonce, 188 }, 189 } 190 191 t.Parallel() 192 193 for _, test := range tests { 194 tc := test 195 t.Run(tc.name, func(t *testing.T) { 196 cbcHMAC, err := josecipher.NewCBCHMAC(tc.key, aes.NewCipher) 197 require.NoError(t, err) 198 199 enc := mockNONCEInCBCHMAC{ 200 nonce: nonce, 201 cbcHMAC: cbcHMAC, 202 } 203 204 out, err := enc.Encrypt(plaintext, aad) 205 require.NoError(t, err, "unable to encrypt") 206 207 tagSize := len(tc.expectedAuthtag) 208 209 ct := make([]byte, len(nonce)+len(tc.expectedCiphertext)+len(tc.expectedAuthtag)) 210 copy(ct, nonce) 211 copy(ct[len(nonce):], tc.expectedCiphertext) 212 copy(ct[len(nonce)+len(tc.expectedCiphertext):], tc.expectedAuthtag) 213 214 out1, err := enc.Decrypt(ct, aad) 215 require.NoError(t, err, "unable to decrypt") 216 217 require.EqualValues(t, plaintext, out1) 218 219 if !bytes.Equal(out[len(nonce):len(out)-tagSize], tc.expectedCiphertext) { 220 t.Error("Ciphertext did not match, got", out[len(nonce):len(out)-tagSize], "wanted", tc.expectedCiphertext) 221 } 222 223 if !bytes.Equal(out[len(out)-tagSize:], tc.expectedAuthtag) { 224 t.Error("Auth tag did not match, got", out[len(out)-tagSize:], "wanted", tc.expectedAuthtag) 225 } 226 }) 227 } 228 } 229 230 type mockNONCEInCBCHMAC struct { 231 subtle.AESCBCHMAC 232 233 cbcHMAC cipher.AEAD 234 nonce []byte 235 } 236 237 // Encrypt using the mocked nonce instead of generating a random one. 238 func (a *mockNONCEInCBCHMAC) Encrypt(plaintext, additionalData []byte) ([]byte, error) { 239 AESCBCIVSize := 16 240 241 ciphertext := a.cbcHMAC.Seal(nil, a.nonce, plaintext, additionalData) 242 243 ciphertextAndIV := make([]byte, AESCBCIVSize+len(ciphertext)) 244 if n := copy(ciphertextAndIV, a.nonce); n != AESCBCIVSize { 245 return nil, fmt.Errorf("aes_cbc_hmac: failed to copy IV (copied %d/%d bytes)", n, AESCBCIVSize) 246 } 247 248 copy(ciphertextAndIV[AESCBCIVSize:], ciphertext) 249 250 return ciphertextAndIV, nil 251 } 252 253 func (a *mockNONCEInCBCHMAC) Decrypt(ciphertext, additionalData []byte) ([]byte, error) { 254 ivSize := a.cbcHMAC.NonceSize() 255 if len(ciphertext) < ivSize { 256 return nil, fmt.Errorf("aes_cbc_hmac: ciphertext too short") 257 } 258 259 iv := ciphertext[:ivSize] 260 261 return a.cbcHMAC.Open(nil, iv, ciphertext[ivSize:], additionalData) 262 } 263 264 func TestAESCBCRoundtrip(t *testing.T) { 265 key128 := []byte{ 266 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 267 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 268 } 269 270 key192 := []byte{ 271 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 272 0, 1, 2, 3, 4, 5, 6, 7, 273 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 274 0, 1, 2, 3, 4, 5, 6, 7, 275 } 276 277 key256 := []byte{ 278 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 279 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 280 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 281 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 282 } 283 284 RunRoundtrip(t, key128) 285 RunRoundtrip(t, key192) 286 RunRoundtrip(t, key256) 287 } 288 289 func RunRoundtrip(t *testing.T, key []byte) { 290 aead, err := subtle.NewAESCBCHMAC(key) 291 require.NoError(t, err) 292 293 // Test pre-existing data in dst buffer 294 plaintext := []byte{0, 0, 0, 0} 295 aad := []byte{4, 3, 2, 1} 296 297 result, err := aead.Encrypt(plaintext, aad) 298 require.NoError(t, err) 299 300 result, err = aead.Decrypt(result, aad) 301 require.NoError(t, err) 302 require.EqualValues(t, plaintext, result, "Plaintext does not match output") 303 304 t.Run("failure: bad cipher", func(t *testing.T) { 305 result, err = aead.Decrypt([]byte("bad cipher"), aad) 306 require.EqualError(t, err, "aes_cbc_hmac: ciphertext too short") 307 }) 308 309 t.Run("failure: cipher not short but not large enough to contain an authentication tag", func(t *testing.T) { 310 result, err = aead.Decrypt([]byte("bad cipher with not too short length to cause decryption failure"), aad) 311 require.EqualError(t, err, "aes_cbc_hmac: failed to decrypt: go-jose/go-jose: invalid ciphertext "+ 312 "(auth tag mismatch)") 313 }) 314 }