|  | // Copyright 2014 The Chromium Authors | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include <stddef.h> | 
|  | #include <stdint.h> | 
|  |  | 
|  | #include <array> | 
|  |  | 
|  | #include "base/test/gmock_expected_support.h" | 
|  | #include "base/types/expected.h" | 
|  | #include "components/webcrypto/algorithm_dispatch.h" | 
|  | #include "components/webcrypto/algorithms/test_helpers.h" | 
|  | #include "components/webcrypto/status.h" | 
|  | #include "testing/gtest/include/gtest/gtest.h" | 
|  | #include "third_party/blink/public/platform/web_crypto_algorithm_params.h" | 
|  | #include "third_party/blink/public/platform/web_crypto_key_algorithm.h" | 
|  |  | 
|  | namespace webcrypto { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | // Creates an AES-GCM algorithm. | 
|  | blink::WebCryptoAlgorithm CreateAesGcmAlgorithm( | 
|  | const std::vector<uint8_t>& iv, | 
|  | const std::vector<uint8_t>& additional_data, | 
|  | unsigned int tag_length_bits) { | 
|  | return blink::WebCryptoAlgorithm::AdoptParamsAndCreate( | 
|  | blink::kWebCryptoAlgorithmIdAesGcm, | 
|  | new blink::WebCryptoAesGcmParams(iv, true, additional_data, true, | 
|  | tag_length_bits)); | 
|  | } | 
|  |  | 
|  | blink::WebCryptoAlgorithm CreateAesGcmKeyGenAlgorithm( | 
|  | uint16_t key_length_bits) { | 
|  | return CreateAesKeyGenAlgorithm(blink::kWebCryptoAlgorithmIdAesGcm, | 
|  | key_length_bits); | 
|  | } | 
|  |  | 
|  | base::expected<std::vector<uint8_t>, Status> AesGcmEncrypt( | 
|  | const blink::WebCryptoKey& key, | 
|  | const std::vector<uint8_t>& iv, | 
|  | const std::vector<uint8_t>& additional_data, | 
|  | size_t tag_length_bits, | 
|  | const std::vector<uint8_t>& plain_text) { | 
|  | blink::WebCryptoAlgorithm algorithm = | 
|  | CreateAesGcmAlgorithm(iv, additional_data, tag_length_bits); | 
|  |  | 
|  | std::vector<uint8_t> output; | 
|  | Status status = Encrypt(algorithm, key, plain_text, &output); | 
|  | if (status.IsError()) { | 
|  | return base::unexpected(status); | 
|  | } | 
|  | return output; | 
|  | } | 
|  |  | 
|  | base::expected<std::vector<uint8_t>, Status> AesGcmDecrypt( | 
|  | const blink::WebCryptoKey& key, | 
|  | const std::vector<uint8_t>& iv, | 
|  | const std::vector<uint8_t>& additional_data, | 
|  | unsigned int tag_length_bits, | 
|  | const std::vector<uint8_t>& ciphertext) { | 
|  | blink::WebCryptoAlgorithm algorithm = | 
|  | CreateAesGcmAlgorithm(iv, additional_data, tag_length_bits); | 
|  |  | 
|  | std::vector<uint8_t> output; | 
|  | Status status = Decrypt(algorithm, key, ciphertext, &output); | 
|  | if (status.IsError()) { | 
|  | return base::unexpected(status); | 
|  | } | 
|  | return output; | 
|  | } | 
|  |  | 
|  | class WebCryptoAesGcmTest : public WebCryptoTestBase {}; | 
|  |  | 
|  | TEST_F(WebCryptoAesGcmTest, GenerateKeyBadLength) { | 
|  | auto generate_key = [](size_t len) { | 
|  | blink::WebCryptoKey key; | 
|  | return GenerateSecretKey(CreateAesGcmKeyGenAlgorithm(len), true, | 
|  | blink::kWebCryptoKeyUsageDecrypt, &key); | 
|  | }; | 
|  | EXPECT_EQ(generate_key(0), Status::ErrorGenerateAesKeyLength()); | 
|  | EXPECT_EQ(generate_key(127), Status::ErrorGenerateAesKeyLength()); | 
|  | EXPECT_EQ(generate_key(257), Status::ErrorGenerateAesKeyLength()); | 
|  | } | 
|  |  | 
|  | TEST_F(WebCryptoAesGcmTest, GenerateKeyEmptyUsage) { | 
|  | blink::WebCryptoKey key; | 
|  | EXPECT_EQ(Status::ErrorCreateKeyEmptyUsages(), | 
|  | GenerateSecretKey(CreateAesGcmKeyGenAlgorithm(256), true, 0, &key)); | 
|  | } | 
|  |  | 
|  | TEST_F(WebCryptoAesGcmTest, ImportExportJwk) { | 
|  | const blink::WebCryptoAlgorithm algorithm = | 
|  | CreateAlgorithm(blink::kWebCryptoAlgorithmIdAesGcm); | 
|  |  | 
|  | // AES-GCM 128 | 
|  | ImportExportJwkSymmetricKey( | 
|  | 128, algorithm, | 
|  | blink::kWebCryptoKeyUsageEncrypt | blink::kWebCryptoKeyUsageDecrypt, | 
|  | "A128GCM"); | 
|  |  | 
|  | // AES-GCM 256 | 
|  | ImportExportJwkSymmetricKey(256, algorithm, blink::kWebCryptoKeyUsageDecrypt, | 
|  | "A256GCM"); | 
|  | } | 
|  |  | 
|  | struct AesGcmKnownAnswer { | 
|  | const char* key; | 
|  | const char* iv; | 
|  | const char* plaintext; | 
|  | const char* additional; | 
|  | const char* ciphertext; | 
|  | size_t tagbits; | 
|  | }; | 
|  |  | 
|  | // NIST GCM test vectors: | 
|  | // http://csrc.nist.gov/groups/STM/cavp/documents/mac/gcmtestvectors.zip | 
|  | constexpr auto kAesGcmKnownAnswers = std::to_array<AesGcmKnownAnswer>({ | 
|  | {"cf063a34d4a9a76c2c86787d3f96db71", "113b9785971864c83b01c787", "", "", | 
|  | "72ac8493e3a5228b5d130a69d2510e42", 128}, | 
|  | {"6dfa1a07c14f978020ace450ad663d18", "34edfa462a14c6969a680ec1", "", | 
|  | "2a35c7f5f8578e919a581c60500c04f6", "751f3098d59cf4ea1d2fb0853bde1c", 120}, | 
|  | {"ed6cd876ceba555706674445c229c12d", "92ecbf74b765bc486383ca2e", | 
|  | "bfaaaea3880d72d4378561e2597a9b35", "95bd10d77dbe0e87fb34217f1a2e5efe", | 
|  | "bdd2ed6c66fa087dce617d7fd1ff6d93ba82e49c55a22ed02ca67da4ec6f", 112}, | 
|  | {"e03548984a7ec8eaf0870637df0ac6bc17f7159315d0ae26a764fd224e483810", | 
|  | "f4feb26b846be4cd224dbc5133a5ae13814ebe19d3032acdd3a006463fdb71e83a9d5d966" | 
|  | "79f26cc1719dd6b4feb3bab5b4b7993d0c0681f36d105ad3002fb66b201538e2b7479838a" | 
|  | "b83402b0d816cd6e0fe5857e6f4adf92de8ee72b122ba1ac81795024943b7d0151bbf84ce" | 
|  | "87c8911f512c397d14112296da7ecdd0da52a", | 
|  | "69fd0c9da10b56ec6786333f8d76d4b74f8a434195f2f241f088b2520fb5fa29455df9893" | 
|  | "164fb1638abe6617915d9497a8fe2", | 
|  | "aab26eb3e7acd09a034a9e2651636ab3868e51281590ecc948355e457da42b7ad1391c7be" | 
|  | "0d9e82895e506173a81857c3226829fbd6dfb3f9657a71a2934445d7c05fa9401cddd5109" | 
|  | "016ba32c3856afaadc48de80b8a01b57cb", | 
|  | "fda718aa1ec163487e21afc34f5a3a34795a9ee71dd3e7ee9a18fdb24181dc982b29c6ec7" | 
|  | "23294a130ca2234952bb0ef68c0f34795fbe0", | 
|  | 32}, | 
|  | }); | 
|  |  | 
|  | // TODO(eroman): | 
|  | //   * Test decryption when the tag length exceeds input size | 
|  | //   * Test decryption with empty input | 
|  | //   * Test decryption with tag length of 0. | 
|  | TEST_F(WebCryptoAesGcmTest, KnownAnswers) { | 
|  | // Note that WebCrypto appends the authentication tag to the ciphertext. | 
|  | for (const auto& test : kAesGcmKnownAnswers) { | 
|  | SCOPED_TRACE(&test - &kAesGcmKnownAnswers[0]); | 
|  |  | 
|  | auto key_bytes = HexStringToBytes(test.key); | 
|  | auto iv_bytes = HexStringToBytes(test.iv); | 
|  | auto plaintext_bytes = HexStringToBytes(test.plaintext); | 
|  | auto additional_bytes = HexStringToBytes(test.additional); | 
|  | auto ciphertext_bytes = HexStringToBytes(test.ciphertext); | 
|  |  | 
|  | blink::WebCryptoKey key = ImportSecretKeyFromRaw( | 
|  | key_bytes, CreateAlgorithm(blink::kWebCryptoAlgorithmIdAesGcm), | 
|  | blink::kWebCryptoKeyUsageEncrypt | blink::kWebCryptoKeyUsageDecrypt); | 
|  |  | 
|  | { | 
|  | std::vector<uint8_t> exported_key; | 
|  | EXPECT_EQ(Status::Success(), | 
|  | ExportKey(blink::kWebCryptoKeyFormatRaw, key, &exported_key)); | 
|  | EXPECT_BYTES_EQ(key_bytes, exported_key); | 
|  | } | 
|  |  | 
|  | ASSERT_OK_AND_ASSIGN(auto encrypt_result, | 
|  | AesGcmEncrypt(key, iv_bytes, additional_bytes, | 
|  | test.tagbits, plaintext_bytes)); | 
|  | EXPECT_BYTES_EQ(encrypt_result, ciphertext_bytes); | 
|  |  | 
|  | ASSERT_OK_AND_ASSIGN(auto decrypt_result, | 
|  | AesGcmDecrypt(key, iv_bytes, additional_bytes, | 
|  | test.tagbits, ciphertext_bytes)); | 
|  | EXPECT_BYTES_EQ(decrypt_result, plaintext_bytes); | 
|  |  | 
|  | // Decryption should fail if any of the inputs are tampered with. | 
|  | EXPECT_THAT(AesGcmDecrypt(key, Corrupted(iv_bytes), additional_bytes, | 
|  | test.tagbits, ciphertext_bytes), | 
|  | base::test::ErrorIs(Status::OperationError())); | 
|  | EXPECT_THAT(AesGcmDecrypt(key, iv_bytes, Corrupted(additional_bytes), | 
|  | test.tagbits, ciphertext_bytes), | 
|  | base::test::ErrorIs(Status::OperationError())); | 
|  | EXPECT_THAT(AesGcmDecrypt(key, iv_bytes, additional_bytes, test.tagbits, | 
|  | Corrupted(ciphertext_bytes)), | 
|  | base::test::ErrorIs(Status::OperationError())); | 
|  |  | 
|  | // Try different incorrect tag lengths | 
|  | for (unsigned int length : {0, 8, 96, 120, 128, 160, 255}) { | 
|  | if (test.tagbits == length) | 
|  | continue; | 
|  | EXPECT_FALSE(AesGcmDecrypt(key, iv_bytes, additional_bytes, length, | 
|  | ciphertext_bytes) | 
|  | .has_value()); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | }  // namespace webcrypto |