From bd19ef446316a2be06f76a55674a5f47fef7ec6b Mon Sep 17 00:00:00 2001 From: Samuel Chiang Date: Wed, 5 Feb 2025 10:38:05 -0800 Subject: [PATCH 1/3] Add support for PKCS12_set_mac (#2128) Ruby has added a binding for an additional function called `PKCS12_set_mac` in it's master branch: ruby/ruby@c79b435. This was discovered prior to the PR for Ruby's CI integration with the master branch being merged: https://github.com/aws/aws-lc/pull/2071 OpenSSL's implementation of the function directly sets a designated mac field within the `PKCS12` structure. Our `PKCS12` structure is folded into a string of bytes along with its length and there aren't any available fields for us to directly set. This means that AWS-LC's implementation of `PKCS12_set_mac` has to parse the proper contents from `PKCS12` and rerun the key and mac generation with the new parameters provided. ### Call-outs: 1. The parsing logic is similar to `PKCS12_get_key_and_certs`, but the logic can't be shared. This is because we need to maintain pointers to certain parts of the CBS parsers in `PKCS12_set_mac`, so that we can properly rewrite the contents later. 2. I've abstracted the key and mac generation in `PKCS12_create` and `PKCS12_set_mac` to a single function called `pkcs12_gen_and_write_mac`. 3. OpenSSL 3.0 uses SHA-256 as the default hash, yet OpenSSL 1.1.1 uses SHA-1. We've decided to go with SHA-1 for now for better 1.1.1 compatibility. HMAC's security also does not rely on the hash function's collision resistance property. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license and the ISC license. --- crypto/pkcs8/pkcs12_test.cc | 129 ++++++++++++++++++--------- crypto/pkcs8/pkcs8_x509.c | 169 ++++++++++++++++++++++++++++++------ include/openssl/pkcs8.h | 25 +++++- 3 files changed, 250 insertions(+), 73 deletions(-) diff --git a/crypto/pkcs8/pkcs12_test.cc b/crypto/pkcs8/pkcs12_test.cc index 2a78a9794c..147567118a 100644 --- a/crypto/pkcs8/pkcs12_test.cc +++ b/crypto/pkcs8/pkcs12_test.cc @@ -395,6 +395,33 @@ static void TestRoundTrip(const char *password, const char *name, ASSERT_TRUE(certs2); ASSERT_TRUE(PKCS12_get_key_and_certs(&key2, certs2.get(), &cbs, password)); bssl::UniquePtr free_key2(key2); + + // Check that writing to a |BIO| does the same thing. + bssl::UniquePtr bio(BIO_new(BIO_s_mem())); + ASSERT_TRUE(bio); + ASSERT_TRUE(i2d_PKCS12_bio(bio.get(), pkcs12.get())); + const uint8_t *bio_data; + size_t bio_len; + ASSERT_TRUE(BIO_mem_contents(bio.get(), &bio_data, &bio_len)); + EXPECT_EQ(Bytes(bio_data, bio_len), Bytes(der, len)); + + // Check that the result round-trips with |PKCS12_set_mac| as well. The + // resulting bytes will be different due to the regenerated salt, but |pkcs12| + // should still be in a usable state with the same certs and keys encoded. + uint8_t *der2 = nullptr; + EXPECT_TRUE(PKCS12_set_mac(pkcs12.get(), password, strlen(password), nullptr, + 0, mac_iterations, nullptr)); + len = i2d_PKCS12(pkcs12.get(), &der2); + ASSERT_GT(len, 0); + bssl::UniquePtr free_der2(der2); + + CBS_init(&cbs, der2, len); + EVP_PKEY *key3 = nullptr; + certs2.reset(sk_X509_new_null()); + ASSERT_TRUE(certs2); + ASSERT_TRUE(PKCS12_get_key_and_certs(&key3, certs2.get(), &cbs, password)); + bssl::UniquePtr free_key3(key3); + // Note |EVP_PKEY_cmp| returns one for equality while |X509_cmp| returns zero. if (key) { EXPECT_EQ(1, EVP_PKEY_cmp(key2, key.get())); @@ -421,15 +448,6 @@ static void TestRoundTrip(const char *password, const char *name, static_cast(actual_name_len))); } } - - // Check that writing to a |BIO| does the same thing. - bssl::UniquePtr bio(BIO_new(BIO_s_mem())); - ASSERT_TRUE(bio); - ASSERT_TRUE(i2d_PKCS12_bio(bio.get(), pkcs12.get())); - const uint8_t *bio_data; - size_t bio_len; - ASSERT_TRUE(BIO_mem_contents(bio.get(), &bio_data, &bio_len)); - EXPECT_EQ(Bytes(bio_data, bio_len), Bytes(der, len)); } TEST(PKCS12Test, RoundTrip) { @@ -533,7 +551,7 @@ static bssl::UniquePtr MakeTestCert(EVP_PKEY *key) { return x509; } -static bool PKCS12CreateVector(std::vector *out, EVP_PKEY *pkey, +static bool PKCS12CreateVector(bssl::UniquePtr &p12, EVP_PKEY *pkey, const std::vector &certs) { bssl::UniquePtr chain(sk_X509_new_null()); if (!chain) { @@ -546,31 +564,17 @@ static bool PKCS12CreateVector(std::vector *out, EVP_PKEY *pkey, } } - bssl::UniquePtr p12(PKCS12_create(kPassword, nullptr /* name */, pkey, - nullptr /* cert */, chain.get(), 0, - 0, 0, 0, 0)); + p12.reset(PKCS12_create(kPassword, nullptr /* name */, pkey, + nullptr /* cert */, chain.get(), 0, 0, 0, 0, 0)); if (!p12) { return false; } - - int len = i2d_PKCS12(p12.get(), nullptr); - if (len < 0) { - return false; - } - out->resize(static_cast(len)); - uint8_t *ptr = out->data(); - return i2d_PKCS12(p12.get(), &ptr) == len; + return true; } -static void ExpectPKCS12Parse(bssl::Span in, +static void ExpectPKCS12Parse(bssl::UniquePtr &p12, EVP_PKEY *expect_key, X509 *expect_cert, const std::vector &expect_ca_certs) { - bssl::UniquePtr bio(BIO_new_mem_buf(in.data(), in.size())); - ASSERT_TRUE(bio); - - bssl::UniquePtr p12(d2i_PKCS12_bio(bio.get(), nullptr)); - ASSERT_TRUE(p12); - EVP_PKEY *key = nullptr; X509 *cert = nullptr; STACK_OF(X509) *ca_certs = nullptr; @@ -618,33 +622,32 @@ TEST(PKCS12Test, Order) { ASSERT_TRUE(cert3); // PKCS12_parse uses the key to select the main certificate. - std::vector p12; - ASSERT_TRUE(PKCS12CreateVector(&p12, key1.get(), + bssl::UniquePtr p12; + ASSERT_TRUE(PKCS12CreateVector(p12, key1.get(), {cert1.get(), cert2.get(), cert3.get()})); ExpectPKCS12Parse(p12, key1.get(), cert1.get(), {cert2.get(), cert3.get()}); - ASSERT_TRUE(PKCS12CreateVector(&p12, key1.get(), + ASSERT_TRUE(PKCS12CreateVector(p12, key1.get(), {cert3.get(), cert1.get(), cert2.get()})); ExpectPKCS12Parse(p12, key1.get(), cert1.get(), {cert3.get(), cert2.get()}); - ASSERT_TRUE(PKCS12CreateVector(&p12, key1.get(), + ASSERT_TRUE(PKCS12CreateVector(p12, key1.get(), {cert2.get(), cert3.get(), cert1.get()})); ExpectPKCS12Parse(p12, key1.get(), cert1.get(), {cert2.get(), cert3.get()}); // In case of duplicates, the last one is selected. (It is unlikely anything // depends on which is selected, but we match OpenSSL.) - ASSERT_TRUE( - PKCS12CreateVector(&p12, key1.get(), {cert1.get(), cert1b.get()})); + ASSERT_TRUE(PKCS12CreateVector(p12, key1.get(), {cert1.get(), cert1b.get()})); ExpectPKCS12Parse(p12, key1.get(), cert1b.get(), {cert1.get()}); // If there is no key, all certificates are returned as "CA" certificates. - ASSERT_TRUE(PKCS12CreateVector(&p12, nullptr, + ASSERT_TRUE(PKCS12CreateVector(p12, nullptr, {cert1.get(), cert2.get(), cert3.get()})); ExpectPKCS12Parse(p12, nullptr, nullptr, {cert1.get(), cert2.get(), cert3.get()}); // The same happens if there is a key, but it does not match any certificate. - ASSERT_TRUE(PKCS12CreateVector(&p12, key1.get(), {cert2.get(), cert3.get()})); + ASSERT_TRUE(PKCS12CreateVector(p12, key1.get(), {cert2.get(), cert3.get()})); ExpectPKCS12Parse(p12, key1.get(), nullptr, {cert2.get(), cert3.get()}); } @@ -663,13 +666,8 @@ TEST(PKCS12Test, CreateWithAlias) { ASSERT_EQ(res, 1); std::vector certs = {cert1.get(), cert2.get()}; - std::vector der; - ASSERT_TRUE(PKCS12CreateVector(&der, key.get(), certs)); - - bssl::UniquePtr bio(BIO_new_mem_buf(der.data(), der.size())); - ASSERT_TRUE(bio); - bssl::UniquePtr p12(d2i_PKCS12_bio(bio.get(), nullptr)); - ASSERT_TRUE(p12); + bssl::UniquePtr p12; + ASSERT_TRUE(PKCS12CreateVector(p12, key.get(), certs)); EVP_PKEY *parsed_key = nullptr; X509 *parsed_cert = nullptr; @@ -695,3 +693,48 @@ TEST(PKCS12Test, BasicAlloc) { bssl::UniquePtr p12(PKCS12_new()); ASSERT_TRUE(p12); } + +TEST(PKCS12Test, SetMac) { + bssl::UniquePtr key1 = MakeTestKey(); + ASSERT_TRUE(key1); + bssl::UniquePtr cert1 = MakeTestCert(key1.get()); + ASSERT_TRUE(cert1); + bssl::UniquePtr cert1b = MakeTestCert(key1.get()); + ASSERT_TRUE(cert1b); + bssl::UniquePtr key2 = MakeTestKey(); + ASSERT_TRUE(key2); + bssl::UniquePtr cert2 = MakeTestCert(key2.get()); + ASSERT_TRUE(cert2); + bssl::UniquePtr key3 = MakeTestKey(); + ASSERT_TRUE(key3); + bssl::UniquePtr cert3 = MakeTestCert(key3.get()); + ASSERT_TRUE(cert3); + + // PKCS12_parse uses the key to select the main certificate. + bssl::UniquePtr p12; + ASSERT_TRUE(PKCS12CreateVector(p12, key1.get(), + {cert1.get(), cert2.get(), cert3.get()})); + EXPECT_TRUE(PKCS12_set_mac(p12.get(), kPassword, strlen(kPassword), nullptr, + 0, 0, nullptr)); + ExpectPKCS12Parse(p12, key1.get(), cert1.get(), {cert2.get(), cert3.get()}); + + ASSERT_TRUE(PKCS12CreateVector(p12, key1.get(), + {cert3.get(), cert1.get(), cert2.get()})); + EXPECT_TRUE(PKCS12_set_mac(p12.get(), kPassword, strlen(kPassword), nullptr, + 0, 0, nullptr)); + ExpectPKCS12Parse(p12, key1.get(), cert1.get(), {cert3.get(), cert2.get()}); + + ASSERT_TRUE(PKCS12CreateVector(p12, key1.get(), + {cert2.get(), cert3.get(), cert1.get()})); + EXPECT_TRUE(PKCS12_set_mac(p12.get(), kPassword, strlen(kPassword), nullptr, + 0, 0, nullptr)); + ExpectPKCS12Parse(p12, key1.get(), cert1.get(), {cert2.get(), cert3.get()}); + + // The same password should be used with |PKCS12_create| and |PKCS12_set_mac|. + ASSERT_TRUE(PKCS12CreateVector(p12, key1.get(), + {cert1.get(), cert2.get(), cert3.get()})); + EXPECT_FALSE(PKCS12_set_mac(p12.get(), kUnicodePassword, + strlen(kUnicodePassword), nullptr, 0, 0, + nullptr)); +} + diff --git a/crypto/pkcs8/pkcs8_x509.c b/crypto/pkcs8/pkcs8_x509.c index 2e7148c45e..a0bdb4b5e9 100644 --- a/crypto/pkcs8/pkcs8_x509.c +++ b/crypto/pkcs8/pkcs8_x509.c @@ -1114,6 +1114,44 @@ static int add_encrypted_data(CBB *out, int pbe_nid, const char *password, return ret; } +static int pkcs12_gen_and_write_mac(CBB *out_pfx, const uint8_t *auth_safe_data, + size_t auth_safe_data_len, + const char *password, size_t password_len, + uint8_t *mac_salt, size_t salt_len, + int mac_iterations, const EVP_MD *md) { + int ret = 0; + uint8_t mac_key[EVP_MAX_MD_SIZE]; + uint8_t mac[EVP_MAX_MD_SIZE]; + unsigned mac_len; + if (!pkcs12_key_gen(password, password_len, mac_salt, salt_len, PKCS12_MAC_ID, + mac_iterations, EVP_MD_size(md), mac_key, md) || + !HMAC(md, mac_key, EVP_MD_size(md), auth_safe_data, auth_safe_data_len, + mac, &mac_len)) { + goto out; + } + + CBB mac_data, digest_info, mac_cbb, mac_salt_cbb; + if (!CBB_add_asn1(out_pfx, &mac_data, CBS_ASN1_SEQUENCE) || + !CBB_add_asn1(&mac_data, &digest_info, CBS_ASN1_SEQUENCE) || + !EVP_marshal_digest_algorithm(&digest_info, md) || + !CBB_add_asn1(&digest_info, &mac_cbb, CBS_ASN1_OCTETSTRING) || + !CBB_add_bytes(&mac_cbb, mac, mac_len) || + !CBB_add_asn1(&mac_data, &mac_salt_cbb, CBS_ASN1_OCTETSTRING) || + !CBB_add_bytes(&mac_salt_cbb, mac_salt, salt_len) || + // The iteration count has a DEFAULT of 1, but RFC 7292 says "The default + // is for historical reasons and its use is deprecated." Thus we + // explicitly encode the iteration count, though it is not valid DER. + !CBB_add_asn1_uint64(&mac_data, mac_iterations) || + !CBB_flush(out_pfx)) { + goto out; + } + ret = 1; + +out: + OPENSSL_cleanse(mac_key, sizeof(mac_key)); + return ret; +} + PKCS12 *PKCS12_create(const char *password, const char *name, const EVP_PKEY *pkey, X509 *cert, const STACK_OF(X509)* chain, int key_nid, int cert_nid, @@ -1194,9 +1232,7 @@ PKCS12 *PKCS12_create(const char *password, const char *name, PKCS12 *ret = NULL; CBB cbb, pfx, auth_safe, auth_safe_oid, auth_safe_wrapper, auth_safe_data, content_infos; - uint8_t mac_key[EVP_MAX_MD_SIZE]; - if (!CBB_init(&cbb, 0) || - !CBB_add_asn1(&cbb, &pfx, CBS_ASN1_SEQUENCE) || + if (!CBB_init(&cbb, 0) || !CBB_add_asn1(&cbb, &pfx, CBS_ASN1_SEQUENCE) || !CBB_add_asn1_uint64(&pfx, 3) || // auth_safe is a data ContentInfo. !CBB_add_asn1(&pfx, &auth_safe, CBS_ASN1_SEQUENCE) || @@ -1299,32 +1335,15 @@ PKCS12 *PKCS12_create(const char *password, const char *name, // Compute the MAC. Match OpenSSL in using SHA-1 as the hash function. The MAC // covers |auth_safe_data|. + // TODO (CryptoAlg-2897): Update the default |md| to SHA-256 to align with + // OpenSSL 3.x. const EVP_MD *mac_md = EVP_sha1(); uint8_t mac_salt[PKCS5_SALT_LEN]; - uint8_t mac[EVP_MAX_MD_SIZE]; - unsigned mac_len; if (!CBB_flush(&auth_safe_data) || !RAND_bytes(mac_salt, sizeof(mac_salt)) || - !pkcs12_key_gen(password, password_len, mac_salt, sizeof(mac_salt), - PKCS12_MAC_ID, mac_iterations, EVP_MD_size(mac_md), - mac_key, mac_md) || - !HMAC(mac_md, mac_key, EVP_MD_size(mac_md), CBB_data(&auth_safe_data), - CBB_len(&auth_safe_data), mac, &mac_len)) { - goto err; - } - - CBB mac_data, digest_info, mac_cbb, mac_salt_cbb; - if (!CBB_add_asn1(&pfx, &mac_data, CBS_ASN1_SEQUENCE) || - !CBB_add_asn1(&mac_data, &digest_info, CBS_ASN1_SEQUENCE) || - !EVP_marshal_digest_algorithm(&digest_info, mac_md) || - !CBB_add_asn1(&digest_info, &mac_cbb, CBS_ASN1_OCTETSTRING) || - !CBB_add_bytes(&mac_cbb, mac, mac_len) || - !CBB_add_asn1(&mac_data, &mac_salt_cbb, CBS_ASN1_OCTETSTRING) || - !CBB_add_bytes(&mac_salt_cbb, mac_salt, sizeof(mac_salt)) || - // The iteration count has a DEFAULT of 1, but RFC 7292 says "The default - // is for historical reasons and its use is deprecated." Thus we - // explicitly encode the iteration count, though it is not valid DER. - !CBB_add_asn1_uint64(&mac_data, mac_iterations)) { + !pkcs12_gen_and_write_mac( + &pfx, CBB_data(&auth_safe_data), CBB_len(&auth_safe_data), password, + password_len, mac_salt, sizeof(mac_salt), mac_iterations, mac_md)) { goto err; } @@ -1337,7 +1356,6 @@ PKCS12 *PKCS12_create(const char *password, const char *name, } err: - OPENSSL_cleanse(mac_key, sizeof(mac_key)); CBB_cleanup(&cbb); return ret; } @@ -1353,3 +1371,102 @@ void PKCS12_free(PKCS12 *p12) { OPENSSL_free(p12->ber_bytes); OPENSSL_free(p12); } + +int PKCS12_set_mac(PKCS12 *p12, const char *password, int password_len, + unsigned char *salt, int salt_len, int mac_iterations, + const EVP_MD *md) { + GUARD_PTR(p12); + int ret = 0; + + if (mac_iterations == 0) { + mac_iterations = 1; + } + if (salt_len == 0) { + salt_len = PKCS5_SALT_LEN; + } + // Generate |mac_salt| if |salt| is NULL and copy if NULL. + uint8_t *mac_salt = OPENSSL_malloc(salt_len); + if (mac_salt == NULL) { + goto out; + } + if (salt == NULL) { + if (!RAND_bytes(mac_salt, salt_len)) { + goto out; + } + } else { + OPENSSL_memcpy(mac_salt, salt, salt_len); + } + // TODO (CryptoAlg-2897): Update the default |md| to SHA-256 to align with + // OpenSSL 3.x. + if (md == NULL) { + md = EVP_sha1(); + } + + uint8_t *storage = NULL; + CBS ber_bytes, in, pfx, authsafe, content_type, wrapped_authsafes, authsafes; + uint64_t version; + // The input may be in BER format. + CBS_init(&ber_bytes, p12->ber_bytes, p12->ber_len); + if (!CBS_asn1_ber_to_der(&ber_bytes, &in, &storage)) { + OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_BAD_PKCS12_DATA); + goto out; + } + // There's no use case for |storage| anymore, so we free early. + OPENSSL_free(storage); + + if (!CBS_get_asn1(&in, &pfx, CBS_ASN1_SEQUENCE) || CBS_len(&in) != 0 || + !CBS_get_asn1_uint64(&pfx, &version)) { + OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_BAD_PKCS12_DATA); + goto out; + } + if (version < 3) { + OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_BAD_PKCS12_VERSION); + goto out; + } + + if (!CBS_get_asn1(&pfx, &authsafe, CBS_ASN1_SEQUENCE)) { + OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_BAD_PKCS12_DATA); + goto out; + } + // Save contents of |authsafe| to write back before the CBS is advanced. + const uint8_t *orig_authsafe = CBS_data(&authsafe); + size_t orig_authsafe_len = CBS_len(&authsafe); + + // Parse for |authsafes| which is the data that we should be running HMAC on. + if (!CBS_get_asn1(&authsafe, &content_type, CBS_ASN1_OBJECT) || + !CBS_get_asn1(&authsafe, &wrapped_authsafes, + CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0) || + !CBS_get_asn1(&wrapped_authsafes, &authsafes, CBS_ASN1_OCTETSTRING)) { + OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_BAD_PKCS12_DATA); + goto out; + } + + // Rewrite contents of |p12| with the original contents and updated MAC. + CBB cbb, out_pfx, out_auth_safe; + if (!CBB_init(&cbb, 0) || !CBB_add_asn1(&cbb, &out_pfx, CBS_ASN1_SEQUENCE) || + !CBB_add_asn1_uint64(&out_pfx, version) || + !CBB_add_asn1(&out_pfx, &out_auth_safe, CBS_ASN1_SEQUENCE) || + !CBB_add_bytes(&out_auth_safe, orig_authsafe, orig_authsafe_len) || + !pkcs12_gen_and_write_mac(&out_pfx, CBS_data(&authsafes), + CBS_len(&authsafes), password, password_len, + mac_salt, salt_len, mac_iterations, md)) { + CBB_cleanup(&cbb); + goto out; + } + + // Verify that the new password is consistent with the original. This is + // behavior specific to AWS-LC. + OPENSSL_free(p12->ber_bytes); + if (!CBB_finish(&cbb, &p12->ber_bytes, &p12->ber_len) || + !PKCS12_verify_mac(p12, password, password_len)) { + CBB_cleanup(&cbb); + goto out; + } + + ret = 1; + +out: + OPENSSL_free(mac_salt); + return ret; +} + diff --git a/include/openssl/pkcs8.h b/include/openssl/pkcs8.h index a6fd1c4cfe..027f7900df 100644 --- a/include/openssl/pkcs8.h +++ b/include/openssl/pkcs8.h @@ -125,8 +125,8 @@ OPENSSL_EXPORT EVP_PKEY *PKCS8_parse_encrypted_private_key(CBS *cbs, // Any friendlyName attributes (RFC 2985) in the PKCS#12 structure will be // returned on the |X509| objects as aliases. See also |X509_alias_get0|. OPENSSL_EXPORT int PKCS12_get_key_and_certs(EVP_PKEY **out_key, - STACK_OF(X509) *out_certs, - CBS *in, const char *password); + STACK_OF(X509) *out_certs, CBS *in, + const char *password); // Deprecated functions. @@ -149,10 +149,10 @@ OPENSSL_EXPORT PKCS12 *d2i_PKCS12(PKCS12 **out_p12, const uint8_t **ber_bytes, size_t ber_len); // d2i_PKCS12_bio acts like |d2i_PKCS12| but reads from a |BIO|. -OPENSSL_EXPORT PKCS12* d2i_PKCS12_bio(BIO *bio, PKCS12 **out_p12); +OPENSSL_EXPORT PKCS12 *d2i_PKCS12_bio(BIO *bio, PKCS12 **out_p12); // d2i_PKCS12_fp acts like |d2i_PKCS12| but reads from a |FILE|. -OPENSSL_EXPORT PKCS12* d2i_PKCS12_fp(FILE *fp, PKCS12 **out_p12); +OPENSSL_EXPORT PKCS12 *d2i_PKCS12_fp(FILE *fp, PKCS12 **out_p12); // i2d_PKCS12 is a dummy function which copies the contents of |p12|. If |out| // is not NULL then the result is written to |*out| and |*out| is advanced just @@ -188,6 +188,23 @@ OPENSSL_EXPORT int PKCS12_parse(const PKCS12 *p12, const char *password, EVP_PKEY **out_pkey, X509 **out_cert, STACK_OF(X509) **out_ca_certs); +// PKCS12_set_mac generates the MAC for |p12| with the designated |password|, +// |salt|, |mac_iterations|, and |md| specified. |password| MUST be the same +// password originally used to encrypt |p12|. Although OpenSSL will allow an +// invalid state with a different |password|, AWS-LC will throw an error and +// return 0. +// +// If |salt| is NULL, a random salt of |salt_len| bytes is generated. If +// |salt_len| is zero, a default salt length is used instead. +// If |md| is NULL, the default is use SHA1 to align with OpenSSL. +// +// TODO (CryptoAlg-2897): Update the default |md| to SHA-256 to align with +// OpenSSL 3.x. +OPENSSL_EXPORT int PKCS12_set_mac(PKCS12 *p12, const char *password, + int password_len, unsigned char *salt, + int salt_len, int mac_iterations, + const EVP_MD *md); + // PKCS12_verify_mac returns one if |password| is a valid password for |p12| // and zero otherwise. Since |PKCS12_parse| doesn't take a length parameter, // it's not actually possible to use a non-NUL-terminated password to actually From 9660ac3f099738a4896fb432dd0a6fda333fa511 Mon Sep 17 00:00:00 2001 From: manastasova <44320407+manastasova@users.noreply.github.com> Date: Wed, 5 Feb 2025 11:57:36 -0800 Subject: [PATCH 2/3] SHA3 and SHAKE - New API Design (#2098) ### Issues: Resolves #CryptoAlg-2810 ### Description of changes: AWS-LC supports SHA3 and SHAKE algorithms though low level SHA3_Init, SHA3_Update, SHA3_Final and SHAKE_init, SHAKE_Final APIs. Currently, there are two issues with the implementation and usage of SHA3 and SHAKE: - There is no support for SHAKE_Update function. SHAKE is implemented by calling SHAKE_Init, SHA3_Update and SHAKE_Final. - SHAKE_Final allows multiple consecutive calls to enable incremental XOF output generation. This PR addresses both of them as follows: - Introduce new API layers - FIPS202, SHA3 and SHAKE. - _Keccak1600_ layer (https://github.com/aws/aws-lc/pull/2097) implements KeccakF1600 Absorb and Squeeze functions; Keccak1600 layer does _not_ manage internal input/output buffers. - _FIPS202_ layer implements Reset, Init, Update, and Finalize functionalities; FIPS202 layer manages the internal input/output buffers, allowing incremental requests (not necessarily multiple of block size) to Update (Absorb) and Squeeze for input/output processing. (Other functionalities, such as zero-ing of bitstate, block size checks, etc. are also handled by FIPS202 API layer). - _FIPS202_ layer implements all common behavior between SHA3 and SHAKE algorithms. - _FIPS202_ layer checks/updates the |ctx->state| flag when handling a common behavior between SHA3 and SHAKE algorithms. |ctx->state| is updated in the higher level SHA3_ SHAKE_ API layer when the behavior of both algorithms diverges (SHAKE _can_ allow incremental squeezes). - _SHA3_ layer implements Init, Update, and Final functionalities; SHA3 layer only implements SHA3 algorithm, thus, offers a single-call SHA3_Final function. SHA3_Final will update internal |ctx->state| flag to prevent any sequential calls. - _SHAKE_ layer implements XOF SHAKE algorithm, therefore, offers Init, Absorb, Squeeze, and Final functionalities; - _SHAKE_ layer implements Init, and Absorb, Squeeze with incremental call support for absorb (byte-wise) and squeeze (block-wise). - _SHAKE_ layer implements a single-call SHAKE_Final function that generates an arbitrary length output and finalizes SHAKE. Incremental XOF output generation is handled by |SHAKE_Squeeze|. |SHAKE_Squeeze| can be called multiple times. SHAKE_Final should be called only once. - KECCAK600_CTX struct updates: - Remove |padded| field - Introduce |state| field - |state| can be |KECCAK1600_STATE_ABSORB|, |KECCAK1600_STATE_SQUEEZE|, |KECCAK1600_STATE_FINAL| - |KECCAK1600_STATE_ABSORB| - allows incremental absorbs until the state is changed - |KECCAK1600_STATE_SQUEEZE| - allows incremental squeezes for |SHAKE_Squeeze| - |KECCAK1600_STATE_Final| - prevents from incremental squeezes via |SHAKE_Final| and prevents from consecutive calls to |SHA3_Final| (Final functions are single-shot functions). SHA3 vs SHAKE algorithms (APIs usage): >- SHA3 digest generation: SHA3_Init; SHA3_Update; SHA3_Final; >- SHAKE (single-shot-output) output generation: SHAKE_Init; SHAKE_Absorb; SHAKE_Final; >- SHAKE (incremental) output generation: SHAKE_Init; SHAKE_Absorb; SHAKE_Squeeze+; ### Call-outs: Service indicator is updated: - Inside SHA3 and SHAKE single shot APIs (as previously in AWS-LC); - Inside SHA3_Final (as previously in AWS-LC); - Inside SHAKE_Final (Single-Shot XOF Final output generation as previously in AWS-LC); - Inside SHAKE_Squeeze (Streaming XOF Squeezes output generation updates the service indicator after each extendable output update); All other algorithms that use SHA3/SHAKE APIs are updated: - ML-KEM (SHA3/SHAKE calls will be inlined later) - ML-DSA (SHAKE_Squeeze (incremental XOF output functionality) inside ML-DSA is never invoked with the KAT test vectors and gtests) ### Testing: _./crypto/crypto_test --gtest_filter="KeccakInternalTest.*"_ _./crypto/crypto_test --gtest_filter="SHA3Test.*"_ _./crypto/crypto_test --gtest_filter="SHAKETest.*"_ _./crypto/crypto_test --gtest_filter="All/PerKEMTest.*"_ _./crypto/crypto_test --gtest_filter="All/PQDSAParameterTest.*"_ By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license and the ISC license. --------- Co-authored-by: Jake Massimo Co-authored-by: Will Childs-Klein Co-authored-by: Justin W Smith <103147162+justsmth@users.noreply.github.com> Co-authored-by: Shubham Mittal <107728331+smittals2@users.noreply.github.com> Co-authored-by: Samuel Chiang Co-authored-by: David Benjamin Co-authored-by: Theo Buehler Co-authored-by: Adam Langley Co-authored-by: Brian Ledger Co-authored-by: Nick Harper Co-authored-by: Andrew Hopkins Co-authored-by: torben-hansen <50673096+torben-hansen@users.noreply.github.com> Co-authored-by: Sean McGrail <549813+skmcgrail@users.noreply.github.com> Co-authored-by: olivergillespie <62296743+olivergillespie@users.noreply.github.com> --- crypto/fipsmodule/digest/digests.c | 4 +- .../ml_kem/ml_kem_ref/symmetric-shake.c | 20 +- crypto/fipsmodule/sha/internal.h | 61 +++-- crypto/fipsmodule/sha/sha3.c | 214 +++++++++++++----- crypto/fipsmodule/sha/sha3_test.cc | 1 - crypto/ml_dsa/ml_dsa_ref/poly.c | 28 +-- crypto/ml_dsa/ml_dsa_ref/sign.c | 15 +- 7 files changed, 236 insertions(+), 107 deletions(-) diff --git a/crypto/fipsmodule/digest/digests.c b/crypto/fipsmodule/digest/digests.c index 88b45d2e5f..d869723685 100644 --- a/crypto/fipsmodule/digest/digests.c +++ b/crypto/fipsmodule/digest/digests.c @@ -430,7 +430,7 @@ static void shake128_init(EVP_MD_CTX *ctx) { } static void shake128_update(EVP_MD_CTX *ctx, const void *data, size_t count) { - CHECK(SHA3_Update(ctx->md_data, data, count)); + CHECK(SHAKE_Absorb(ctx->md_data, data, count)); } static void shake128_final(EVP_MD_CTX *ctx, uint8_t *md, size_t len) { @@ -455,7 +455,7 @@ static void shake256_init(EVP_MD_CTX *ctx) { } static void shake256_update(EVP_MD_CTX *ctx, const void *data, size_t count) { - CHECK(SHA3_Update(ctx->md_data, data, count)); + CHECK(SHAKE_Absorb(ctx->md_data, data, count)); } static void shake256_finalXOF(EVP_MD_CTX *ctx, uint8_t *md, size_t len) { diff --git a/crypto/fipsmodule/ml_kem/ml_kem_ref/symmetric-shake.c b/crypto/fipsmodule/ml_kem/ml_kem_ref/symmetric-shake.c index d31d7136fd..72bf98c2f2 100644 --- a/crypto/fipsmodule/ml_kem/ml_kem_ref/symmetric-shake.c +++ b/crypto/fipsmodule/ml_kem/ml_kem_ref/symmetric-shake.c @@ -29,8 +29,8 @@ void kyber_shake128_absorb(KECCAK1600_CTX *ctx, // SHAKE_Init always returns 1 when called with correct block size value SHAKE_Init(ctx, SHAKE128_BLOCKSIZE); - // SHA3_Update always returns 1 on first call of sizeof(extseed) (34 bytes) - SHA3_Update(ctx, extseed, sizeof(extseed)); + // SHAKE_Absorb always returns 1 on first call of sizeof(extseed) (34 bytes) + SHAKE_Absorb(ctx, extseed, sizeof(extseed)); } /************************************************* @@ -48,8 +48,9 @@ void kyber_shake128_absorb(KECCAK1600_CTX *ctx, void kyber_shake128_squeeze(KECCAK1600_CTX *ctx, uint8_t *out, int nblocks) { // Return code checks can be omitted - // SHAKE_Final always returns 1 - SHAKE_Final(out, ctx, nblocks * SHAKE128_BLOCKSIZE); + // SHAKE_Squeeze always returns 1 when |ctx->state| flag is different + // from |KECCAK1600_STATE_FINAL| + SHAKE_Squeeze(out, ctx, nblocks * SHAKE128_BLOCKSIZE); } /************************************************* @@ -94,12 +95,13 @@ void kyber_shake256_rkprf(ml_kem_params *params, uint8_t out[KYBER_SSBYTES], con // SHAKE_Init always returns 1 when called with correct block size value SHAKE_Init(&ctx, SHAKE256_BLOCKSIZE); - // SHA3_Update always returns 1 on first call of KYBER_SYMBYTES (32 bytes) - SHA3_Update(&ctx, key, KYBER_SYMBYTES); + // SHAKE_Absorb always returns 1 on first call of KYBER_SYMBYTES (32 bytes) + SHAKE_Absorb(&ctx, key, KYBER_SYMBYTES); - // SHA3_Update always returns 1 processing all data blocks that don't need pad - SHA3_Update(&ctx, input, params->ciphertext_bytes); + // SHAKE_Absorb always returns 1 processing all data blocks that don't need pad + SHAKE_Absorb(&ctx, input, params->ciphertext_bytes); - // SHAKE_Final always returns 1 + // SHAKE_Final always returns 1 when |ctx->state| flag is set to + // |KECCAK1600_STATE_ABSORB| (no previous calls to SHAKE_Final) SHAKE_Final(out, &ctx, KYBER_SSBYTES); } diff --git a/crypto/fipsmodule/sha/internal.h b/crypto/fipsmodule/sha/internal.h index 3a0a1c37ce..73d389cb61 100644 --- a/crypto/fipsmodule/sha/internal.h +++ b/crypto/fipsmodule/sha/internal.h @@ -71,6 +71,16 @@ extern "C" { // SHAKE128 has the maximum block size among the SHA3/SHAKE algorithms. #define SHA3_MAX_BLOCKSIZE SHAKE128_BLOCKSIZE +// Define state flag values for Keccak-based functions +#define KECCAK1600_STATE_ABSORB 0 +// KECCAK1600_STATE_SQUEEZE is set when |SHAKE_Squeeze| is called. +// It remains set while |SHAKE_Squeeze| is called repeatedly to output +// chunks of the XOF output. +#define KECCAK1600_STATE_SQUEEZE 1 +// KECCAK1600_STATE_FINAL is set once |SHAKE_Final| is called +// so that |SHAKE_Squeeze| cannot be called anymore. +#define KECCAK1600_STATE_FINAL 2 + typedef struct keccak_st KECCAK1600_CTX; // The data buffer should have at least the maximum number of @@ -82,7 +92,7 @@ struct keccak_st { size_t buf_load; // used bytes in below buffer uint8_t buf[SHA3_MAX_BLOCKSIZE]; // should have at least the max data block size bytes uint8_t pad; // padding character - uint8_t padded; // denotes if padding has been performed + uint8_t state; // denotes the keccak phase (absorb, squeeze, final) }; // Define SHA{n}[_{variant}]_ASM if sha{n}_block_data_order[_{variant}] is @@ -396,32 +406,43 @@ OPENSSL_EXPORT uint8_t *SHAKE128(const uint8_t *data, const size_t in_len, OPENSSL_EXPORT uint8_t *SHAKE256(const uint8_t *data, const size_t in_len, uint8_t *out, size_t out_len); -// SHAKE_Init initializes |ctx| with specified |block_size|, returns 1 on -// success and 0 on failure. Calls SHA3_Init under the hood. -int SHAKE_Init(KECCAK1600_CTX *ctx, size_t block_size); - -// SHAKE_Final writes |len| bytes of finalized digest to |md|, returns 1 on -// success and 0 on failure. Calls SHA3_Final under the hood. -int SHAKE_Final(uint8_t *md, KECCAK1600_CTX *ctx, size_t len); - -// SHA3_Reset zeros the bitstate and the amount of processed input. -void SHA3_Reset(KECCAK1600_CTX *ctx); - -// SHA3_Init initialises |ctx| fields and returns 1 on success and 0 on failure. +// SHA3_Init initialises |ctx| fields through |FIPS202_Init| and +// returns 1 on success and 0 on failure. OPENSSL_EXPORT int SHA3_Init(KECCAK1600_CTX *ctx, size_t bitlen); -// SHA3_Update processes all data blocks that don't need pad through -// |Keccak1600_Absorb| and returns 1 and 0 on failure. + // SHA3_Update check |ctx| pointer and |len| value, calls |FIPS202_Update| + // and returns 1 on success and 0 on failure. int SHA3_Update(KECCAK1600_CTX *ctx, const void *data, size_t len); -// SHA3_Final pads the last data block and processes it through |Keccak1600_Absorb|. -// It processes the data through |Keccak1600_Squeeze| and returns 1 and 0 on failure. +// SHA3_Final pads the last data block and absorbs it through |FIPS202_Finalize|. +// It then calls |Keccak1600_Squeeze| and returns 1 on success +// and 0 on failure. int SHA3_Final(uint8_t *md, KECCAK1600_CTX *ctx); -// Keccak1600_Absorb processes the largest multiple of |r| out of |len| bytes and -// returns the remaining number of bytes. +// SHAKE_Init initialises |ctx| fields through |FIPS202_Init| and +// returns 1 on success and 0 on failure. +int SHAKE_Init(KECCAK1600_CTX *ctx, size_t block_size); + +// SHAKE_Absorb checks |ctx| pointer and |len| values. It updates and absorbs +// input blocks via |FIPS202_Update|. +int SHAKE_Absorb(KECCAK1600_CTX *ctx, const void *data, + size_t len); + +// SHAKE_Squeeze pads the last data block and absorbs it through +// |FIPS202_Finalize| on first call. It writes |len| bytes of incremental +// XOF output to |md| and returns 1 on success and 0 on failure. It can be +// called multiple times. +int SHAKE_Squeeze(uint8_t *md, KECCAK1600_CTX *ctx, size_t len); + +// SHAKE_Final writes |len| bytes of finalized extendible output to |md|, returns 1 on +// success and 0 on failure. It should be called once to finalize absorb and +// squeeze phases. Incremental XOF output should be generated via |SHAKE_Squeeze|. +int SHAKE_Final(uint8_t *md, KECCAK1600_CTX *ctx, size_t len); + +// Keccak1600_Absorb processes the largest multiple of |r| (block size) out of +// |len| bytes and returns the remaining number of bytes. size_t Keccak1600_Absorb(uint64_t A[KECCAK1600_ROWS][KECCAK1600_ROWS], - const uint8_t *data, size_t len, size_t r); + const uint8_t *data, size_t len, size_t r); // Keccak1600_Squeeze generates |out| value of |len| bytes (per call). It can be called // multiple times when used as eXtendable Output Function. |padded| indicates diff --git a/crypto/fipsmodule/sha/sha3.c b/crypto/fipsmodule/sha/sha3.c index 708d554a6d..16b098023a 100644 --- a/crypto/fipsmodule/sha/sha3.c +++ b/crypto/fipsmodule/sha/sha3.c @@ -82,7 +82,7 @@ uint8_t *SHAKE128(const uint8_t *data, const size_t in_len, uint8_t *out, size_t FIPS_service_indicator_lock_state(); KECCAK1600_CTX ctx; int ok = (SHAKE_Init(&ctx, SHAKE128_BLOCKSIZE) && - SHA3_Update(&ctx, data, in_len) && + SHAKE_Absorb(&ctx, data, in_len) && SHAKE_Final(out, &ctx, out_len)); OPENSSL_cleanse(&ctx, sizeof(ctx)); @@ -98,7 +98,7 @@ uint8_t *SHAKE256(const uint8_t *data, const size_t in_len, uint8_t *out, size_t FIPS_service_indicator_lock_state(); KECCAK1600_CTX ctx; int ok = (SHAKE_Init(&ctx, SHAKE256_BLOCKSIZE) && - SHA3_Update(&ctx, data, in_len) && + SHAKE_Absorb(&ctx, data, in_len) && SHAKE_Final(out, &ctx, out_len)); OPENSSL_cleanse(&ctx, sizeof(ctx)); FIPS_service_indicator_unlock_state(); @@ -111,9 +111,9 @@ uint8_t *SHAKE256(const uint8_t *data, const size_t in_len, uint8_t *out, size_t // FIPS202 APIs manage internal input/output buffer on top of Keccak1600 API layer static void FIPS202_Reset(KECCAK1600_CTX *ctx) { - memset(ctx->A, 0, sizeof(ctx->A)); + OPENSSL_memset(ctx->A, 0, sizeof(ctx->A)); ctx->buf_load = 0; - ctx->padded=0; + ctx->state = KECCAK1600_STATE_ABSORB; } static int FIPS202_Init(KECCAK1600_CTX *ctx, uint8_t pad, size_t block_size, size_t bit_len) { @@ -132,47 +132,31 @@ static int FIPS202_Init(KECCAK1600_CTX *ctx, uint8_t pad, size_t block_size, siz return 0; } -// SHA3 APIs implement SHA3 functionalities on top of FIPS202 API layer -void SHA3_Reset(KECCAK1600_CTX *ctx) { - memset(ctx->A, 0, sizeof(ctx->A)); - ctx->buf_load = 0; - ctx->padded = 0; -} - -int SHA3_Init(KECCAK1600_CTX *ctx, size_t bit_len) { - if (bit_len == SHA3_224_DIGEST_BITLENGTH || - bit_len == SHA3_256_DIGEST_BITLENGTH || - bit_len == SHA3_384_DIGEST_BITLENGTH || - bit_len == SHA3_512_DIGEST_BITLENGTH) { - // |block_size| depends on the SHA3 |bit_len| output (digest) length - return FIPS202_Init(ctx, SHA3_PAD_CHAR, SHA3_BLOCKSIZE(bit_len), bit_len); - } - return 0; -} - -int SHA3_Update(KECCAK1600_CTX *ctx, const void *data, size_t len) { +static int FIPS202_Update(KECCAK1600_CTX *ctx, const void *data, size_t len) { uint8_t *data_ptr_copy = (uint8_t *) data; size_t block_size = ctx->block_size; size_t num, rem; - if (len == 0) { - return 1; + if (ctx->state == KECCAK1600_STATE_SQUEEZE || + ctx->state == KECCAK1600_STATE_FINAL ) { + return 0; } + // Case |len| equals 0 is checked in SHA3/SHAKE higher level APIs // Process intermediate buffer. num = ctx->buf_load; if (num != 0) { rem = block_size - num; if (len < rem) { - memcpy(ctx->buf + num, data_ptr_copy, len); + OPENSSL_memcpy(ctx->buf + num, data_ptr_copy, len); ctx->buf_load += len; return 1; } - // There is enough data to fill or overflow the intermediate - // buffer. So we append |rem| bytes and process the block, - // leaving the rest for later processing. - memcpy(ctx->buf + num, data_ptr_copy, rem); + // There is enough data to fill or overflow the intermediate + // buffer. So we append |rem| bytes and process the block, + // leaving the rest for later processing. + OPENSSL_memcpy(ctx->buf + num, data_ptr_copy, rem); data_ptr_copy += rem, len -= rem; if (Keccak1600_Absorb(ctx->A, ctx->buf, block_size, block_size) != 0 ) { return 0; @@ -189,54 +173,176 @@ int SHA3_Update(KECCAK1600_CTX *ctx, const void *data, size_t len) { } if (rem != 0) { - memcpy(ctx->buf, data_ptr_copy + len - rem, rem); + OPENSSL_memcpy(ctx->buf, data_ptr_copy + len - rem, rem); ctx->buf_load = rem; } return 1; } -int SHA3_Final(uint8_t *md, KECCAK1600_CTX *ctx) { +// FIPS202_Finalize processes padding and absorb of last input block +// This function should be called once to finalize absorb and initiate squeeze phase +static int FIPS202_Finalize(uint8_t *md, KECCAK1600_CTX *ctx) { size_t block_size = ctx->block_size; size_t num = ctx->buf_load; - if (ctx->md_size == 0) { + if (ctx->state == KECCAK1600_STATE_SQUEEZE || + ctx->state == KECCAK1600_STATE_FINAL ) { + return 0; + } + + // Pad the data with 10*1. Note that |num| can be |block_size - 1| + // in which case both byte operations below are performed on + // the same byte. + OPENSSL_memset(ctx->buf + num, 0, block_size - num); + ctx->buf[num] = ctx->pad; + ctx->buf[block_size - 1] |= 0x80; + + if (Keccak1600_Absorb(ctx->A, ctx->buf, block_size, block_size) != 0) { + return 0; + } + + // ctx->buf is processed, ctx->buf_load is guaranteed to be zero + ctx->buf_load = 0; + + return 1; +} + +// SHA3 APIs implement SHA3 functionalities on top of FIPS202 API layer +int SHA3_Init(KECCAK1600_CTX *ctx, size_t bit_len) { + if (ctx == NULL) { + return 0; + } + + if (bit_len != SHA3_224_DIGEST_BITLENGTH && + bit_len != SHA3_256_DIGEST_BITLENGTH && + bit_len != SHA3_384_DIGEST_BITLENGTH && + bit_len != SHA3_512_DIGEST_BITLENGTH) { + return 0; + } + // |block_size| depends on the SHA3 |bit_len| output (digest) length + return FIPS202_Init(ctx, SHA3_PAD_CHAR, SHA3_BLOCKSIZE(bit_len), bit_len); +} + +int SHA3_Update(KECCAK1600_CTX *ctx, const void *data, size_t len) { + if (ctx == NULL) { + return 0; + } + + if (data == NULL && len != 0) { + return 0; + } + + if (len == 0) { return 1; } - if (ctx->padded == 0) { - // Pad the data with 10*1. Note that |num| can be |block_size - 1| - // in which case both byte operations below are performed on - // the same byte. - memset(ctx->buf + num, 0, block_size - num); - ctx->buf[num] = ctx->pad; - ctx->buf[block_size - 1] |= 0x80; + return FIPS202_Update(ctx, data, len); +} - if (Keccak1600_Absorb(ctx->A, ctx->buf, block_size, block_size) != 0) { - return 0; - } +// SHA3_Final should be called once to process final digest value +int SHA3_Final(uint8_t *md, KECCAK1600_CTX *ctx) { + if (md == NULL || ctx == NULL) { + return 0; } - Keccak1600_Squeeze(ctx->A, md, ctx->md_size, block_size, ctx->padded); - ctx->padded = 1; + if (ctx->md_size == 0) { + return 1; + } - FIPS_service_indicator_update_state(); + if (FIPS202_Finalize(md, ctx) == 0) { + return 0; + } + Keccak1600_Squeeze(ctx->A, md, ctx->md_size, ctx->block_size, ctx->state); + ctx->state = KECCAK1600_STATE_FINAL; + + FIPS_service_indicator_update_state(); return 1; } -// SHAKE APIs implement SHAKE functionalities on top of FIPS202 API layer int SHAKE_Init(KECCAK1600_CTX *ctx, size_t block_size) { - if (block_size == SHAKE128_BLOCKSIZE || - block_size == SHAKE256_BLOCKSIZE) { - // |block_size| depends on the SHAKE security level - // The output length |bit_len| is initialized to 0 - return FIPS202_Init(ctx, SHAKE_PAD_CHAR, block_size, 0); + if (ctx == NULL) { + return 0; + } + + if (block_size != SHAKE128_BLOCKSIZE && + block_size != SHAKE256_BLOCKSIZE) { + return 0; + } + // |block_size| depends on the SHAKE security level + // The output length |bit_len| is initialized to 0 + return FIPS202_Init(ctx, SHAKE_PAD_CHAR, block_size, 0); +} + +int SHAKE_Absorb(KECCAK1600_CTX *ctx, const void *data, size_t len) { + if (ctx == NULL) { + return 0; + } + + if (data == NULL && len != 0) { + return 0; + } + + if (len == 0) { + return 1; } - return 0; + + return FIPS202_Update(ctx, data, len); } +// SHAKE_Final is to be called once to finalize absorb and squeeze phases +// |ctx->state| restricts consecutive calls to FIPS202_Finalize +// Function SHAKE_Squeeze should be used for incremental XOF output int SHAKE_Final(uint8_t *md, KECCAK1600_CTX *ctx, size_t len) { + if (ctx == NULL || md == NULL) { + return 0; + } + ctx->md_size = len; - return SHA3_Final(md, ctx); + if (ctx->md_size == 0) { + return 1; + } + + if (FIPS202_Finalize(md, ctx) == 0) { + return 0; + } + + Keccak1600_Squeeze(ctx->A, md, ctx->md_size, ctx->block_size, ctx->state); + ctx->state = KECCAK1600_STATE_FINAL; + + FIPS_service_indicator_update_state(); + + return 1; +} + +// SHAKE_Squeeze can be called multiple time for incremental XOF output +int SHAKE_Squeeze(uint8_t *md, KECCAK1600_CTX *ctx, size_t len) { + if (ctx == NULL || md == NULL) { + return 0; + } + + ctx->md_size = len; + + if (ctx->md_size == 0) { + return 1; + } + + if (ctx->state == KECCAK1600_STATE_FINAL) { + return 0; + } + + // Skip FIPS202_Finalize if the input has been padded and + // the last block has been processed + if (ctx->state == KECCAK1600_STATE_ABSORB) { + if (FIPS202_Finalize(md, ctx) == 0) { + return 0; + } + } + + Keccak1600_Squeeze(ctx->A, md, len, ctx->block_size, ctx->state); + ctx->state = KECCAK1600_STATE_SQUEEZE; + + //FIPS_service_indicator_update_state(); + return 1; } diff --git a/crypto/fipsmodule/sha/sha3_test.cc b/crypto/fipsmodule/sha/sha3_test.cc index 9e45ddf7d1..310a5af9c3 100644 --- a/crypto/fipsmodule/sha/sha3_test.cc +++ b/crypto/fipsmodule/sha/sha3_test.cc @@ -165,7 +165,6 @@ TEST(SHA3Test, NISTTestVectors) { }); } - TEST(SHA3Test, NISTTestVectors_SingleShot) { FileTestGTest("crypto/fipsmodule/sha/testvectors/SHA3_224ShortMsg.txt", [](FileTest *t) { diff --git a/crypto/ml_dsa/ml_dsa_ref/poly.c b/crypto/ml_dsa/ml_dsa_ref/poly.c index 42809f98e3..72d81d2e8d 100644 --- a/crypto/ml_dsa/ml_dsa_ref/poly.c +++ b/crypto/ml_dsa/ml_dsa_ref/poly.c @@ -316,9 +316,9 @@ void ml_dsa_poly_uniform(ml_dsa_poly *a, t[1] = nonce >> 8; SHAKE_Init(&state, SHAKE128_BLOCKSIZE); - SHA3_Update(&state, seed, ML_DSA_SEEDBYTES); - SHA3_Update(&state, t, 2); - SHAKE_Final(buf, &state, POLY_UNIFORM_NBLOCKS * SHAKE128_BLOCKSIZE); + SHAKE_Absorb(&state, seed, ML_DSA_SEEDBYTES); + SHAKE_Absorb(&state, t, 2); + SHAKE_Squeeze(buf, &state, POLY_UNIFORM_NBLOCKS * SHAKE128_BLOCKSIZE); ctr = ml_dsa_rej_uniform(a->coeffs, ML_DSA_N, buf, buflen); @@ -327,7 +327,7 @@ void ml_dsa_poly_uniform(ml_dsa_poly *a, for(i = 0; i < off; ++i) buf[i] = buf[buflen - off + i]; - SHAKE_Final(buf + off, &state, POLY_UNIFORM_NBLOCKS * SHAKE128_BLOCKSIZE); + SHAKE_Squeeze(buf + off, &state, POLY_UNIFORM_NBLOCKS * SHAKE128_BLOCKSIZE); buflen = SHAKE128_BLOCKSIZE + off; ctr += ml_dsa_rej_uniform(a->coeffs + ctr, ML_DSA_N - ctr, buf, buflen); } @@ -418,16 +418,17 @@ void ml_dsa_poly_uniform_eta(ml_dsa_params *params, t[1] = nonce >> 8; SHAKE_Init(&state, SHAKE256_BLOCKSIZE); - SHA3_Update(&state, seed, ML_DSA_CRHBYTES); - SHA3_Update(&state, t, 2); - SHAKE_Final(buf, &state, ML_DSA_POLY_UNIFORM_ETA_NBLOCKS_MAX * SHAKE256_BLOCKSIZE); + SHAKE_Absorb(&state, seed, ML_DSA_CRHBYTES); + SHAKE_Absorb(&state, t, 2); + SHAKE_Squeeze(buf, &state, ML_DSA_POLY_UNIFORM_ETA_NBLOCKS_MAX * SHAKE256_BLOCKSIZE); ctr = rej_eta(params, a->coeffs, ML_DSA_N, buf, buflen); while(ctr < ML_DSA_N) { - SHAKE_Final(buf, &state, SHAKE256_BLOCKSIZE); + SHAKE_Squeeze(buf, &state, SHAKE256_BLOCKSIZE); ctr += rej_eta(params, a->coeffs + ctr, ML_DSA_N - ctr, buf, SHAKE256_BLOCKSIZE); } + /* FIPS 204. Section 3.6.3 Destruction of intermediate values. */ OPENSSL_cleanse(buf, sizeof(buf)); OPENSSL_cleanse(&state, sizeof(state)); @@ -459,9 +460,8 @@ void ml_dsa_poly_uniform_gamma1(ml_dsa_params *params, t[1] = nonce >> 8; SHAKE_Init(&state, SHAKE256_BLOCKSIZE); - SHA3_Update(&state, seed, ML_DSA_CRHBYTES); - SHA3_Update(&state, t, 2); - + SHAKE_Absorb(&state, seed, ML_DSA_CRHBYTES); + SHAKE_Absorb(&state, t, 2); SHAKE_Final(buf, &state, POLY_UNIFORM_GAMMA1_NBLOCKS * SHAKE256_BLOCKSIZE); ml_dsa_polyz_unpack(params, a, buf); /* FIPS 204. Section 3.6.3 Destruction of intermediate values. */ @@ -487,8 +487,8 @@ void ml_dsa_poly_challenge(ml_dsa_params *params, ml_dsa_poly *c, const uint8_t KECCAK1600_CTX state; SHAKE_Init(&state, SHAKE256_BLOCKSIZE); - SHA3_Update(&state, seed, params->c_tilde_bytes); - SHAKE_Final(buf, &state, SHAKE256_BLOCKSIZE); + SHAKE_Absorb(&state, seed, params->c_tilde_bytes); + SHAKE_Squeeze(buf, &state, SHAKE256_BLOCKSIZE); signs = 0; for(i = 0; i < 8; ++i) { @@ -502,7 +502,7 @@ void ml_dsa_poly_challenge(ml_dsa_params *params, ml_dsa_poly *c, const uint8_t for(i = ML_DSA_N-params->tau; i < ML_DSA_N; ++i) { do { if(pos >= SHAKE256_BLOCKSIZE) { - SHAKE_Final(buf, &state, SHAKE256_BLOCKSIZE); + SHAKE_Squeeze(buf, &state, SHAKE256_BLOCKSIZE); pos = 0; } diff --git a/crypto/ml_dsa/ml_dsa_ref/sign.c b/crypto/ml_dsa/ml_dsa_ref/sign.c index 64f4110656..0573eb1b01 100644 --- a/crypto/ml_dsa/ml_dsa_ref/sign.c +++ b/crypto/ml_dsa/ml_dsa_ref/sign.c @@ -171,9 +171,9 @@ int ml_dsa_sign_internal(ml_dsa_params *params, /* FIPS 204: line 7 Compute rhoprime = CRH(key, rnd, mu) */ SHAKE_Init(&state, SHAKE256_BLOCKSIZE); - SHA3_Update(&state, key, ML_DSA_SEEDBYTES); - SHA3_Update(&state, rnd, ML_DSA_RNDBYTES); - SHA3_Update(&state, mu, ML_DSA_CRHBYTES); + SHAKE_Absorb(&state, key, ML_DSA_SEEDBYTES); + SHAKE_Absorb(&state, rnd, ML_DSA_RNDBYTES); + SHAKE_Absorb(&state, mu, ML_DSA_CRHBYTES); SHAKE_Final(rhoprime, &state, ML_DSA_CRHBYTES); /* FIPS 204: line 5 Expand matrix and transform vectors */ @@ -199,8 +199,8 @@ int ml_dsa_sign_internal(ml_dsa_params *params, ml_dsa_polyveck_pack_w1(params, sig, &w1); SHAKE_Init(&state, SHAKE256_BLOCKSIZE); - SHA3_Update(&state, mu, ML_DSA_CRHBYTES); - SHA3_Update(&state, sig, params->k * params->poly_w1_packed_bytes); + SHAKE_Absorb(&state, mu, ML_DSA_CRHBYTES); + SHAKE_Absorb(&state, sig, params->k * params->poly_w1_packed_bytes); SHAKE_Final(sig, &state, params->c_tilde_bytes); ml_dsa_poly_challenge(params, &cp, sig); ml_dsa_poly_ntt(&cp); @@ -471,9 +471,10 @@ int ml_dsa_verify_internal(ml_dsa_params *params, /* FIPS 204: line 12 Call random oracle and verify challenge */ SHAKE_Init(&state, SHAKE256_BLOCKSIZE); - SHA3_Update(&state, mu, ML_DSA_CRHBYTES); - SHA3_Update(&state, buf, params->k * params->poly_w1_packed_bytes); + SHAKE_Absorb(&state, mu, ML_DSA_CRHBYTES); + SHAKE_Absorb(&state, buf, params->k * params->poly_w1_packed_bytes); SHAKE_Final(c2, &state, params->c_tilde_bytes); + for(i = 0; i < params->c_tilde_bytes; ++i) { if(c[i] != c2[i]) { return -1; From 54d50513b5972389240c4eee1ffc2bdebecad1c3 Mon Sep 17 00:00:00 2001 From: Jake Massimo Date: Wed, 5 Feb 2025 12:07:10 -0800 Subject: [PATCH 3/3] ML-DSA private keys from seeds (#2157) ### Issues: Resolves #CryptoAlg-2889 ### Description of changes: The industry has decided upon a few conventions to assist with the usability of ML-DSA key sizes, one of which is to decrease the amount of bytes transferred when encoding ML-DSA private keys. As ML-DSA keys can be deterministically generated from a provided 32-byte seed, many standards bodies have selected to use private keys in seed format. For example, the IETF draft that standardizes the use of ML-DSA in X.509. https://datatracker.ietf.org/doc/draft-ietf-lamps-dilithium-certificates/ This provides: - Compliance with IETF spec https://datatracker.ietf.org/doc/draft-ietf-lamps-dilithium-certificates/ - Will allow interoperability with BC to allow customers to import ML-DSA keys The functionality that provides key generation from seeds for ML-DSA was added in my previous PR https://github.com/aws/aws-lc/pull/1999. This PR exposes the internal key generation functions within the `PQDSA` struct, so that a new function named `PQDSA_KEY_set_raw_keypair_from_seed` can directly call the method. The function `PQDSA_KEY_set_raw_keypair_from_seed` simply performs: - a check that the provided `seed` is of the correct length - allocation of public/private key buffers - key generation from seed using `pqdsa_keygen_internal` As such, `pqdsa_priv_decode` has been modified to call: - `PQDSA_KEY_set_raw_private_key` when the provided length of the key is `pqdsa->private_key_len` - `PQDSA_KEY_set_raw_keypair_from_seed` when the provided length of the key is `pqdsa->keygen_seed_len` We implement this at the EVP layer by modifying `EVP_PKEY_pqdsa_new_raw_private_key` to: - call `PQDSA_KEY_set_raw_private_key` when the `len` provided is `private_key_len` - call `PQDSA_KEY_set_raw_keypair_from_seed` when the `len` provided is `keygen_seed_len` ### Call-outs: - The API `EVP_PKEY_pqdsa_new_raw_private_key` will now populate both public and private keys when provided a seed. - The API `EVP_PKEY_pqdsa_new_raw_public_key` only sets the public key - The API `PQDSA_KEY_set_raw_private_key` will only set private key - The API `PQDSA_KEY_set_raw_public_key` will only set public key - The API `EVP_parse_public_key` sets the public key from DER encoded of public key - The API `EVP_parse_private_key` can accept DER encodings of either the full private key representation, or the 32-byte seed. - The API `EVP_marshal_public_key` sets the DER encoded public key from a EVP PKEY - The API `EVP_marshal_private_key` sets the DER encoded full private key representation (it cannot marshal the seed, as this cannot be derived from the full private key representation. ### Testing: To provide KAT for keys from seeds I have included the `Appendix C. Examples` from https://datatracker.ietf.org/doc/draft-ietf-lamps-dilithium-certificates/. For each provided ML-DSA parameter set, we generate the example private key provided from seed, and check that the corresponding public key matches that in the specification. These tests are in `PQDSAParameterTest` namely, `ParsePrivateKey`. These examples show how much shorter ML-DSA-87 private keys are in seed format: ``` -----BEGIN PRIVATE KEY----- MDICAQAwCwYJYIZIAWUDBAMTBCAAAQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRob HB0eHw== -----END PRIVATE KEY----- ``` By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license and the ISC license. --- crypto/evp_extra/p_pqdsa.c | 22 ++- crypto/evp_extra/p_pqdsa_asn1.c | 41 +++--- crypto/evp_extra/p_pqdsa_test.cc | 239 ++++++++++++++++++++++++++++++- crypto/pqdsa/internal.h | 5 + crypto/pqdsa/pqdsa.c | 53 +++++++ include/openssl/evp.h | 6 +- 6 files changed, 331 insertions(+), 35 deletions(-) diff --git a/crypto/evp_extra/p_pqdsa.c b/crypto/evp_extra/p_pqdsa.c index 57584765fe..612ea0e69d 100644 --- a/crypto/evp_extra/p_pqdsa.c +++ b/crypto/evp_extra/p_pqdsa.c @@ -302,21 +302,31 @@ EVP_PKEY *EVP_PKEY_pqdsa_new_raw_private_key(int nid, const uint8_t *in, size_t EVP_PKEY *ret = EVP_PKEY_pqdsa_new(nid); if (ret == NULL || ret->pkey.pqdsa_key == NULL) { - // EVP_PKEY_kem_new sets the appropriate error. + // EVP_PKEY_pqdsa_new sets the appropriate error. goto err; } - const PQDSA *pqdsa = PQDSA_KEY_get0_dsa(ret->pkey.pqdsa_key); - if (pqdsa->private_key_len != len) { + // Get PQDSA instance and validate lengths + const PQDSA *pqdsa = PQDSA_KEY_get0_dsa(ret->pkey.pqdsa_key); + if (len != pqdsa->private_key_len && len != pqdsa->keygen_seed_len) { OPENSSL_PUT_ERROR(EVP, EVP_R_INVALID_BUFFER_SIZE); goto err; } CBS cbs; CBS_init(&cbs, in, len); - if (!PQDSA_KEY_set_raw_private_key(ret->pkey.pqdsa_key, &cbs)) { - // PQDSA_KEY_set_raw_private_key sets the appropriate error. - goto err; + + // Set key based on input length + if (len == pqdsa->private_key_len) { + if (!PQDSA_KEY_set_raw_private_key(ret->pkey.pqdsa_key, &cbs)) { + // PQDSA_KEY_set_raw_private_key sets the appropriate error. + goto err; + } + } else if (len == pqdsa->keygen_seed_len) { + if (!PQDSA_KEY_set_raw_keypair_from_seed(ret->pkey.pqdsa_key, &cbs)) { + // PQDSA_KEY_set_raw_keypair_from_seed sets the appropriate error. + goto err; + } } return ret; diff --git a/crypto/evp_extra/p_pqdsa_asn1.c b/crypto/evp_extra/p_pqdsa_asn1.c index aa47734593..29a6ba9e7a 100644 --- a/crypto/evp_extra/p_pqdsa_asn1.c +++ b/crypto/evp_extra/p_pqdsa_asn1.c @@ -153,31 +153,30 @@ static int pqdsa_priv_decode(EVP_PKEY *out, CBS *params, CBS *key, CBS *pubkey) return 0; } - // Set the private key - if (!PQDSA_KEY_set_raw_private_key(out->pkey.pqdsa_key, key)) { - OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); - return 0; - } - - // Create buffers to store public key based on size - size_t pk_len = out->pkey.pqdsa_key->pqdsa->public_key_len; - uint8_t *public_key = OPENSSL_malloc(pk_len); - - if (public_key == NULL) { - OPENSSL_PUT_ERROR(EVP, ERR_R_MALLOC_FAILURE); + // check the size of the provided input against the private key and seed len + if (CBS_len(key) != out->pkey.pqdsa_key->pqdsa->private_key_len && + CBS_len(key) != out->pkey.pqdsa_key->pqdsa->keygen_seed_len) { + OPENSSL_PUT_ERROR(EVP, EVP_R_INVALID_BUFFER_SIZE); return 0; } - // Construct the public key from the private key - if (!out->pkey.pqdsa_key->pqdsa->method->pqdsa_pack_pk_from_sk( - public_key, CBS_data(key))) { - OPENSSL_free(public_key); - OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); - return 0; + // See https://datatracker.ietf.org/doc/draft-ietf-lamps-dilithium-certificates/ + // The caller can either provide the full key of size |private_key_len| or + // |keygen_seed_len|. + if (CBS_len(key) == out->pkey.pqdsa_key->pqdsa->private_key_len) { + + // Set the private key + if (!PQDSA_KEY_set_raw_private_key(out->pkey.pqdsa_key, key)) { + // PQDSA_KEY_set_raw_private_key sets the appropriate error. + return 0; + } + + } else if (CBS_len(key) == out->pkey.pqdsa_key->pqdsa->keygen_seed_len) { + if (!PQDSA_KEY_set_raw_keypair_from_seed(out->pkey.pqdsa_key, key)) { + // PQDSA_KEY_set_raw_keypair_from_seed sets the appropriate error. + return 0; + } } - - out->pkey.pqdsa_key->public_key = public_key; - return 1; } diff --git a/crypto/evp_extra/p_pqdsa_test.cc b/crypto/evp_extra/p_pqdsa_test.cc index a01e27bb0d..bc1833c9eb 100644 --- a/crypto/evp_extra/p_pqdsa_test.cc +++ b/crypto/evp_extra/p_pqdsa_test.cc @@ -3,10 +3,12 @@ #include #include +#include #include #include #include #include +#include #include "../test/test_util.h" #include @@ -944,6 +946,165 @@ static const uint8_t mldsa87kPublicKeySPKI[] = { 0x67, 0x46, 0xC8, 0x68, 0xB7, 0x65, 0x05, 0x20, 0x02, 0x70, 0xDA, 0x6B, 0xC7, 0x34}; +// https://datatracker.ietf.org/doc/draft-ietf-lamps-dilithium-certificates/06/ +// C.2. Example Public Key +const char *mldsa_44_pub_pem_str = +"-----BEGIN PUBLIC KEY-----\n" +"MIIFMjALBglghkgBZQMEAxEDggUhANeytHJUquDbReeTDUqY0sl9jxOX0Xidr6Fw\n" +"JLMW6b7JT8mUbULxm3mnQTu6oz5xSctC7VEVaTrAQfrLmIretf4OHYYxGEmVtZLD\n" +"l9IpTi4U+QqkFLo4JomaxD9MzKy8JumoMrlRGNXLQzy++WYLABOOCBf2HnYsonTD\n" +"atVU6yKqwRYuSrAay6HjjE79j4C2WzM9D3LlXf5xzpweu5iJ58VhBsD9c4A6Kuz+\n" +"r97XqjyyztpU0SvYzTanjPl1lDtHq9JeiArEUuV0LtHo0agq+oblkMdYwVrk0oQN\n" +"kryhpQkPQElll/yn2LlRPxob2m6VCqqY3kZ1B9Sk9aTwWZIWWCw1cvYu2okFqzWB\n" +"ZwxKAnd6M+DKcpX9j0/20aCjp2g9ZfX19/xg2gI+gmxfkhRMAvfRuhB1mHVT6pNn\n" +"/NdtmQt/qZzUWv24g21D5Fn1GH3wWEeXCaAepoNZNfpwRgmQzT3BukAbqUurHd5B\n" +"rGerMxncrKBgSNTE7vJ+4TqcF9BTj0MPLWQtwkFWYN54h32NirxyUjl4wELkKF9D\n" +"GYRsRBJiQpdoRMEOVWuiFbWnGeWdDGsqltOYWQcf3MLN51JKe+2uVOhbMY6FTo/i\n" +"svPt+slxkSgnCq/R5QRMOk/a/Z/zH5B4S46ORZYUSg2vWGUR09mWK56pWvGXtOX8\n" +"YPKx7RXeOlvvX4m9x52RBR2bKBbnT6VFMe/cHL501EiFf0drzVjyHAtlOzt2pOB2\n" +"plWaMCcYVVzGP3SFmqurkl8COGHKjND3utsocfZ9VTJtdFETWtRfShumkRj7ssij\n" +"DuyTku8/l3Bmya3VxxDMZHsVFNIX2VjHAXw+kP0gwE5nS5BIbpNwoxoAHTL0c5ee\n" +"SQZ0nn5Hf6C3RQj4pfI3gxK4PCW9OIygsP/3R4uvQrcWZ+2qyXxGsSlkPlhuWwVa\n" +"DCEZRtTzbmdb7Vhg+gQqMV2YJhZNapI3w1pfv0lUkKW9TfJIuVxKrneEtgVnMWas\n" +"QkW1tLCCoJ6TI+YvIHjFt2eDRG3v1zatOjcC1JsImESQCmGDM5e8RBmzDXqXoLOH\n" +"wZEUdMTUG1PjKpd6y28Op122W7OeWecB52lX3vby1EVZwxp3EitSBOO1whnxaIsU\n" +"7QvAuAGz5ugtzUPpwOn0F0TNmBW9G8iCDYuxI/BPrNGxtoXdWisbjbvz7ZM2cPCV\n" +"oYC08ZLQixC4+rvfzCskUY4y7qCl4MkEyoRHgAg/OwzS0Li2r2e8NVuUlAJdx7Cn\n" +"j6gOOi2/61EyiFHWB4GY6Uk2Ua54fsAlH5Irow6fUd9iptcnhM890gU5MXbfoySl\n" +"Er2Ulwo23TSlFKhnkfDrNvAUWwmrZGUbSgMTsplhGiocSIkWJ1mHaKMRQGC6RENI\n" +"bfUVIqHOiLMJhcIW+ObtF43VZ7MEoNTK+6iCooNC8XqaomrljbYwCD0sNY/fVmw/\n" +"XWKkKFZ7yeqM6VyqDzVHSwv6jzOaJQq0388gg76O77wQVeGP4VNw7ssmBWbYP/Br\n" +"IRquxDyim1TM0A+IFaJGXvC0ZRXMfkHzEk8J7/9zkwmrWLKaFFmgC85QOOk4yWeP\n" +"cusOTuX9quZtn4Vz/Jf8QrSVn0v4th14Qz6GsDNdbpGRxNi/SHs5BcEIz9asJLDO\n" +"t9y3z1H4TQ7Wh7lerrHFM8BvDZcCPZKnCCWDe1m6bLfU5WsKh8IDhiro8xW6WSXo\n" +"7e+meTaaIgJ2YVHxapZfn4Hs52zAcLVYaeTbl4TPBcgwsyQsgxI=\n" +"-----END PUBLIC KEY-----\n"; + +const char *mldsa_65_pub_pem_str = +"-----BEGIN PUBLIC KEY-----\n" +"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\n" +"HgLy6ERP\n" +"-----END PUBLIC KEY-----\n"; + +const char *mldsa_87_pub_pem_str = +"-----BEGIN PUBLIC KEY-----\n" +"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\n" +"5VvgYiIvi6kKl0BzMmsxISrs4KNKYA==\n" +"-----END PUBLIC KEY-----\n"; + +// https://datatracker.ietf.org/doc/draft-ietf-lamps-dilithium-certificates/06/ +// C.1. Example Private Key +const char *mldsa_44_priv_pem_str = +"-----BEGIN PRIVATE KEY-----\n" +"MDICAQAwCwYJYIZIAWUDBAMRBCAAAQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRob\n" +"HB0eHw==\n" +"-----END PRIVATE KEY-----\n"; + +const char *mldsa_65_priv_pem_str = +"-----BEGIN PRIVATE KEY-----\n" +"MDICAQAwCwYJYIZIAWUDBAMSBCAAAQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRob\n" +"HB0eHw==\n" +"-----END PRIVATE KEY-----\n"; + +const char *mldsa_87_priv_pem_str = +"-----BEGIN PRIVATE KEY-----\n" +"MDICAQAwCwYJYIZIAWUDBAMTBCAAAQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRob\n" +"HB0eHw==\n" +"-----END PRIVATE KEY-----\n"; + struct PQDSATestVector { const char name[20]; const int nid; @@ -954,6 +1115,8 @@ struct PQDSATestVector { const uint8_t *kPublicKey; const uint8_t *kPublicKeySPKI; const size_t kPublicKeySPKI_len; + const char *public_pem_str; + const char *private_pem_str; int (*keygen)(uint8_t *public_key, uint8_t *private_key, const uint8_t *seed); @@ -1004,6 +1167,8 @@ static const struct PQDSATestVector parameterSet[] = { mldsa44kPublicKey, mldsa44kPublicKeySPKI, 1334, + mldsa_44_pub_pem_str, + mldsa_44_priv_pem_str, ml_dsa_44_keypair_internal, ml_dsa_44_sign_internal, ml_dsa_44_verify_internal, @@ -1019,6 +1184,8 @@ static const struct PQDSATestVector parameterSet[] = { mldsa65kPublicKey, mldsa65kPublicKeySPKI, 1974, + mldsa_65_pub_pem_str, + mldsa_65_priv_pem_str, ml_dsa_65_keypair_internal, ml_dsa_65_sign_internal, ml_dsa_65_verify_internal, @@ -1034,6 +1201,8 @@ static const struct PQDSATestVector parameterSet[] = { mldsa87kPublicKey, mldsa87kPublicKeySPKI, 2614, + mldsa_87_pub_pem_str, + mldsa_87_priv_pem_str, ml_dsa_87_keypair_internal, ml_dsa_87_sign_internal, ml_dsa_87_verify_internal, @@ -1287,22 +1456,20 @@ TEST_P(PQDSAParameterTest, RawFunctions) { EXPECT_NE(public_pkey->pkey.pqdsa_key->public_key, nullptr); EXPECT_EQ(public_pkey->pkey.pqdsa_key->private_key, nullptr); - // check that private key is present and public key is not present in private_key + // check that calling EVP_PKEY_pqdsa_new_raw_private_key populates both the + // public and private key ASSERT_NE(private_pkey, nullptr); - EXPECT_EQ(private_pkey->pkey.pqdsa_key->public_key, nullptr); + EXPECT_NE(private_pkey->pkey.pqdsa_key->public_key, nullptr); EXPECT_NE(private_pkey->pkey.pqdsa_key->private_key, nullptr); // ---- 5. Test get_raw public/private failure modes ---- uint8_t *buf = nullptr; size_t buf_size; - // Attempting to get a public/private key that is not present must fail correctly + // Attempting to get a private key that is not present must fail correctly EXPECT_FALSE(EVP_PKEY_get_raw_private_key(public_pkey.get(), buf, &buf_size)); GET_ERR_AND_CHECK_REASON(EVP_R_NOT_A_PRIVATE_KEY); - EXPECT_FALSE(EVP_PKEY_get_raw_public_key(private_pkey.get(), buf, &buf_size)); - GET_ERR_AND_CHECK_REASON(EVP_R_OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE); - // Null PKEY must fail correctly. ASSERT_FALSE(EVP_PKEY_get_raw_public_key(nullptr, pk.data(), &pk_len)); GET_ERR_AND_CHECK_REASON(EVP_R_OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE); @@ -1532,6 +1699,66 @@ TEST_P(PQDSAParameterTest, ParsePublicKey) { ASSERT_TRUE(pkey_from_der); } +// Helper function that: +// 1. Creates a BIO +// 2. Reads the provided |pem_string| into bio +// 3. Reads the PEM into DER encoding +// 4. Returns the DER data and length +static bool PEM_to_DER(const char* pem_str, uint8_t** out_der, long* out_der_len) { + char *name = nullptr; + char *header = nullptr; + + // Create BIO from memory + bssl::UniquePtr bio(BIO_new_mem_buf(pem_str, strlen(pem_str))); + if (!bio) { + return false; + } + + // Read PEM into DER + if (PEM_read_bio(bio.get(), &name, &header, out_der, out_der_len) <= 0) { + OPENSSL_free(name); + OPENSSL_free(header); + OPENSSL_free(*out_der); + *out_der = nullptr; + return false; + } + + OPENSSL_free(name); + OPENSSL_free(header); + return true; +} + +TEST_P(PQDSAParameterTest, ParsePrivateKey) { + // ---- 1. Setup phase: parse provided public/private from PEM strings ---- + CBS cbs_pub, cbs_priv; + uint8_t *der_pub = nullptr, *der_priv = nullptr; + long der_pub_len = 0, der_priv_len = 0; + + ASSERT_TRUE(PEM_to_DER(GetParam().public_pem_str, &der_pub, &der_pub_len)); + ASSERT_TRUE(PEM_to_DER(GetParam().private_pem_str, &der_priv, &der_priv_len)); + + CBS_init(&cbs_pub, der_pub, der_pub_len); + CBS_init(&cbs_priv, der_priv, der_priv_len); + + // ---- 2. Attempt to parse private key ---- + bssl::UniquePtr pkey1(EVP_parse_private_key(&cbs_priv)); + ASSERT_TRUE(pkey1); + + // ---- 3. Attempt to parse public key ---- + bssl::UniquePtr pkey2(EVP_parse_public_key(&cbs_pub)); + ASSERT_TRUE(pkey2); + + // ---- 4. Compare public keys ---- + // EVP_parse_private_key will populate both public and private key, we verify + // that the public key calculated by EVP_parse_private_key is equivalent to + // the public key that was parsed from PEM. + ASSERT_EQ(1, EVP_PKEY_cmp(pkey1.get(), pkey2.get())); + + // Clean up + OPENSSL_free(der_pub); + OPENSSL_free(der_priv); +} + TEST_P(PQDSAParameterTest, KeyConsistencyTest) { // This test: generates a random PQDSA key pair extracts the private key, and // runs the public key calculator function to populate the coresponding public key. diff --git a/crypto/pqdsa/internal.h b/crypto/pqdsa/internal.h index 7a3aabdd66..390489a8f0 100644 --- a/crypto/pqdsa/internal.h +++ b/crypto/pqdsa/internal.h @@ -15,6 +15,10 @@ typedef struct { int (*pqdsa_keygen)(uint8_t *public_key, uint8_t *private_key); + int (*pqdsa_keygen_internal)(uint8_t *public_key, + uint8_t *private_key, + const uint8_t *seed); + int (*pqdsa_sign_message)(const uint8_t *private_key, uint8_t *sig, size_t *sig_len, @@ -76,6 +80,7 @@ PQDSA_KEY *PQDSA_KEY_new(void); void PQDSA_KEY_free(PQDSA_KEY *key); int EVP_PKEY_pqdsa_set_params(EVP_PKEY *pkey, int nid); +int PQDSA_KEY_set_raw_keypair_from_seed(PQDSA_KEY *key, CBS *in); int PQDSA_KEY_set_raw_public_key(PQDSA_KEY *key, CBS *in); int PQDSA_KEY_set_raw_private_key(PQDSA_KEY *key, CBS *in); #if defined(__cplusplus) diff --git a/crypto/pqdsa/pqdsa.c b/crypto/pqdsa/pqdsa.c index 49f5f9d58d..84e3bb09f9 100644 --- a/crypto/pqdsa/pqdsa.c +++ b/crypto/pqdsa/pqdsa.c @@ -81,6 +81,38 @@ int PQDSA_KEY_set_raw_public_key(PQDSA_KEY *key, CBS *in) { return 1; } +int PQDSA_KEY_set_raw_keypair_from_seed(PQDSA_KEY *key, CBS *in) { + // Check if the parsed length corresponds with the expected length. + if (CBS_len(in) != key->pqdsa->keygen_seed_len) { + OPENSSL_PUT_ERROR(EVP, EVP_R_INVALID_BUFFER_SIZE); + return 0; + } + + //allocate buffers to store key pair + uint8_t *public_key = OPENSSL_malloc(key->pqdsa->public_key_len); + uint8_t *private_key = OPENSSL_malloc(key->pqdsa->private_key_len); + + // check buffers are allocated + if (public_key == NULL || private_key == NULL) { + OPENSSL_PUT_ERROR(CRYPTO, ERR_R_MALLOC_FAILURE); + return 0; + } + + // attempt to generate the key from the provided seed + if (!key->pqdsa->method->pqdsa_keygen_internal(public_key, + private_key, + CBS_data(in))) { + OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); + return 0; + } + + // set the public and private key + key->public_key = public_key; + key->private_key = private_key; + + return 1; +} + int PQDSA_KEY_set_raw_private_key(PQDSA_KEY *key, CBS *in) { // Check if the parsed length corresponds with the expected length. if (CBS_len(in) != key->pqdsa->private_key_len) { @@ -93,11 +125,30 @@ int PQDSA_KEY_set_raw_private_key(PQDSA_KEY *key, CBS *in) { return 0; } + // Create buffers to store public key based on size + size_t pk_len = key->pqdsa->public_key_len; + uint8_t *public_key = OPENSSL_malloc(pk_len); + + if (public_key == NULL) { + OPENSSL_PUT_ERROR(EVP, ERR_R_MALLOC_FAILURE); + return 0; + } + + // Construct the public key from the private key + if (!key->pqdsa->method->pqdsa_pack_pk_from_sk(public_key, key->private_key)) { + OPENSSL_free(public_key); + OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); + return 0; + } + + key->public_key = public_key; + return 1; } static const PQDSA_METHOD sig_ml_dsa_44_method = { ml_dsa_44_keypair, + ml_dsa_44_keypair_internal, ml_dsa_44_sign, ml_dsa_extmu_44_sign, ml_dsa_44_verify, @@ -107,6 +158,7 @@ static const PQDSA_METHOD sig_ml_dsa_44_method = { static const PQDSA_METHOD sig_ml_dsa_65_method = { ml_dsa_65_keypair, + ml_dsa_65_keypair_internal, ml_dsa_65_sign, ml_dsa_extmu_65_sign, ml_dsa_65_verify, @@ -116,6 +168,7 @@ static const PQDSA_METHOD sig_ml_dsa_65_method = { static const PQDSA_METHOD sig_ml_dsa_87_method = { ml_dsa_87_keypair, + ml_dsa_87_keypair_internal, ml_dsa_87_sign, ml_dsa_extmu_87_sign, ml_dsa_87_verify, diff --git a/include/openssl/evp.h b/include/openssl/evp.h index d7a18271a3..306dcd7096 100644 --- a/include/openssl/evp.h +++ b/include/openssl/evp.h @@ -965,8 +965,10 @@ OPENSSL_EXPORT EVP_PKEY *EVP_PKEY_pqdsa_new_raw_public_key(int nid, const uint8_ // EVP_PKEY_pqdsa_new_raw_private_key generates a new EVP_PKEY object of type // EVP_PKEY_PQDSA, initializes the PQDSA key based on |nid| and populates the -// secret key part of the PQDSA key with the contents of |in|. It returns the -// pointer to the allocated PKEY on sucess and NULL on error. +// secret key part of the PQDSA key with the contents of |in|. If the contents +// of |in| is the private key seed, then this function will generate the +// corresponding key pair and populate both public and private parts of the PKEY. +// It returns the pointer to the allocated PKEY on sucess and NULL on error. OPENSSL_EXPORT EVP_PKEY *EVP_PKEY_pqdsa_new_raw_private_key(int nid, const uint8_t *in, size_t len); // Diffie-Hellman-specific control functions.