| // 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 <string_view> |
| |
| #include "base/check_op.h" |
| #include "base/containers/span.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_util.h" |
| #include "base/values.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.h" |
| #include "third_party/blink/public/platform/web_crypto_key_algorithm.h" |
| |
| namespace webcrypto { |
| |
| namespace { |
| |
| // Helper for ImportJwkRsaFailures. Restores the JWK JSON |
| // dictionary to a good state |
| base::Value::Dict BuildTestJwkPublicKey() { |
| base::Value::Dict jwk; |
| jwk.Set("kty", "RSA"); |
| jwk.Set("alg", "RS256"); |
| jwk.Set("use", "sig"); |
| jwk.Set("ext", false); |
| jwk.Set( |
| "n", |
| "qLOyhK-OtQs4cDSoYPFGxJGfMYdjzWxVmMiuSBGh4KvEx-CwgtaTpef87Wdc9GaFEncsDLxk" |
| "p0LGxjD1M8jMcvYq6DPEC_JYQumEu3i9v5fAEH1VvbZi9cTg-rmEXLUUjvc5LdOq_5OuHmtm" |
| "e7PUJHYW1PW6ENTP0ibeiNOfFvs"); |
| jwk.Set("e", "AQAB"); |
| return jwk; |
| } |
| |
| base::Value::Dict BuildTestJwkPrivateKey() { |
| base::Value::Dict jwk; |
| jwk.Set("kty", "RSA"); |
| jwk.Set("d", |
| "ZmJJJ3PBfirgPEOb844fI_1_zXn3A09X9fkk-65xeTNo3JeigTPpuB54FC_" |
| "GXUmqiXLVx5gynO6cwl9wjxVKYQ"); |
| jwk.Set("dp", "DOUuUiDhtjpnCuIjcGRWhQYok8NeUO5XV1Uwx1-DxtU"); |
| jwk.Set("dq", "mKOBL1e74J8OuGtW1kc2-s4VEP5Eeiwe__TAeBm-roE"); |
| jwk.Set("e", "AQAB"); |
| jwk.Set("n", |
| "tFJAFt_UiJsHlRavDgOxOnYKTHkV-" |
| "cF1aTDtkzNg6WYt9geaPbAvFnR3FVO0BFsl8tzPzMOTkI_kbOfCfAw3FQ"); |
| jwk.Set("p", "7kMQn01JVhyHM7B85hLUuNBDsXiboMc4Di81qmxX7r0"); |
| jwk.Set("q", "wb7rEiGxG4CrybVYns9voNQM2NPCuCEgWPLA_vCkuzk"); |
| jwk.Set("qi", "6rrPQ4YaOLNGtG7TrLXUR_FSWpOFSUveHTHbFQU6iNU"); |
| return jwk; |
| } |
| |
| void SwapDictMembers(base::Value::Dict& d, const char* a, const char* b) { |
| auto va = d.Extract(a); |
| auto vb = d.Extract(b); |
| CHECK(va); |
| CHECK(vb); |
| d.Set(a, std::move(*vb)); |
| d.Set(b, std::move(*va)); |
| } |
| |
| std::string FlipHexByte(std::string_view hex, size_t index) { |
| auto bytes = HexStringToBytes(hex); |
| bytes[index] ^= 0xff; |
| return base::HexEncode(base::span(bytes)); |
| } |
| |
| blink::WebCryptoAlgorithm RS256Algorithm() { |
| return CreateRsaHashedImportAlgorithm( |
| blink::kWebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::kWebCryptoAlgorithmIdSha256); |
| } |
| |
| blink::WebCryptoKey ImportJwkRS256OrDie(std::string_view jwk) { |
| std::vector<uint8_t> jwk_bytes(jwk.begin(), jwk.end()); |
| blink::WebCryptoKey key; |
| Status status = |
| ImportKey(blink::kWebCryptoKeyFormatJwk, jwk_bytes, RS256Algorithm(), |
| true, blink::kWebCryptoKeyUsageSign, &key); |
| CHECK(status.IsSuccess()) << StatusToString(status); |
| return key; |
| } |
| |
| blink::WebCryptoKey ImportJwkRS256OrDie(const base::Value::Dict& jwk) { |
| blink::WebCryptoKey key; |
| Status status = ImportKeyJwkFromDict(jwk, RS256Algorithm(), false, |
| blink::kWebCryptoKeyUsageSign, &key); |
| CHECK(status.IsSuccess()) << StatusToString(status); |
| return key; |
| } |
| |
| Status ImportJwkRS256MustFail(std::string_view jwk) { |
| std::vector<uint8_t> jwk_bytes(jwk.begin(), jwk.end()); |
| blink::WebCryptoKey key; |
| Status status = |
| ImportKey(blink::kWebCryptoKeyFormatJwk, jwk_bytes, RS256Algorithm(), |
| true, blink::kWebCryptoKeyUsageSign, &key); |
| CHECK(!status.IsSuccess()); |
| return status; |
| } |
| |
| Status ImportJwkRS256MustFail(const base::Value::Dict& jwk) { |
| blink::WebCryptoKey key; |
| return ImportKeyJwkFromDict(jwk, RS256Algorithm(), false, |
| blink::kWebCryptoKeyUsageSign, &key); |
| } |
| |
| Status ImportSpkiRS256MustFail(std::string_view spki) { |
| auto spki_bytes = HexStringToBytes(spki); |
| blink::WebCryptoKey key; |
| // Note: SPKI keys can only be used for verification, not signing. |
| Status status = |
| ImportKey(blink::kWebCryptoKeyFormatSpki, spki_bytes, RS256Algorithm(), |
| true, blink::kWebCryptoKeyUsageVerify, &key); |
| CHECK(!status.IsSuccess()); |
| return status; |
| } |
| |
| Status ImportPkcs8RS256MustFail(std::string_view pkcs8) { |
| auto pkcs8_bytes = HexStringToBytes(pkcs8); |
| blink::WebCryptoKey key; |
| Status status = |
| ImportKey(blink::kWebCryptoKeyFormatPkcs8, pkcs8_bytes, RS256Algorithm(), |
| true, blink::kWebCryptoKeyUsageSign, &key); |
| CHECK(!status.IsSuccess()); |
| return status; |
| } |
| |
| std::vector<uint8_t> ExportPkcs8OrDie(blink::WebCryptoKey key) { |
| std::vector<uint8_t> exported; |
| Status status = ExportKey(blink::kWebCryptoKeyFormatPkcs8, key, &exported); |
| CHECK(status.IsSuccess()) << StatusToString(status); |
| return exported; |
| } |
| |
| class WebCryptoRsaSsaTest : public WebCryptoTestBase {}; |
| |
| TEST_F(WebCryptoRsaSsaTest, ImportExportSpki) { |
| // Passing case: Import a valid RSA key in SPKI format. |
| blink::WebCryptoKey key; |
| ASSERT_EQ(Status::Success(), |
| ImportKey(blink::kWebCryptoKeyFormatSpki, |
| HexStringToBytes(kPublicKeySpkiDerHex), |
| CreateRsaHashedImportAlgorithm( |
| blink::kWebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::kWebCryptoAlgorithmIdSha256), |
| true, blink::kWebCryptoKeyUsageVerify, &key)); |
| EXPECT_TRUE(key.Handle()); |
| EXPECT_EQ(blink::kWebCryptoKeyTypePublic, key.GetType()); |
| EXPECT_TRUE(key.Extractable()); |
| EXPECT_EQ(blink::kWebCryptoKeyUsageVerify, key.Usages()); |
| EXPECT_EQ(kModulusLengthBits, |
| key.Algorithm().RsaHashedParams()->ModulusLengthBits()); |
| EXPECT_BYTES_EQ_HEX("010001", |
| key.Algorithm().RsaHashedParams()->PublicExponent()); |
| |
| // Failing case: Import RSA key but provide an inconsistent input algorithm. |
| EXPECT_EQ(Status::ErrorUnsupportedImportKeyFormat(), |
| ImportKey(blink::kWebCryptoKeyFormatSpki, |
| HexStringToBytes(kPublicKeySpkiDerHex), |
| CreateAlgorithm(blink::kWebCryptoAlgorithmIdAesCbc), true, |
| blink::kWebCryptoKeyUsageEncrypt, &key)); |
| |
| // Passing case: Export a previously imported RSA public key in SPKI format |
| // and compare to original data. |
| std::vector<uint8_t> output; |
| ASSERT_EQ(Status::Success(), |
| ExportKey(blink::kWebCryptoKeyFormatSpki, key, &output)); |
| EXPECT_BYTES_EQ_HEX(kPublicKeySpkiDerHex, output); |
| |
| // Failing case: Try to export a previously imported RSA public key in raw |
| // format (not allowed for a public key). |
| EXPECT_EQ(Status::ErrorUnsupportedExportKeyFormat(), |
| ExportKey(blink::kWebCryptoKeyFormatRaw, key, &output)); |
| |
| // Failing case: Try to export a non-extractable key |
| ASSERT_EQ(Status::Success(), |
| ImportKey(blink::kWebCryptoKeyFormatSpki, |
| HexStringToBytes(kPublicKeySpkiDerHex), |
| CreateRsaHashedImportAlgorithm( |
| blink::kWebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::kWebCryptoAlgorithmIdSha256), |
| false, blink::kWebCryptoKeyUsageVerify, &key)); |
| EXPECT_TRUE(key.Handle()); |
| EXPECT_FALSE(key.Extractable()); |
| EXPECT_EQ(Status::ErrorKeyNotExtractable(), |
| ExportKey(blink::kWebCryptoKeyFormatSpki, key, &output)); |
| |
| // TODO(eroman): Failing test: Import a SPKI with an unrecognized hash OID |
| // TODO(eroman): Failing test: Import a SPKI with invalid algorithm params |
| // TODO(eroman): Failing test: Import a SPKI with inconsistent parameters |
| // (e.g. SHA-1 in OID, SHA-256 in params) |
| // TODO(eroman): Failing test: Import a SPKI for RSA-SSA, but with params |
| // as OAEP/PSS |
| } |
| |
| TEST_F(WebCryptoRsaSsaTest, ImportExportPkcs8) { |
| // Passing case: Import a valid RSA key in PKCS#8 format. |
| blink::WebCryptoKey key; |
| ASSERT_EQ(Status::Success(), |
| ImportKey(blink::kWebCryptoKeyFormatPkcs8, |
| HexStringToBytes(kPrivateKeyPkcs8DerHex), |
| CreateRsaHashedImportAlgorithm( |
| blink::kWebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::kWebCryptoAlgorithmIdSha1), |
| true, blink::kWebCryptoKeyUsageSign, &key)); |
| EXPECT_TRUE(key.Handle()); |
| EXPECT_EQ(blink::kWebCryptoKeyTypePrivate, key.GetType()); |
| EXPECT_TRUE(key.Extractable()); |
| EXPECT_EQ(blink::kWebCryptoKeyUsageSign, key.Usages()); |
| EXPECT_EQ(blink::kWebCryptoAlgorithmIdSha1, |
| key.Algorithm().RsaHashedParams()->GetHash().Id()); |
| EXPECT_EQ(kModulusLengthBits, |
| key.Algorithm().RsaHashedParams()->ModulusLengthBits()); |
| EXPECT_BYTES_EQ_HEX("010001", |
| key.Algorithm().RsaHashedParams()->PublicExponent()); |
| |
| std::vector<uint8_t> exported_key; |
| ASSERT_EQ(Status::Success(), |
| ExportKey(blink::kWebCryptoKeyFormatPkcs8, key, &exported_key)); |
| EXPECT_BYTES_EQ_HEX(kPrivateKeyPkcs8DerHex, exported_key); |
| |
| // Failing case: Import RSA key but provide an inconsistent input algorithm |
| // and usage. Several issues here: |
| // * AES-CBC doesn't support PKCS8 key format |
| // * AES-CBC doesn't support "sign" usage |
| EXPECT_EQ(Status::ErrorUnsupportedImportKeyFormat(), |
| ImportKey(blink::kWebCryptoKeyFormatPkcs8, |
| HexStringToBytes(kPrivateKeyPkcs8DerHex), |
| CreateAlgorithm(blink::kWebCryptoAlgorithmIdAesCbc), true, |
| blink::kWebCryptoKeyUsageSign, &key)); |
| } |
| |
| // Tests JWK import and export by doing a roundtrip key conversion and ensuring |
| // it was lossless: |
| // |
| // PKCS8 --> JWK --> PKCS8 |
| TEST_F(WebCryptoRsaSsaTest, ImportRsaPrivateKeyJwkToPkcs8RoundTrip) { |
| blink::WebCryptoKey key; |
| ASSERT_EQ(Status::Success(), |
| ImportKey(blink::kWebCryptoKeyFormatPkcs8, |
| HexStringToBytes(kPrivateKeyPkcs8DerHex), |
| CreateRsaHashedImportAlgorithm( |
| blink::kWebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::kWebCryptoAlgorithmIdSha1), |
| true, blink::kWebCryptoKeyUsageSign, &key)); |
| |
| std::vector<uint8_t> exported_key_jwk; |
| ASSERT_EQ(Status::Success(), |
| ExportKey(blink::kWebCryptoKeyFormatJwk, key, &exported_key_jwk)); |
| |
| // All of the optional parameters (p, q, dp, dq, qi) should be present in the |
| // output. |
| static constexpr char kExpectedJwk[] = |
| "{\"alg\":\"RS1\",\"d\":\"M6UEKpCyfU9UUcqbu9C0R3GhAa-IQ0Cu-YhfKku-" |
| "kuiUpySsPFaMj5eFOtB8AmbIxqPKCSnx6PESMYhEKfxNmuVf7olqEM5wfD7X5zTkRyejlXRQ" |
| "GlMmgxCcKrrKuig8MbS9L1PD7jfjUs7jT55QO9gMBiKtecbc7og1R8ajsyU\",\"dp\":" |
| "\"KPoTk4ZVvh-" |
| "KFZy6ylpy6hkMMAieGc0nSlVvNsT24Z9VSzTAd3kEJ7vdjdPt4kSDKPOF2Bsw6OQ7L_-" |
| "gJ4YZeQ\",\"dq\":\"Gos485j6cSBJiY1_t57gp3ZoeRKZzfoJ78DlB6yyHtdDAe9b_Ui-" |
| "RV6utuFnglWCdYCo5OjhQVHRUQqCo_LnKQ\",\"e\":\"AQAB\",\"ext\":true,\"key_" |
| "ops\":[\"sign\"],\"kty\":\"RSA\",\"n\":" |
| "\"pW5KDnAQF1iaUYfcfqhB0Vby7A42rVKkTf6x5h962ZHYxRBW_-2xYrTA8oOhKoijlN_" |
| "1JqtykcuzB86r_OCx39XNlQgJbVsri2311nHvY3fAkhyyPCcKcOJZjm_4nRnxBazC0_" |
| "DLNfKSgOE4a29kxO8i4eHyDQzoz_siSb2aITc\",\"p\":\"5-" |
| "iUJyCod1Fyc6NWBT6iobwMlKpy1VxuhilrLfyWeUjApyy8zKfqyzVwbgmh31WhU1vZs8w0Fg" |
| "s7bc0-2o5kQw\",\"q\":\"tp3KHPfU1-yB51uQ_MqHSrzeEj_" |
| "ScAGAqpBHm25I3o1n7ST58Z2FuidYdPVCzSDccj5pYzZKH5QlRSsmmmeZ_Q\",\"qi\":" |
| "\"JxVqukEm0kqB86Uoy_sn9WiG-" |
| "ECp9uhuF6RLlP6TGVhLjiL93h5aLjvYqluo2FhBlOshkKz4MrhH8To9JKefTQ\"}"; |
| |
| EXPECT_BYTES_EQ(base::byte_span_from_cstring(kExpectedJwk), exported_key_jwk); |
| |
| ASSERT_EQ(Status::Success(), |
| ImportKey(blink::kWebCryptoKeyFormatJwk, exported_key_jwk, |
| CreateRsaHashedImportAlgorithm( |
| blink::kWebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::kWebCryptoAlgorithmIdSha1), |
| true, blink::kWebCryptoKeyUsageSign, &key)); |
| |
| std::vector<uint8_t> exported_key_pkcs8; |
| ASSERT_EQ(Status::Success(), ExportKey(blink::kWebCryptoKeyFormatPkcs8, key, |
| &exported_key_pkcs8)); |
| |
| ASSERT_EQ(HexStringToBytes(kPrivateKeyPkcs8DerHex), exported_key_pkcs8); |
| } |
| |
| constexpr char kRsa512Jwk_0[] = |
| R"({ |
| "alg": "RS256", |
| "d": "DXYeCQ4W_Yv9zN4vCIQQtgvunsoeWfPeRvYEgVAIYdhuNFRmcinD9UuNP70VOoe2qiZ0DNAjsQn-uYCW9TEZ4Q", |
| "dp": "5f8auF7xPSfhZlklUtBnKFYKEDaYR2dFWg_zQB7oCzE", |
| "dq": "hkRVAMcErDAaCKp0V3QzWYhY_J22nJkiNXIxHz4Ja2c", |
| "e": "AQAB", |
| "kty": "RSA", |
| "n": "yLuHfrJbqUSFFqhUu70z585pWrw1IFcnBCccj43uwGOiesMkx0SWw4jyk3UNTux5AO-7VVCU8jb7237YYaOmOw", |
| "p": "8rLWJeMlHCtwZstui6p8jyai6m7GQ6fC1hK17vxA_JE", |
| "q": "07vkNpoE6SUze7Af2KEP6M_sz8dABZ3EQJuQ6JfiDAs", |
| "qi": "kxd6mc-3AhJtuixmzrSywxvwVwChdEG4I6WVTBe_bvE" |
| })"; |
| // Note that the first letter of "d" has been changed. |
| constexpr char kRsa512Jwk_0_Damaged[] = |
| R"({ |
| "alg": "RS256", |
| "d": "EXYeCQ4W_Yv9zN4vCIQQtgvunsoeWfPeRvYEgVAIYdhuNFRmcinD9UuNP70VOoe2qiZ0DNAjsQn-uYCW9TEZ4Q", |
| "dp": "5f8auF7xPSfhZlklUtBnKFYKEDaYR2dFWg_zQB7oCzE", |
| "dq": "hkRVAMcErDAaCKp0V3QzWYhY_J22nJkiNXIxHz4Ja2c", |
| "e": "AQAB", |
| "kty": "RSA", |
| "n": "yLuHfrJbqUSFFqhUu70z585pWrw1IFcnBCccj43uwGOiesMkx0SWw4jyk3UNTux5AO-7VVCU8jb7237YYaOmOw", |
| "p": "8rLWJeMlHCtwZstui6p8jyai6m7GQ6fC1hK17vxA_JE", |
| "q": "07vkNpoE6SUze7Af2KEP6M_sz8dABZ3EQJuQ6JfiDAs", |
| "qi": "kxd6mc-3AhJtuixmzrSywxvwVwChdEG4I6WVTBe_bvE" |
| })"; |
| constexpr char kRsa512Pkcs8_0[] = |
| "30820156020100300D06092A864886F70D0101010500048201403082013C020100024100C8" |
| "BB877EB25BA9448516A854BBBD33E7CE695ABC3520572704271C8F8DEEC063A27AC324C744" |
| "96C388F293750D4EEC7900EFBB555094F236FBDB7ED861A3A63B020301000102400D761E09" |
| "0E16FD8BFDCCDE2F088410B60BEE9ECA1E59F3DE46F60481500861D86E3454667229C3F54B" |
| "8D3FBD153A87B6AA26740CD023B109FEB98096F53119E1022100F2B2D625E3251C2B7066CB" |
| "6E8BAA7C8F26A2EA6EC643A7C2D612B5EEFC40FC91022100D3BBE4369A04E925337BB01FD8" |
| "A10FE8CFECCFC740059DC4409B90E897E20C0B022100E5FF1AB85EF13D27E166592552D067" |
| "28560A1036984767455A0FF3401EE80B3102210086445500C704AC301A08AA745774335988" |
| "58FC9DB69C99223572311F3E096B6702210093177A99CFB702126DBA2C66CEB4B2C31BF057" |
| "00A17441B823A5954C17BF6EF1"; |
| |
| constexpr char kRsa512Jwk_1[] = |
| R"({ |
| "alg": "RS256", |
| "d": "phZ8gCMB14I-A35dwg7j16uSd91COBNN4GuwZchy7FPGH0hNzaH2jOYBU3sWy2ORxwWN8PbKqKOkZb8mh4v_gQ", |
| "dp": "PPEZjFS3paYuOvD2ROr6Es1mP2gGeM_9QNouoZjbpZE", |
| "dq": "pXDNDS8Z77HJXB2EsG40JLsNv-sUkakmAbEzwDfSoFE", |
| "e": "AQAB", |
| "kty": "RSA", |
| "n": "zg5KF3GIFp9XJdOMD9Iz-SeC_CVdUeI-gTxw2Igpd8FB0cJllMxg6n3FALqZ7YKPAp7rCL3VYhu-GR8OnqhNaQ", |
| "p": "8TlLFr-SEpz_ItKjdarp9q8S8_2OHy2RFysdY6yGndE", |
| "q": "2q2EDZHQQ_dp9-Cx2Z8kWn7sYo8K9caFneAJge8ZpBk", |
| "qi": "GT51ibfjUV05KRQhyjiqeCkGT12aAWvLzKRsaV9VE54" |
| })"; |
| constexpr char kRsa512Pkcs8_1[] = |
| "30820155020100300D06092A864886F70D01010105000482013F3082013B020100024100CE" |
| "0E4A177188169F5725D38C0FD233F92782FC255D51E23E813C70D8882977C141D1C26594CC" |
| "60EA7DC500BA99ED828F029EEB08BDD5621BBE191F0E9EA84D690203010001024100A6167C" |
| "802301D7823E037E5DC20EE3D7AB9277DD4238134DE06BB065C872EC53C61F484DCDA1F68C" |
| "E601537B16CB6391C7058DF0F6CAA8A3A465BF26878BFF81022100F1394B16BF92129CFF22" |
| "D2A375AAE9F6AF12F3FD8E1F2D91172B1D63AC869DD1022100DAAD840D91D043F769F7E0B1" |
| "D99F245A7EEC628F0AF5C6859DE00981EF19A41902203CF1198C54B7A5A62E3AF0F644EAFA" |
| "12CD663F680678CFFD40DA2EA198DBA591022100A570CD0D2F19EFB1C95C1D84B06E3424BB" |
| "0DBFEB1491A92601B133C037D2A0510220193E7589B7E3515D39291421CA38AA7829064F5D" |
| "9A016BCBCCA46C695F55139E"; |
| |
| // This is a regression test for http://crbug.com/378315, for which importing |
| // a sequence of keys from JWK could yield the wrong key. The first key would |
| // be imported correctly, however every key after that would actually import |
| // the first key. |
| TEST_F(WebCryptoRsaSsaTest, ImportMultipleRsaKeysJwk) { |
| // Ensure that both keys stay alive across the whole test body, to ensure the |
| // existence of one doesn't impact the other. |
| std::vector<blink::WebCryptoKey> keys = { |
| ImportJwkRS256OrDie(kRsa512Jwk_0), |
| ImportJwkRS256OrDie(kRsa512Jwk_1), |
| }; |
| EXPECT_EQ(HexStringToBytes(kRsa512Pkcs8_0), ExportPkcs8OrDie(keys[0])); |
| EXPECT_EQ(HexStringToBytes(kRsa512Pkcs8_1), ExportPkcs8OrDie(keys[1])); |
| } |
| |
| // Import an RSA private key using JWK. Next import a JWK containing the same |
| // modulus, but mismatched parameters for the rest. It should NOT be possible |
| // that the second import retrieves the first key. See http://crbug.com/378315 |
| // for how that could happen. |
| TEST_F(WebCryptoRsaSsaTest, ImportCorruptKeyReusedModulus) { |
| blink::WebCryptoKey key = ImportJwkRS256OrDie(kRsa512Jwk_0); |
| EXPECT_EQ(Status::DataError(), ImportJwkRS256MustFail(kRsa512Jwk_0_Damaged)); |
| } |
| |
| TEST_F(WebCryptoRsaSsaTest, GenerateKeyPairRsa) { |
| // Successful WebCryptoAlgorithmIdRsaSsaPkcs1v1_5 key generation (sha256) |
| const unsigned int modulus_length = 2048; |
| const std::vector<uint8_t> public_exponent = HexStringToBytes("010001"); |
| blink::WebCryptoAlgorithm algorithm = CreateRsaHashedKeyGenAlgorithm( |
| blink::kWebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::kWebCryptoAlgorithmIdSha256, modulus_length, public_exponent); |
| bool extractable = true; |
| const blink::WebCryptoKeyUsageMask public_usages = |
| blink::kWebCryptoKeyUsageVerify; |
| const blink::WebCryptoKeyUsageMask private_usages = |
| blink::kWebCryptoKeyUsageSign; |
| const blink::WebCryptoKeyUsageMask usages = public_usages | private_usages; |
| blink::WebCryptoKey public_key; |
| blink::WebCryptoKey private_key; |
| |
| EXPECT_EQ(Status::Success(), GenerateKeyPair(algorithm, extractable, usages, |
| &public_key, &private_key)); |
| ASSERT_FALSE(public_key.IsNull()); |
| ASSERT_FALSE(private_key.IsNull()); |
| EXPECT_EQ(blink::kWebCryptoKeyTypePublic, public_key.GetType()); |
| EXPECT_EQ(blink::kWebCryptoKeyTypePrivate, private_key.GetType()); |
| EXPECT_EQ(modulus_length, |
| public_key.Algorithm().RsaHashedParams()->ModulusLengthBits()); |
| EXPECT_EQ(modulus_length, |
| private_key.Algorithm().RsaHashedParams()->ModulusLengthBits()); |
| EXPECT_EQ(blink::kWebCryptoAlgorithmIdSha256, |
| public_key.Algorithm().RsaHashedParams()->GetHash().Id()); |
| EXPECT_EQ(blink::kWebCryptoAlgorithmIdSha256, |
| private_key.Algorithm().RsaHashedParams()->GetHash().Id()); |
| EXPECT_TRUE(public_key.Extractable()); |
| EXPECT_EQ(extractable, private_key.Extractable()); |
| EXPECT_EQ(public_usages, public_key.Usages()); |
| EXPECT_EQ(private_usages, private_key.Usages()); |
| |
| // Try exporting the generated key pair, and then re-importing to verify that |
| // the exported data was valid. |
| std::vector<uint8_t> public_key_spki; |
| EXPECT_EQ(Status::Success(), ExportKey(blink::kWebCryptoKeyFormatSpki, |
| public_key, &public_key_spki)); |
| |
| public_key = blink::WebCryptoKey::CreateNull(); |
| ASSERT_EQ(Status::Success(), |
| ImportKey(blink::kWebCryptoKeyFormatSpki, public_key_spki, |
| CreateRsaHashedImportAlgorithm( |
| blink::kWebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::kWebCryptoAlgorithmIdSha256), |
| true, public_usages, &public_key)); |
| EXPECT_EQ(modulus_length, |
| public_key.Algorithm().RsaHashedParams()->ModulusLengthBits()); |
| |
| std::vector<uint8_t> private_key_pkcs8; |
| EXPECT_EQ(Status::Success(), ExportKey(blink::kWebCryptoKeyFormatPkcs8, |
| private_key, &private_key_pkcs8)); |
| private_key = blink::WebCryptoKey::CreateNull(); |
| ASSERT_EQ(Status::Success(), |
| ImportKey(blink::kWebCryptoKeyFormatPkcs8, private_key_pkcs8, |
| CreateRsaHashedImportAlgorithm( |
| blink::kWebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::kWebCryptoAlgorithmIdSha256), |
| true, private_usages, &private_key)); |
| EXPECT_EQ(modulus_length, |
| private_key.Algorithm().RsaHashedParams()->ModulusLengthBits()); |
| |
| // Fail with bad modulus. |
| algorithm = CreateRsaHashedKeyGenAlgorithm( |
| blink::kWebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::kWebCryptoAlgorithmIdSha256, 0, public_exponent); |
| EXPECT_EQ(Status::ErrorGenerateRsaUnsupportedModulus(), |
| GenerateKeyPair(algorithm, extractable, usages, &public_key, |
| &private_key)); |
| |
| // Fail with bad exponent: larger than unsigned long. |
| unsigned int exponent_length = sizeof(unsigned long) + 1; // NOLINT |
| const std::vector<uint8_t> long_exponent(exponent_length, 0x01); |
| algorithm = CreateRsaHashedKeyGenAlgorithm( |
| blink::kWebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::kWebCryptoAlgorithmIdSha256, modulus_length, long_exponent); |
| EXPECT_EQ(Status::ErrorGenerateKeyPublicExponent(), |
| GenerateKeyPair(algorithm, extractable, usages, &public_key, |
| &private_key)); |
| |
| // Fail with bad exponent: empty. |
| const std::vector<uint8_t> empty_exponent; |
| algorithm = CreateRsaHashedKeyGenAlgorithm( |
| blink::kWebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::kWebCryptoAlgorithmIdSha256, modulus_length, empty_exponent); |
| EXPECT_EQ(Status::ErrorGenerateKeyPublicExponent(), |
| GenerateKeyPair(algorithm, extractable, usages, &public_key, |
| &private_key)); |
| |
| // Fail with bad exponent: all zeros. |
| std::vector<uint8_t> exponent_with_leading_zeros(15, 0x00); |
| algorithm = CreateRsaHashedKeyGenAlgorithm( |
| blink::kWebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::kWebCryptoAlgorithmIdSha256, modulus_length, |
| exponent_with_leading_zeros); |
| EXPECT_EQ(Status::ErrorGenerateKeyPublicExponent(), |
| GenerateKeyPair(algorithm, extractable, usages, &public_key, |
| &private_key)); |
| |
| // Key generation success using exponent with leading zeros. |
| exponent_with_leading_zeros.insert(exponent_with_leading_zeros.end(), |
| public_exponent.begin(), |
| public_exponent.end()); |
| algorithm = CreateRsaHashedKeyGenAlgorithm( |
| blink::kWebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::kWebCryptoAlgorithmIdSha256, modulus_length, |
| exponent_with_leading_zeros); |
| EXPECT_EQ(Status::Success(), GenerateKeyPair(algorithm, extractable, usages, |
| &public_key, &private_key)); |
| EXPECT_FALSE(public_key.IsNull()); |
| EXPECT_FALSE(private_key.IsNull()); |
| EXPECT_EQ(blink::kWebCryptoKeyTypePublic, public_key.GetType()); |
| EXPECT_EQ(blink::kWebCryptoKeyTypePrivate, private_key.GetType()); |
| EXPECT_TRUE(public_key.Extractable()); |
| EXPECT_EQ(extractable, private_key.Extractable()); |
| EXPECT_EQ(public_usages, public_key.Usages()); |
| EXPECT_EQ(private_usages, private_key.Usages()); |
| |
| // Successful WebCryptoAlgorithmIdRsaSsaPkcs1v1_5 key generation (sha1) |
| algorithm = CreateRsaHashedKeyGenAlgorithm( |
| blink::kWebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::kWebCryptoAlgorithmIdSha1, modulus_length, public_exponent); |
| EXPECT_EQ(Status::Success(), GenerateKeyPair(algorithm, false, usages, |
| &public_key, &private_key)); |
| EXPECT_FALSE(public_key.IsNull()); |
| EXPECT_FALSE(private_key.IsNull()); |
| EXPECT_EQ(blink::kWebCryptoKeyTypePublic, public_key.GetType()); |
| EXPECT_EQ(blink::kWebCryptoKeyTypePrivate, private_key.GetType()); |
| EXPECT_EQ(modulus_length, |
| public_key.Algorithm().RsaHashedParams()->ModulusLengthBits()); |
| EXPECT_EQ(modulus_length, |
| private_key.Algorithm().RsaHashedParams()->ModulusLengthBits()); |
| EXPECT_EQ(blink::kWebCryptoAlgorithmIdSha1, |
| public_key.Algorithm().RsaHashedParams()->GetHash().Id()); |
| EXPECT_EQ(blink::kWebCryptoAlgorithmIdSha1, |
| private_key.Algorithm().RsaHashedParams()->GetHash().Id()); |
| // Even though "extractable" was set to false, the public key remains |
| // extractable. |
| EXPECT_TRUE(public_key.Extractable()); |
| EXPECT_FALSE(private_key.Extractable()); |
| EXPECT_EQ(public_usages, public_key.Usages()); |
| EXPECT_EQ(private_usages, private_key.Usages()); |
| |
| // Exporting a private key as SPKI format doesn't make sense. However this |
| // will first fail because the key is not extractable. |
| std::vector<uint8_t> output; |
| EXPECT_EQ(Status::ErrorKeyNotExtractable(), |
| ExportKey(blink::kWebCryptoKeyFormatSpki, private_key, &output)); |
| |
| // Re-generate an extractable private_key and try to export it as SPKI format. |
| // This should fail since spki is for public keys. |
| EXPECT_EQ(Status::Success(), GenerateKeyPair(algorithm, true, usages, |
| &public_key, &private_key)); |
| EXPECT_EQ(Status::ErrorUnexpectedKeyType(), |
| ExportKey(blink::kWebCryptoKeyFormatSpki, private_key, &output)); |
| } |
| |
| TEST_F(WebCryptoRsaSsaTest, GenerateKeyPairRsaBadModulusLength) { |
| const unsigned int kBadModulusBits[] = { |
| 0, |
| 248, // Too small. |
| 257, // Not a multiple of 8. |
| 1023, // Not a multiple of 8. |
| 0xFFFFFFFF, // Too big. |
| 8192 + 8, // 8192 is the maximum length that is allowed in BoringSSL. |
| }; |
| |
| const std::vector<uint8_t> public_exponent = HexStringToBytes("010001"); |
| |
| for (auto modulus_length_bits : kBadModulusBits) { |
| blink::WebCryptoAlgorithm algorithm = CreateRsaHashedKeyGenAlgorithm( |
| blink::kWebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::kWebCryptoAlgorithmIdSha256, modulus_length_bits, |
| public_exponent); |
| bool extractable = true; |
| const blink::WebCryptoKeyUsageMask usages = blink::kWebCryptoKeyUsageSign; |
| blink::WebCryptoKey public_key; |
| blink::WebCryptoKey private_key; |
| |
| EXPECT_EQ(Status::ErrorGenerateRsaUnsupportedModulus(), |
| GenerateKeyPair(algorithm, extractable, usages, &public_key, |
| &private_key)); |
| } |
| } |
| |
| // Try generating RSA key pairs using unsupported public exponents. Only |
| // exponents of 3 and 65537 are supported. Although OpenSSL can support other |
| // values, it can also hang when given invalid exponents. To avoid hanging, use |
| // a whitelist of known safe exponents. |
| TEST_F(WebCryptoRsaSsaTest, GenerateKeyPairRsaBadExponent) { |
| const unsigned int modulus_length = 1024; |
| |
| const auto kPublicExponents = std::to_array<const char*>({ |
| "11", // 17 - This is a valid public exponent, but currently disallowed. |
| "00", "01", "02", |
| "010000", // 65536 |
| }); |
| |
| for (auto* const exponent : kPublicExponents) { |
| SCOPED_TRACE(&exponent - &kPublicExponents[0]); |
| blink::WebCryptoAlgorithm algorithm = CreateRsaHashedKeyGenAlgorithm( |
| blink::kWebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::kWebCryptoAlgorithmIdSha256, modulus_length, |
| HexStringToBytes(exponent)); |
| |
| blink::WebCryptoKey public_key; |
| blink::WebCryptoKey private_key; |
| |
| EXPECT_EQ(Status::ErrorGenerateKeyPublicExponent(), |
| GenerateKeyPair(algorithm, true, blink::kWebCryptoKeyUsageSign, |
| &public_key, &private_key)); |
| } |
| } |
| |
| TEST_F(WebCryptoRsaSsaTest, SignVerifyFailures) { |
| // Import a key pair. |
| blink::WebCryptoAlgorithm import_algorithm = CreateRsaHashedImportAlgorithm( |
| blink::kWebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::kWebCryptoAlgorithmIdSha1); |
| blink::WebCryptoKey public_key; |
| blink::WebCryptoKey private_key; |
| ASSERT_NO_FATAL_FAILURE(ImportRsaKeyPair( |
| HexStringToBytes(kPublicKeySpkiDerHex), |
| HexStringToBytes(kPrivateKeyPkcs8DerHex), import_algorithm, false, |
| blink::kWebCryptoKeyUsageVerify, blink::kWebCryptoKeyUsageSign, |
| &public_key, &private_key)); |
| |
| blink::WebCryptoAlgorithm algorithm = |
| CreateAlgorithm(blink::kWebCryptoAlgorithmIdRsaSsaPkcs1v1_5); |
| |
| std::vector<uint8_t> signature; |
| bool signature_match; |
| |
| // Compute a signature. |
| const std::vector<uint8_t> data = HexStringToBytes("010203040506070809"); |
| ASSERT_EQ(Status::Success(), Sign(algorithm, private_key, data, &signature)); |
| |
| // Ensure truncated signature does not verify by passing one less byte. |
| EXPECT_EQ(Status::Success(), |
| Verify(algorithm, public_key, |
| base::span(signature).first(signature.size() - 1), data, |
| &signature_match)); |
| EXPECT_FALSE(signature_match); |
| |
| // Ensure truncated signature does not verify by passing no bytes. |
| EXPECT_EQ(Status::Success(), |
| Verify(algorithm, public_key, {}, data, &signature_match)); |
| EXPECT_FALSE(signature_match); |
| |
| // Ensure corrupted signature does not verify. |
| std::vector<uint8_t> corrupt_sig = signature; |
| corrupt_sig[corrupt_sig.size() / 2] ^= 0x1; |
| EXPECT_EQ(Status::Success(), |
| Verify(algorithm, public_key, corrupt_sig, data, &signature_match)); |
| EXPECT_FALSE(signature_match); |
| |
| // Ensure signatures that are greater than the modulus size fail. |
| const size_t long_message_size_bytes = 1024; |
| DCHECK_GT(long_message_size_bytes, kModulusLengthBits / 8); |
| const unsigned char kLongSignature[long_message_size_bytes] = {}; |
| EXPECT_EQ(Status::Success(), Verify(algorithm, public_key, kLongSignature, |
| data, &signature_match)); |
| EXPECT_FALSE(signature_match); |
| |
| // Ensure that signing and verifying with an incompatible algorithm fails. |
| algorithm = CreateAlgorithm(blink::kWebCryptoAlgorithmIdRsaOaep); |
| |
| EXPECT_EQ(Status::ErrorUnexpected(), |
| Sign(algorithm, private_key, data, &signature)); |
| EXPECT_EQ(Status::ErrorUnexpected(), |
| Verify(algorithm, public_key, signature, data, &signature_match)); |
| |
| // Some crypto libraries (NSS) can automatically select the RSA SSA inner hash |
| // based solely on the contents of the input signature data. In the Web Crypto |
| // implementation, the inner hash should be specified uniquely by the key |
| // algorithm parameter. To validate this behavior, call Verify with a computed |
| // signature that used one hash type (SHA-1), but pass in a key with a |
| // different inner hash type (SHA-256). If the hash type is determined by the |
| // signature itself (undesired), the verify will pass, while if the hash type |
| // is specified by the key algorithm (desired), the verify will fail. |
| |
| // Compute a signature using SHA-1 as the inner hash. |
| EXPECT_EQ(Status::Success(), |
| Sign(CreateAlgorithm(blink::kWebCryptoAlgorithmIdRsaSsaPkcs1v1_5), |
| private_key, data, &signature)); |
| |
| blink::WebCryptoKey public_key_256; |
| EXPECT_EQ(Status::Success(), |
| ImportKey(blink::kWebCryptoKeyFormatSpki, |
| HexStringToBytes(kPublicKeySpkiDerHex), |
| CreateRsaHashedImportAlgorithm( |
| blink::kWebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::kWebCryptoAlgorithmIdSha256), |
| true, blink::kWebCryptoKeyUsageVerify, &public_key_256)); |
| |
| // Now verify using an algorithm whose inner hash is SHA-256, not SHA-1. The |
| // signature should not verify. |
| // NOTE: public_key was produced by generateKey, and so its associated |
| // algorithm has WebCryptoRsaKeyGenParams and not WebCryptoRsaSsaParams. Thus |
| // it has no inner hash to conflict with the input algorithm. |
| EXPECT_EQ(blink::kWebCryptoAlgorithmIdSha1, |
| private_key.Algorithm().RsaHashedParams()->GetHash().Id()); |
| EXPECT_EQ(blink::kWebCryptoAlgorithmIdSha256, |
| public_key_256.Algorithm().RsaHashedParams()->GetHash().Id()); |
| |
| bool is_match; |
| EXPECT_EQ(Status::Success(), |
| Verify(CreateAlgorithm(blink::kWebCryptoAlgorithmIdRsaSsaPkcs1v1_5), |
| public_key_256, signature, data, &is_match)); |
| EXPECT_FALSE(is_match); |
| } |
| |
| // Try importing an RSA-SSA public key with unsupported key usages using SPKI |
| // format. RSA-SSA public keys only support the 'verify' usage. |
| TEST_F(WebCryptoRsaSsaTest, ImportRsaSsaPublicKeyBadUsage_SPKI) { |
| const blink::WebCryptoAlgorithm algorithm = CreateRsaHashedImportAlgorithm( |
| blink::kWebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::kWebCryptoAlgorithmIdSha256); |
| |
| const blink::WebCryptoKeyUsageMask kBadUsages[] = { |
| blink::kWebCryptoKeyUsageSign, |
| blink::kWebCryptoKeyUsageSign | blink::kWebCryptoKeyUsageVerify, |
| blink::kWebCryptoKeyUsageEncrypt, |
| blink::kWebCryptoKeyUsageEncrypt | blink::kWebCryptoKeyUsageDecrypt, |
| }; |
| |
| for (auto usage : kBadUsages) { |
| blink::WebCryptoKey public_key; |
| ASSERT_EQ(Status::ErrorCreateKeyBadUsages(), |
| ImportKey(blink::kWebCryptoKeyFormatSpki, |
| HexStringToBytes(kPublicKeySpkiDerHex), algorithm, |
| false, usage, &public_key)); |
| } |
| } |
| |
| // Try importing an RSA-SSA public key with unsupported key usages using JWK |
| // format. RSA-SSA public keys only support the 'verify' usage. |
| TEST_F(WebCryptoRsaSsaTest, ImportRsaSsaPublicKeyBadUsage_JWK) { |
| const blink::WebCryptoAlgorithm algorithm = CreateRsaHashedImportAlgorithm( |
| blink::kWebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::kWebCryptoAlgorithmIdSha256); |
| |
| const blink::WebCryptoKeyUsageMask kBadUsages[] = { |
| blink::kWebCryptoKeyUsageSign, |
| blink::kWebCryptoKeyUsageSign | blink::kWebCryptoKeyUsageVerify, |
| blink::kWebCryptoKeyUsageEncrypt, |
| blink::kWebCryptoKeyUsageEncrypt | blink::kWebCryptoKeyUsageDecrypt, |
| }; |
| |
| base::Value::Dict jwk = BuildTestJwkPublicKey(); |
| jwk.Remove("use"); |
| |
| for (auto usage : kBadUsages) { |
| blink::WebCryptoKey public_key; |
| ASSERT_EQ(Status::ErrorCreateKeyBadUsages(), |
| ImportKeyJwkFromDict(jwk, algorithm, false, usage, &public_key)); |
| } |
| } |
| |
| // Generate an RSA-SSA key pair with invalid usages. RSA-SSA supports: |
| // 'sign', 'verify' |
| TEST_F(WebCryptoRsaSsaTest, GenerateKeyBadUsages) { |
| const blink::WebCryptoKeyUsageMask kBadUsages[] = { |
| blink::kWebCryptoKeyUsageDecrypt, |
| blink::kWebCryptoKeyUsageVerify | blink::kWebCryptoKeyUsageDecrypt, |
| blink::kWebCryptoKeyUsageWrapKey, |
| }; |
| |
| const unsigned int modulus_length = 256; |
| const std::vector<uint8_t> public_exponent = HexStringToBytes("010001"); |
| |
| for (auto usage : kBadUsages) { |
| blink::WebCryptoKey public_key; |
| blink::WebCryptoKey private_key; |
| |
| ASSERT_EQ(Status::ErrorCreateKeyBadUsages(), |
| GenerateKeyPair(CreateRsaHashedKeyGenAlgorithm( |
| blink::kWebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::kWebCryptoAlgorithmIdSha256, |
| modulus_length, public_exponent), |
| true, usage, &public_key, &private_key)); |
| } |
| } |
| |
| // Generate an RSA-SSA key pair. The public and private keys should select the |
| // key usages which are applicable, and not have the exact same usages as was |
| // specified to GenerateKey |
| TEST_F(WebCryptoRsaSsaTest, GenerateKeyPairIntersectUsages) { |
| const unsigned int modulus_length = 2048; |
| const std::vector<uint8_t> public_exponent = HexStringToBytes("010001"); |
| |
| blink::WebCryptoKey public_key; |
| blink::WebCryptoKey private_key; |
| |
| ASSERT_EQ( |
| Status::Success(), |
| GenerateKeyPair( |
| CreateRsaHashedKeyGenAlgorithm( |
| blink::kWebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::kWebCryptoAlgorithmIdSha256, modulus_length, |
| public_exponent), |
| true, blink::kWebCryptoKeyUsageSign | blink::kWebCryptoKeyUsageVerify, |
| &public_key, &private_key)); |
| |
| EXPECT_EQ(blink::kWebCryptoKeyUsageVerify, public_key.Usages()); |
| EXPECT_EQ(blink::kWebCryptoKeyUsageSign, private_key.Usages()); |
| |
| // Try again but this time without the Verify usages. |
| ASSERT_EQ(Status::Success(), |
| GenerateKeyPair(CreateRsaHashedKeyGenAlgorithm( |
| blink::kWebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::kWebCryptoAlgorithmIdSha256, |
| modulus_length, public_exponent), |
| true, blink::kWebCryptoKeyUsageSign, &public_key, |
| &private_key)); |
| |
| EXPECT_EQ(0, public_key.Usages()); |
| EXPECT_EQ(blink::kWebCryptoKeyUsageSign, private_key.Usages()); |
| } |
| |
| TEST_F(WebCryptoRsaSsaTest, GenerateKeyPairEmptyUsages) { |
| const unsigned int modulus_length = 2048; |
| const std::vector<uint8_t> public_exponent = HexStringToBytes("010001"); |
| |
| blink::WebCryptoKey public_key; |
| blink::WebCryptoKey private_key; |
| |
| ASSERT_EQ(Status::ErrorCreateKeyEmptyUsages(), |
| GenerateKeyPair(CreateRsaHashedKeyGenAlgorithm( |
| blink::kWebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::kWebCryptoAlgorithmIdSha256, |
| modulus_length, public_exponent), |
| true, 0, &public_key, &private_key)); |
| } |
| |
| TEST_F(WebCryptoRsaSsaTest, ImportKeyEmptyUsages) { |
| blink::WebCryptoKey public_key; |
| blink::WebCryptoKey private_key; |
| |
| // Public without usage does not throw an error. |
| ASSERT_EQ(Status::Success(), |
| ImportKey(blink::kWebCryptoKeyFormatSpki, |
| HexStringToBytes(kPublicKeySpkiDerHex), |
| CreateRsaHashedImportAlgorithm( |
| blink::kWebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::kWebCryptoAlgorithmIdSha256), |
| true, 0, &public_key)); |
| EXPECT_EQ(0, public_key.Usages()); |
| |
| // Private empty usage will throw an error. |
| ASSERT_EQ(Status::ErrorCreateKeyEmptyUsages(), |
| ImportKey(blink::kWebCryptoKeyFormatPkcs8, |
| HexStringToBytes(kPrivateKeyPkcs8DerHex), |
| CreateRsaHashedImportAlgorithm( |
| blink::kWebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::kWebCryptoAlgorithmIdSha1), |
| true, 0, &private_key)); |
| |
| std::vector<uint8_t> public_jwk; |
| ASSERT_EQ(Status::Success(), |
| ExportKey(blink::kWebCryptoKeyFormatJwk, public_key, &public_jwk)); |
| |
| ASSERT_EQ(Status::Success(), |
| ImportKey(blink::kWebCryptoKeyFormatJwk, public_jwk, |
| CreateRsaHashedImportAlgorithm( |
| blink::kWebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::kWebCryptoAlgorithmIdSha256), |
| true, 0, &public_key)); |
| EXPECT_EQ(0, public_key.Usages()); |
| |
| // With correct usage to get correct imported private_key |
| std::vector<uint8_t> private_jwk; |
| ImportKey(blink::kWebCryptoKeyFormatPkcs8, |
| HexStringToBytes(kPrivateKeyPkcs8DerHex), |
| CreateRsaHashedImportAlgorithm( |
| blink::kWebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::kWebCryptoAlgorithmIdSha1), |
| true, blink::kWebCryptoKeyUsageSign, &private_key); |
| |
| ASSERT_EQ(Status::Success(), ExportKey(blink::kWebCryptoKeyFormatJwk, |
| private_key, &private_jwk)); |
| |
| ASSERT_EQ(Status::ErrorCreateKeyEmptyUsages(), |
| ImportKey(blink::kWebCryptoKeyFormatJwk, private_jwk, |
| CreateRsaHashedImportAlgorithm( |
| blink::kWebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::kWebCryptoAlgorithmIdSha1), |
| true, 0, &private_key)); |
| } |
| |
| TEST_F(WebCryptoRsaSsaTest, ImportExportJwkRsaPublicKey) { |
| struct TestCase { |
| const blink::WebCryptoAlgorithmId hash; |
| const blink::WebCryptoKeyUsageMask usage; |
| const char* const jwk_alg; |
| }; |
| const auto kTests = std::to_array<TestCase>({ |
| {blink::kWebCryptoAlgorithmIdSha1, blink::kWebCryptoKeyUsageVerify, |
| "RS1"}, |
| {blink::kWebCryptoAlgorithmIdSha256, blink::kWebCryptoKeyUsageVerify, |
| "RS256"}, |
| {blink::kWebCryptoAlgorithmIdSha384, blink::kWebCryptoKeyUsageVerify, |
| "RS384"}, |
| {blink::kWebCryptoAlgorithmIdSha512, blink::kWebCryptoKeyUsageVerify, |
| "RS512"}, |
| }); |
| |
| for (const auto& test : kTests) { |
| SCOPED_TRACE(&test - &kTests[0]); |
| |
| const blink::WebCryptoAlgorithm import_algorithm = |
| CreateRsaHashedImportAlgorithm( |
| blink::kWebCryptoAlgorithmIdRsaSsaPkcs1v1_5, test.hash); |
| |
| // Import the spki to create a public key |
| blink::WebCryptoKey public_key; |
| ASSERT_EQ(Status::Success(), |
| ImportKey(blink::kWebCryptoKeyFormatSpki, |
| HexStringToBytes(kPublicKeySpkiDerHex), |
| import_algorithm, true, test.usage, &public_key)); |
| |
| // Export the public key as JWK and verify its contents |
| std::vector<uint8_t> jwk; |
| ASSERT_EQ(Status::Success(), |
| ExportKey(blink::kWebCryptoKeyFormatJwk, public_key, &jwk)); |
| EXPECT_TRUE(VerifyPublicJwk(jwk, test.jwk_alg, kPublicKeyModulusHex, |
| kPublicKeyExponentHex, test.usage)); |
| |
| // Import the JWK back in to create a new key |
| blink::WebCryptoKey public_key2; |
| ASSERT_EQ(Status::Success(), |
| ImportKey(blink::kWebCryptoKeyFormatJwk, jwk, import_algorithm, |
| true, test.usage, &public_key2)); |
| ASSERT_TRUE(public_key2.Handle()); |
| EXPECT_EQ(blink::kWebCryptoKeyTypePublic, public_key2.GetType()); |
| EXPECT_TRUE(public_key2.Extractable()); |
| EXPECT_EQ(import_algorithm.Id(), public_key2.Algorithm().Id()); |
| |
| // Export the new key as spki and compare to the original. |
| std::vector<uint8_t> spki; |
| ASSERT_EQ(Status::Success(), |
| ExportKey(blink::kWebCryptoKeyFormatSpki, public_key2, &spki)); |
| EXPECT_BYTES_EQ_HEX(kPublicKeySpkiDerHex, spki); |
| } |
| } |
| |
| TEST_F(WebCryptoRsaSsaTest, ImportJwkRsaFailures) { |
| blink::WebCryptoAlgorithm algorithm = CreateRsaHashedImportAlgorithm( |
| blink::kWebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::kWebCryptoAlgorithmIdSha256); |
| blink::WebCryptoKeyUsageMask usages = blink::kWebCryptoKeyUsageVerify; |
| blink::WebCryptoKey key; |
| |
| // An RSA public key JWK _must_ have an "n" (modulus) and an "e" (exponent) |
| // entry, while an RSA private key must have those plus at least a "d" |
| // (private exponent) entry. |
| // See http://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-18, |
| // section 6.3. |
| |
| // Baseline pass. |
| EXPECT_EQ(Status::Success(), |
| ImportKeyJwkFromDict(BuildTestJwkPublicKey(), algorithm, false, |
| usages, &key)); |
| EXPECT_EQ(algorithm.Id(), key.Algorithm().Id()); |
| EXPECT_FALSE(key.Extractable()); |
| EXPECT_EQ(blink::kWebCryptoKeyUsageVerify, key.Usages()); |
| EXPECT_EQ(blink::kWebCryptoKeyTypePublic, key.GetType()); |
| |
| // The following are specific failure cases for when kty = "RSA". |
| |
| // Fail if either "n" or "e" is not present or malformed. |
| for (auto* const param : {"n", "e"}) { |
| base::Value::Dict jwk = BuildTestJwkPublicKey(); |
| |
| // Fail on missing parameter. |
| jwk.Remove(param); |
| EXPECT_NE(Status::Success(), |
| ImportKeyJwkFromDict(jwk, algorithm, false, usages, &key)); |
| |
| // Fail on bad b64 parameter encoding. |
| jwk.Set(param, "Qk3f0DsytU8lfza2au #$% Htaw2xpop9yTuH0"); |
| EXPECT_NE(Status::Success(), |
| ImportKeyJwkFromDict(jwk, algorithm, false, usages, &key)); |
| |
| // Fail on empty parameter. |
| jwk.Set(param, ""); |
| EXPECT_EQ(Status::ErrorJwkEmptyBigInteger(param), |
| ImportKeyJwkFromDict(jwk, algorithm, false, usages, &key)); |
| } |
| } |
| |
| // Try importing an RSA-SSA key from JWK format, having specified both Sign and |
| // Verify usage, AND an invalid JWK. |
| // |
| // Parsing the invalid JWK will fail before the usage check is done. |
| TEST_F(WebCryptoRsaSsaTest, ImportRsaSsaJwkBadUsageAndData) { |
| std::string bad_data = "hello"; |
| |
| blink::WebCryptoKey key; |
| ASSERT_EQ( |
| Status::ErrorJwkNotDictionary(), |
| ImportKey(blink::kWebCryptoKeyFormatJwk, base::as_byte_span(bad_data), |
| CreateRsaHashedImportAlgorithm( |
| blink::kWebCryptoAlgorithmIdRsaSsaPkcs1v1_5, |
| blink::kWebCryptoAlgorithmIdSha256), |
| true, |
| blink::kWebCryptoKeyUsageVerify | blink::kWebCryptoKeyUsageSign, |
| &key)); |
| } |
| |
| TEST_F(WebCryptoRsaSsaTest, ImportValidJwkPrivateKey) { |
| ImportJwkRS256OrDie(BuildTestJwkPrivateKey()); |
| } |
| |
| TEST_F(WebCryptoRsaSsaTest, ImportInvalidJwkPrivateKey_SwapPQ) { |
| auto key = BuildTestJwkPrivateKey(); |
| SwapDictMembers(key, "p", "q"); |
| EXPECT_EQ(StatusToString(ImportJwkRS256MustFail(key)), "DataError"); |
| } |
| |
| TEST_F(WebCryptoRsaSsaTest, ImportInvalidJwkPrivateKey_SwapPQDPDQ) { |
| auto key = BuildTestJwkPrivateKey(); |
| SwapDictMembers(key, "p", "q"); |
| SwapDictMembers(key, "dp", "dq"); |
| EXPECT_EQ(StatusToString(ImportJwkRS256MustFail(key)), "DataError"); |
| } |
| |
| TEST_F(WebCryptoRsaSsaTest, ImportInvalidJwkPrivateKey_MissingMostOptionals) { |
| auto key = BuildTestJwkPrivateKey(); |
| for (const auto* param : {"q", "dp", "dq", "qi"}) |
| key.Remove(param); |
| EXPECT_EQ(StatusToString(ImportJwkRS256MustFail(key)), |
| "DataError: The required JWK member \"q\" was missing"); |
| } |
| |
| TEST_F(WebCryptoRsaSsaTest, ImportInvalidJwkPrivateKey_MissingAllOptionals) { |
| // This is a deliberate spec divergence: while JWK allows keys that are |
| // missing all these attributes (because they are all optional), Chromium |
| // doesn't. That's because without those attributes, we wouldn't be able to |
| // serialize these keys as PKCS#8 keys anyway, which would break a bunch |
| // of assumptions throughout our implementation. |
| // |
| // See: https://crbug.com/374927 |
| auto key = BuildTestJwkPrivateKey(); |
| for (const auto* param : {"p", "q", "dp", "dq", "qi"}) |
| key.Remove(param); |
| EXPECT_EQ(StatusToString(ImportJwkRS256MustFail(key)), |
| "DataError: The required JWK member \"p\" was missing"); |
| } |
| |
| TEST_F(WebCryptoRsaSsaTest, ImportInvalidJwkPrivateKey_LeadingZeroesOnE) { |
| auto key = BuildTestJwkPrivateKey(); |
| key.Set("e", "AAEAAQ"); // 00 01 00 01 |
| EXPECT_EQ("DataError: The JWK \"e\" member contained a leading zero.", |
| StatusToString(ImportJwkRS256MustFail(key))); |
| } |
| |
| TEST_F(WebCryptoRsaSsaTest, ImportInvalidJwkPrivateKey_Base64PaddingInD) { |
| auto key = BuildTestJwkPrivateKey(); |
| auto d = *key.FindString("d"); |
| key.Set("d", d + "=="); |
| EXPECT_EQ( |
| "DataError: The JWK member \"d\" could not be base64url decoded or" |
| " contained padding", |
| StatusToString(ImportJwkRS256MustFail(key))); |
| } |
| |
| TEST_F(WebCryptoRsaSsaTest, ImportInvalidJwkPrivateKey_Base64UrlInN) { |
| auto key = BuildTestJwkPrivateKey(); |
| auto n = *key.FindString("n"); |
| ASSERT_TRUE(base::ReplaceChars(n, "_", "/", &n)); |
| key.Set("n", n); |
| EXPECT_EQ( |
| "DataError: The JWK member \"n\" could not be base64url decoded or" |
| " contained padding", |
| StatusToString(ImportJwkRS256MustFail(key))); |
| } |
| |
| TEST_F(WebCryptoRsaSsaTest, ImportInvalidJwkPrivateKey_Base64UrlInDQ) { |
| auto key = BuildTestJwkPrivateKey(); |
| auto dq = *key.FindString("dq"); |
| ASSERT_TRUE(base::ReplaceChars(dq, "-", "+", &dq)); |
| key.Set("dq", dq); |
| EXPECT_EQ( |
| "DataError: The JWK member \"dq\" could not be base64url decoded or" |
| " contained padding", |
| StatusToString(ImportJwkRS256MustFail(key))); |
| } |
| |
| TEST_F(WebCryptoRsaSsaTest, ImportInvalidSpki_Empty) { |
| EXPECT_EQ("DataError", StatusToString(ImportSpkiRS256MustFail(""))); |
| } |
| |
| TEST_F(WebCryptoRsaSsaTest, ImportInvalidSpki_BadDER) { |
| EXPECT_EQ("DataError", StatusToString(ImportSpkiRS256MustFail("618333c4cb"))); |
| } |
| |
| TEST_F(WebCryptoRsaSsaTest, ImportInvalidSpki_NotRSA) { |
| EXPECT_EQ("DataError", |
| StatusToString(ImportSpkiRS256MustFail( |
| "3059301306072A8648CE3D020106082A8648CE3D030107034200049CB0CF69" |
| "303DAFC761D4E4687B4ECF039E6D34AB964AF80810D8D558A4A8D6F72D5123" |
| "3A1788920A86EE08A1962C79EFA317FB7879E297DAD2146DB995FA1C78"))); |
| } |
| |
| TEST_F(WebCryptoRsaSsaTest, ImportInvalidSpki_TrailingData) { |
| EXPECT_EQ( |
| "DataError", |
| StatusToString(ImportSpkiRS256MustFail( |
| "30819F300D06092A864886F70D010101050003818D0030818902818100A56E4A0E70" |
| "1017589A5187DC7EA841D156F2EC0E36AD52A44DFEB1E61F7AD991D8C51056FFEDB1" |
| "62B4C0F283A12A88A394DFF526AB7291CBB307CEABFCE0B1DFD5CD9508096D5B2B8B" |
| "6DF5D671EF6377C0921CB23C270A70E2598E6FF89D19F105ACC2D3F0CB35F29280E1" |
| "386B6F64C4EF22E1E1F20D0CE8CFFB2249BD9A2137020301000100000000"))); |
| } |
| |
| TEST_F(WebCryptoRsaSsaTest, ImportInvalidPkcs8_Empty) { |
| EXPECT_EQ("DataError", StatusToString(ImportPkcs8RS256MustFail(""))); |
| } |
| |
| TEST_F(WebCryptoRsaSsaTest, ImportInvalidPkcs8_BadDER) { |
| EXPECT_EQ("DataError", |
| StatusToString(ImportPkcs8RS256MustFail("618333c4cb"))); |
| } |
| |
| TEST_F(WebCryptoRsaSsaTest, ImportInvalidPkcs8_CRTValuesAreZero) { |
| EXPECT_EQ( |
| "DataError", |
| StatusToString(ImportPkcs8RS256MustFail( |
| "30820138020100300D06092A864886F70D0101010500048201223082011E02010002" |
| "818100A56E4A0E701017589A5187DC7EA841D156F2EC0E36AD52A44DFEB1E61F7AD9" |
| "91D8C51056FFEDB162B4C0F283A12A88A394DFF526AB7291CBB307CEABFCE0B1DFD5" |
| "CD9508096D5B2B8B6DF5D671EF6377C0921CB23C270A70E2598E6FF89D19F105ACC2" |
| "D3F0CB35F29280E1386B6F64C4EF22E1E1F20D0CE8CFFB2249BD9A21370203010001" |
| "02818033A5042A90B27D4F5451CA9BBBD0B44771A101AF884340AEF9885F2A4BBE92" |
| "E894A724AC3C568C8F97853AD07C0266C8C6A3CA0929F1E8F11231884429FC4D9AE5" |
| "5FEE896A10CE707C3ED7E734E44727A39574501A532683109C2ABACABA283C31B4BD" |
| "2F53C3EE37E352CEE34F9E503BD80C0622AD79C6DCEE883547C6A3B3250201000201" |
| "00020100020100020100"))); |
| } |
| |
| TEST_F(WebCryptoRsaSsaTest, ImportInvalidPkcs8_NotRSA) { |
| EXPECT_EQ("DataError", |
| StatusToString(ImportPkcs8RS256MustFail( |
| "308187020100301306072A8648CE3D020106082A8648CE3D030107046D306B" |
| "02010104201FE33950C5F461124AE992C2BDFDF1C73B1615F571BD567E60D1" |
| "9AA1F48CDF42A144034200047C110C66DCFDA807F6E69E45DDB3C74F69A148" |
| "4D203E8DC5ADA8E9A9DD7CB3C70DF448986E51BDE5D1576F99901F9C2C6A80" |
| "6A47FD907643A72B835597EFC8C6"))); |
| } |
| |
| TEST_F(WebCryptoRsaSsaTest, ImportInvalidPkcs8_TrailingData) { |
| EXPECT_EQ( |
| "DataError", |
| StatusToString(ImportPkcs8RS256MustFail( |
| "30820275020100300D06092A864886F70D01010105000482025F3082025B02010002" |
| "818100A56E4A0E701017589A5187DC7EA841D156F2EC0E36AD52A44DFEB1E61F7AD9" |
| "91D8C51056FFEDB162B4C0F283A12A88A394DFF526AB7291CBB307CEABFCE0B1DFD5" |
| "CD9508096D5B2B8B6DF5D671EF6377C0921CB23C270A70E2598E6FF89D19F105ACC2" |
| "D3F0CB35F29280E1386B6F64C4EF22E1E1F20D0CE8CFFB2249BD9A21370203010001" |
| "02818033A5042A90B27D4F5451CA9BBBD0B44771A101AF884340AEF9885F2A4BBE92" |
| "E894A724AC3C568C8F97853AD07C0266C8C6A3CA0929F1E8F11231884429FC4D9AE5" |
| "5FEE896A10CE707C3ED7E734E44727A39574501A532683109C2ABACABA283C31B4BD" |
| "2F53C3EE37E352CEE34F9E503BD80C0622AD79C6DCEE883547C6A3B325024100E7E8" |
| "942720A877517273A356053EA2A1BC0C94AA72D55C6E86296B2DFC967948C0A72CBC" |
| "CCA7EACB35706E09A1DF55A1535BD9B3CC34160B3B6DCD3EDA8E6443024100B69DCA" |
| "1CF7D4D7EC81E75B90FCCA874ABCDE123FD2700180AA90479B6E48DE8D67ED24F9F1" |
| "9D85BA275874F542CD20DC723E6963364A1F9425452B269A6799FD024028FA139386" |
| "55BE1F8A159CBACA5A72EA190C30089E19CD274A556F36C4F6E19F554B34C0777904" |
| "27BBDD8DD3EDE2448328F385D81B30E8E43B2FFFA02786197902401A8B38F398FA71" |
| "2049898D7FB79EE0A77668791299CDFA09EFC0E507ACB21ED74301EF5BFD48BE455E" |
| "AEB6E1678255827580A8E4E8E14151D1510A82A3F2E729024027156ABA4126D24A81" |
| "F3A528CBFB27F56886F840A9F6E86E17A44B94FE9319584B8E22FDDE1E5A2E3BD8AA" |
| "5BA8D8584194EB2190ACF832B847F13A3D24A79F4D00000000"))); |
| } |
| |
| TEST_F(WebCryptoRsaSsaTest, ImportInvalidPkcs8_CorruptFields) { |
| const struct { |
| size_t byte; |
| const char* name; |
| } kTestCases[] = { |
| {50, "n"}, {168, "e"}, {175, "d"}, {333, "p"}, |
| {373, "q"}, {450, "dp"}, {550, "dq"}, {600, "qi"}, |
| }; |
| for (const auto& test : kTestCases) { |
| std::string key = FlipHexByte(kPrivateKeyPkcs8DerHex, test.byte); |
| EXPECT_EQ("DataError", StatusToString(ImportPkcs8RS256MustFail(key))) |
| << "byte flip should have invalidated key: " << test.name; |
| } |
| } |
| |
| } // namespace |
| |
| } // namespace webcrypto |