| // 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/strings/string_number_conversions.h" |
| #include "components/webcrypto/algorithm_dispatch.h" |
| #include "components/webcrypto/algorithms/test_helpers.h" |
| #include "components/webcrypto/jwk.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 { |
| |
| blink::WebCryptoAlgorithm CreateRsaPssAlgorithm( |
| unsigned int salt_length_bytes) { |
| return blink::WebCryptoAlgorithm::AdoptParamsAndCreate( |
| blink::kWebCryptoAlgorithmIdRsaPss, |
| new blink::WebCryptoRsaPssParams(salt_length_bytes)); |
| } |
| |
| class WebCryptoRsaPssTest : public WebCryptoTestBase {}; |
| |
| // Test that no two RSA-PSS signatures are identical, when using a non-zero |
| // lengthed salt. |
| TEST_F(WebCryptoRsaPssTest, SignIsRandom) { |
| // Import public/private key pair. |
| blink::WebCryptoKey public_key = blink::WebCryptoKey::CreateNull(); |
| blink::WebCryptoKey private_key = blink::WebCryptoKey::CreateNull(); |
| |
| ImportRsaKeyPair( |
| HexStringToBytes(kPublicKeySpkiDerHex), |
| HexStringToBytes(kPrivateKeyPkcs8DerHex), |
| CreateRsaHashedImportAlgorithm(blink::kWebCryptoAlgorithmIdRsaPss, |
| blink::kWebCryptoAlgorithmIdSha1), |
| true, blink::kWebCryptoKeyUsageVerify, blink::kWebCryptoKeyUsageSign, |
| &public_key, &private_key); |
| |
| // Use a 20-byte length salt. |
| blink::WebCryptoAlgorithm params = CreateRsaPssAlgorithm(20); |
| |
| // Some random message to sign. |
| std::vector<uint8_t> message = HexStringToBytes(kPublicKeySpkiDerHex); |
| |
| // Sign twice. |
| std::vector<uint8_t> signature1; |
| std::vector<uint8_t> signature2; |
| |
| ASSERT_EQ(Status::Success(), Sign(params, private_key, message, &signature1)); |
| ASSERT_EQ(Status::Success(), Sign(params, private_key, message, &signature2)); |
| |
| // The signatures will be different because of the salt. |
| EXPECT_NE(signature1, signature2); |
| |
| // However both signatures should work when verifying. |
| bool is_match = false; |
| |
| ASSERT_EQ(Status::Success(), |
| Verify(params, public_key, signature1, message, &is_match)); |
| EXPECT_TRUE(is_match); |
| |
| ASSERT_EQ(Status::Success(), |
| Verify(params, public_key, signature2, message, &is_match)); |
| EXPECT_TRUE(is_match); |
| |
| // Corrupt the signature and verification must fail. |
| ASSERT_EQ(Status::Success(), Verify(params, public_key, Corrupted(signature2), |
| message, &is_match)); |
| EXPECT_FALSE(is_match); |
| } |
| |
| // Try signing and verifying when the salt length is 0. The signature in this |
| // case is not random. |
| TEST_F(WebCryptoRsaPssTest, SignVerifyNoSalt) { |
| // Import public/private key pair. |
| blink::WebCryptoKey public_key = blink::WebCryptoKey::CreateNull(); |
| blink::WebCryptoKey private_key = blink::WebCryptoKey::CreateNull(); |
| |
| ImportRsaKeyPair( |
| HexStringToBytes(kPublicKeySpkiDerHex), |
| HexStringToBytes(kPrivateKeyPkcs8DerHex), |
| CreateRsaHashedImportAlgorithm(blink::kWebCryptoAlgorithmIdRsaPss, |
| blink::kWebCryptoAlgorithmIdSha1), |
| true, blink::kWebCryptoKeyUsageVerify, blink::kWebCryptoKeyUsageSign, |
| &public_key, &private_key); |
| |
| // Zero-length salt. |
| blink::WebCryptoAlgorithm params = CreateRsaPssAlgorithm(0); |
| |
| // Some random message to sign. |
| std::vector<uint8_t> message = HexStringToBytes(kPublicKeySpkiDerHex); |
| |
| // Sign twice. |
| std::vector<uint8_t> signature1; |
| std::vector<uint8_t> signature2; |
| |
| ASSERT_EQ(Status::Success(), Sign(params, private_key, message, &signature1)); |
| ASSERT_EQ(Status::Success(), Sign(params, private_key, message, &signature2)); |
| |
| // The signatures will be the same this time. |
| EXPECT_EQ(signature1, signature2); |
| |
| // Make sure that verification works. |
| bool is_match = false; |
| ASSERT_EQ(Status::Success(), |
| Verify(params, public_key, signature1, message, &is_match)); |
| EXPECT_TRUE(is_match); |
| |
| // Corrupt the signature and verification must fail. |
| ASSERT_EQ(Status::Success(), Verify(params, public_key, Corrupted(signature2), |
| message, &is_match)); |
| EXPECT_FALSE(is_match); |
| } |
| |
| TEST_F(WebCryptoRsaPssTest, SignEmptyMessage) { |
| // Import public/private key pair. |
| blink::WebCryptoKey public_key = blink::WebCryptoKey::CreateNull(); |
| blink::WebCryptoKey private_key = blink::WebCryptoKey::CreateNull(); |
| |
| ImportRsaKeyPair( |
| HexStringToBytes(kPublicKeySpkiDerHex), |
| HexStringToBytes(kPrivateKeyPkcs8DerHex), |
| CreateRsaHashedImportAlgorithm(blink::kWebCryptoAlgorithmIdRsaPss, |
| blink::kWebCryptoAlgorithmIdSha1), |
| true, blink::kWebCryptoKeyUsageVerify, blink::kWebCryptoKeyUsageSign, |
| &public_key, &private_key); |
| |
| blink::WebCryptoAlgorithm params = CreateRsaPssAlgorithm(20); |
| std::vector<uint8_t> message; // Empty message. |
| std::vector<uint8_t> signature; |
| |
| ASSERT_EQ(Status::Success(), Sign(params, private_key, message, &signature)); |
| |
| // Make sure that verification works. |
| bool is_match = false; |
| ASSERT_EQ(Status::Success(), |
| Verify(params, public_key, signature, message, &is_match)); |
| EXPECT_TRUE(is_match); |
| |
| // Corrupt the signature and verification must fail. |
| ASSERT_EQ(Status::Success(), Verify(params, public_key, Corrupted(signature), |
| message, &is_match)); |
| EXPECT_FALSE(is_match); |
| } |
| |
| // RSA-PSS known answer tests: |
| const char kKey1[] = |
| "30819F300D06092A864886F70D010101050003818D0030818902818100A56E4A0E70101758" |
| "9A5187DC7EA841D156F2EC0E36AD52A44DFEB1E61F7AD991D8C51056FFEDB162B4C0F283A1" |
| "2A88A394DFF526AB7291CBB307CEABFCE0B1DFD5CD9508096D5B2B8B6DF5D671EF6377C092" |
| "1CB23C270A70E2598E6FF89D19F105ACC2D3F0CB35F29280E1386B6F64C4EF22E1E1F20D0C" |
| "E8CFFB2249BD9A21370203010001"; |
| |
| const char kKey2[] = |
| "30819D300D06092A864886F70D010101050003818B0030818702818100BE499B5E7F06C83F" |
| "A0293E31465C8EB6B58AF920BAE52A7B5B9BFEB7AA72DB1264112EB3FD431D31A2A7E50941" |
| "566929494A0E891ED5613918B4B51B0D1FB97783B26ACF7D0F384CFB35F4D2824F5DD38062" |
| "3A26BF180B63961C619DCDB20CAE406F22F6E276C80A37259490CFEB72C1A71A84F1846D33" |
| "0877BA3E3101EC9C7B020111"; |
| |
| const char kNistTestMessage[] = |
| "c7f5270fca725f9bd19f519a8d7cca3cc5c079024029f3bae510f9b02140fe238908e4f6c1" |
| "8f07a89c687c8684669b1f1db2baf9251a3c829faccb493084e16ec9e28d58868074a5d622" |
| "1667dd6e528d16fe2c9f3db4cfaf6c4dce8c8439af38ceaaaa9ce2ecae7bc8f4a5a55e3bf9" |
| "6df9cd575c4f9cb327951b8cdfe4087168"; |
| |
| struct RsaPssKnownAnswer { |
| blink::WebCryptoAlgorithmId hash; |
| const char* key; |
| const char* message; |
| size_t salt_length; |
| const char* signature; |
| }; |
| |
| constexpr auto kRsaPssKnownAnswers = std::to_array<RsaPssKnownAnswer>({ |
| // Example 1.1 from pss-vect.txt in |
| // ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1-vec.zip |
| {blink::kWebCryptoAlgorithmIdSha1, kKey1, |
| "cdc87da223d786df3b45e0bbbc721326d1ee2af806cc315475cc6f0d9c66e1b62371d4" |
| "5ce2392e1ac92844c310102f156a0d8d52c1f4c40ba3aa65095786cb769757a6563ba9" |
| "58fed0bcc984e8b517a3d5f515b23b8a41e74aa867693f90dfb061a6e86dfaaee64472" |
| "c00e5f20945729cbebe77f06ce78e08f4098fba41f9d6193c0317e8b60d4b6084acb42" |
| "d29e3808a3bc372d85e331170fcbf7cc72d0b71c296648b3a4d10f416295d0807aa625" |
| "cab2744fd9ea8fd223c42537029828bd16be02546f130fd2e33b936d2676e08aed1b73" |
| "318b750a0167d0", |
| 20, |
| "9074308fb598e9701b2294388e52f971faac2b60a5145af185df5287b5ed2887e57ce7" |
| "fd44dc8634e407c8e0e4360bc226f3ec227f9d9e54638e8d31f5051215df6ebb9c2f95" |
| "79aa77598a38f914b5b9c1bd83c4e2f9f382a0d0aa3542ffee65984a601bc69eb28deb" |
| "27dca12c82c2d4c3f66cd500f1ff2b994d8a4e30cbb33c"}, |
| // Example 1.4 from pss-vect.txt in |
| // ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1-vec.zip |
| {blink::kWebCryptoAlgorithmIdSha1, kKey1, "bc656747fa9eafb3f0", 20, |
| "4609793b23e9d09362dc21bb47da0b4f3a7622649a47d464019b9aeafe53359c178c91" |
| "cd58ba6bcb78be0346a7bc637f4b873d4bab38ee661f199634c547a1ad8442e03da015" |
| "b136e543f7ab07c0c13e4225b8de8cce25d4f6eb8400f81f7e1833b7ee6e334d370964" |
| "ca79fdb872b4d75223b5eeb08101591fb532d155a6de87"}, |
| // The next three are from SigVerPSS_186-3.rsp in |
| // http://csrc.nist.gov/groups/STM/cavp/documents/dss/186-2rsatestvectors.zip |
| {blink::kWebCryptoAlgorithmIdSha256, kKey2, kNistTestMessage, 10, |
| "11e169f2fd40b07641b9768a2ab19965fb6c27f10fcf0323fcc6d12eb4f1c06b330dda" |
| "a1ea504407afa29de9ebe0374fe9d1e7d0ffbd5fc1cf3a3446e4145415d2ab24f789b3" |
| "464c5c43a256bbc1d692cf7f04801dac5bb401a4a03ab7d5728a860c19e1a4dc797ca5" |
| "42c8203cec2e601eb0c51f567f2eda022b0b9ebddeeefa"}, |
| {blink::kWebCryptoAlgorithmIdSha384, kKey2, kNistTestMessage, 10, |
| "b281ad934b2775c0cba5fb10aa574d2ed85c7f99b942b78e49702480069362ed394bad" |
| "ed55e56cfcbe7b0b8d2217a05a60e1acd725cb09060dfac585bc2132b99b41cdbd530c" |
| "69d17cdbc84bc6b9830fc7dc8e1b2412cfe06dcf8c1a0cc3453f93f25ebf10cb0c9033" |
| "4fac573f449138616e1a194c67f44efac34cc07a526267"}, |
| {blink::kWebCryptoAlgorithmIdSha512, kKey2, kNistTestMessage, 10, |
| "8ffc38f9b820ef6b080fd2ec7de5626c658d79056f3edf610a295b7b0546f73e01ffdf4d0" |
| "070ebf79c33fd86c2d608be9438b3d420d09535b97cd3d846ecaf8f6551cdf93197e9f8fb" |
| "048044473ab41a801e9f7fc983c62b324361dade9f71a65952bd35c59faaa4d6ff462f68a" |
| "6c4ec0b428aa47336f2178aeb276136563b7d"}, |
| }); |
| |
| blink::WebCryptoKey RsaPssKeyFromBytes(blink::WebCryptoAlgorithmId hash, |
| const char* key) { |
| auto pubkey = blink::WebCryptoKey::CreateNull(); |
| webcrypto::Status result = ImportKey( |
| blink::kWebCryptoKeyFormatSpki, HexStringToBytes(key), |
| CreateRsaHashedImportAlgorithm(blink::kWebCryptoAlgorithmIdRsaPss, hash), |
| true, blink::kWebCryptoKeyUsageVerify, &pubkey); |
| CHECK(result == Status::Success()); |
| return pubkey; |
| } |
| |
| bool VerifySignature(size_t salt_length, |
| blink::WebCryptoKey pubkey, |
| const std::vector<uint8_t>& signature, |
| const std::vector<uint8_t>& message) { |
| bool is_match = false; |
| auto result = Verify(CreateRsaPssAlgorithm(salt_length), pubkey, signature, |
| message, &is_match); |
| CHECK(result == Status::Success()); |
| return is_match; |
| } |
| |
| // Iterate through known answers and test verification. |
| // * Verify over original message should succeed |
| // * Verify over corrupted message should fail |
| // * Verification with corrupted signature should fail |
| TEST_F(WebCryptoRsaPssTest, VerifyKnownAnswer) { |
| for (const auto& test : kRsaPssKnownAnswers) { |
| SCOPED_TRACE(&test - &kRsaPssKnownAnswers[0]); |
| blink::WebCryptoKey pubkey = RsaPssKeyFromBytes(test.hash, test.key); |
| |
| std::vector<uint8_t> message = HexStringToBytes(test.message); |
| std::vector<uint8_t> signature = HexStringToBytes(test.signature); |
| |
| EXPECT_TRUE(VerifySignature(test.salt_length, pubkey, signature, message)); |
| |
| EXPECT_FALSE(VerifySignature(test.salt_length, pubkey, signature, |
| Corrupted(message))); |
| EXPECT_FALSE(VerifySignature(test.salt_length, pubkey, Corrupted(signature), |
| message)); |
| } |
| } |
| |
| } // namespace |
| |
| } // namespace webcrypto |