diff --git a/crypto/pem/pem_lib.c b/crypto/pem/pem_lib.c index 01c6d6bca7..2c9a347ba4 100644 --- a/crypto/pem/pem_lib.c +++ b/crypto/pem/pem_lib.c @@ -71,6 +71,7 @@ #include #include "../internal.h" +#include "../fipsmodule/evp/internal.h" #define MIN_LENGTH 4 @@ -146,6 +147,13 @@ static int check_pem(const char *nm, const char *name) { !strcmp(nm, PEM_STRING_DSA); } + // These correspond with the PEM strings that have "PARAMETERS". + if (!strcmp(name, PEM_STRING_PARAMETERS)) { + return !strcmp(nm, PEM_STRING_ECPARAMETERS) || + !strcmp(nm, PEM_STRING_DSAPARAMS) || + !strcmp(nm, PEM_STRING_DHPARAMS); + } + // Permit older strings if (!strcmp(nm, PEM_STRING_X509_OLD) && !strcmp(name, PEM_STRING_X509)) { diff --git a/crypto/pem/pem_pkey.c b/crypto/pem/pem_pkey.c index 2d28d6c021..0572de7bc0 100644 --- a/crypto/pem/pem_pkey.c +++ b/crypto/pem/pem_pkey.c @@ -67,6 +67,7 @@ #include #include #include +#include "../fipsmodule/evp/internal.h" EVP_PKEY *PEM_read_bio_PrivateKey(BIO *bp, EVP_PKEY **x, pem_password_cb *cb, void *u) { @@ -156,6 +157,88 @@ int PEM_write_bio_PrivateKey(BIO *bp, EVP_PKEY *x, const EVP_CIPHER *enc, return PEM_write_bio_PKCS8PrivateKey(bp, x, enc, (char *)kstr, klen, cb, u); } +EVP_PKEY *PEM_read_bio_Parameters(BIO *bio, EVP_PKEY **pkey) { + char *nm = NULL; + unsigned char *data = NULL; + long len; + if (!PEM_bytes_read_bio(&data, &len, &nm, PEM_STRING_PARAMETERS, bio, 0, + NULL)) { + return NULL; + } + const unsigned char *data_const = data; + + // Implementing the ASN1 logic here allows us to decouple the pem logic for + // |EVP_PKEY|. These correspond to the historical |param_decode| + // |EVP_PKEY_ASN1_METHOD| hooks in OpenSSL. + EVP_PKEY *ret = EVP_PKEY_new(); + if (strcmp(nm, PEM_STRING_ECPARAMETERS) == 0) { + EC_KEY *ec_key = d2i_ECParameters(NULL, &data_const, len); + if (ec_key == NULL || !EVP_PKEY_assign_EC_KEY(ret, ec_key)) { + OPENSSL_PUT_ERROR(EVP, ERR_R_EC_LIB); + EC_KEY_free(ec_key); + goto err; + } + } else if (strcmp(nm, PEM_STRING_DSAPARAMS) == 0) { + DSA *dsa = d2i_DSAparams(NULL, &data_const, len); + if (dsa == NULL || !EVP_PKEY_assign_DSA(ret, dsa)) { + OPENSSL_PUT_ERROR(EVP, ERR_R_DSA_LIB); + DSA_free(dsa); + goto err; + } + } else if (strcmp(nm, PEM_STRING_DHPARAMS) == 0) { + DH *dh = d2i_DHparams(NULL, &data_const, len); + if (dh == NULL || !EVP_PKEY_assign_DH(ret, dh)) { + OPENSSL_PUT_ERROR(EVP, ERR_R_DH_LIB); + DH_free(dh); + goto err; + } + } else { + goto err; + } + + if (pkey != NULL) { + EVP_PKEY_free(*pkey); + *pkey = ret; + } + + OPENSSL_free(nm); + OPENSSL_free(data); + return ret; + +err: + EVP_PKEY_free(ret); + OPENSSL_free(nm); + OPENSSL_free(data); + return NULL; +} + +int PEM_write_bio_Parameters(BIO *bio, EVP_PKEY *pkey) { + if (bio == NULL || pkey == NULL || pkey->ameth == NULL) { + return 0; + } + + // Implementing the ASN1 logic here allows us to decouple the pem logic for + // |EVP_PKEY|. These correspond to the historical |param_encode| + // |EVP_PKEY_ASN1_METHOD| hooks in OpenSSL. + char pem_str[80]; + switch (pkey->type) { + case EVP_PKEY_EC: + BIO_snprintf(pem_str, 80, PEM_STRING_ECPARAMETERS); + return PEM_ASN1_write_bio((i2d_of_void *)i2d_ECParameters, pem_str, bio, + pkey->pkey.ec, NULL, NULL, 0, 0, NULL); + case EVP_PKEY_DSA: + BIO_snprintf(pem_str, 80, PEM_STRING_DSAPARAMS); + return PEM_ASN1_write_bio((i2d_of_void *)i2d_DSAparams, pem_str, bio, + pkey->pkey.dsa, NULL, NULL, 0, 0, NULL); + case EVP_PKEY_DH: + BIO_snprintf(pem_str, 80, PEM_STRING_DHPARAMS); + return PEM_ASN1_write_bio((i2d_of_void *)i2d_DHparams, pem_str, bio, + pkey->pkey.dh, NULL, NULL, 0, 0, NULL); + default: + return 0; + } +} + EVP_PKEY *PEM_read_PrivateKey(FILE *fp, EVP_PKEY **x, pem_password_cb *cb, void *u) { BIO *b = BIO_new_fp(fp, BIO_NOCLOSE); diff --git a/crypto/pem/pem_test.cc b/crypto/pem/pem_test.cc index fb960f63c2..5039a494e1 100644 --- a/crypto/pem/pem_test.cc +++ b/crypto/pem/pem_test.cc @@ -25,6 +25,7 @@ #include "../test/test_util.h" +#include "openssl/rand.h" const char* SECRET = "test"; @@ -265,3 +266,103 @@ TEST(PEMTest, WriteReadECPKPem) { ASSERT_TRUE(read_group); ASSERT_EQ(EC_GROUP_cmp(EC_group_p256(), read_group.get(), nullptr), 0); } + +TEST(ParametersTest, PEMReadwrite) { + // Test |PEM_read/write_bio_Parameters| with |EC_KEY|. + bssl::UniquePtr ec_key(EC_KEY_new()); + ASSERT_TRUE(ec_key); + bssl::UniquePtr ec_group(EC_GROUP_new_by_curve_name(NID_secp384r1)); + ASSERT_TRUE(ec_group); + ASSERT_TRUE(EC_KEY_set_group(ec_key.get(), ec_group.get())); + ASSERT_TRUE(EC_KEY_generate_key(ec_key.get())); + + bssl::UniquePtr write_bio(BIO_new(BIO_s_mem())); + bssl::UniquePtr pkey(EVP_PKEY_new()); + ASSERT_TRUE(EVP_PKEY_set1_EC_KEY(pkey.get(), ec_key.get())); + EXPECT_TRUE(PEM_write_bio_Parameters(write_bio.get(), pkey.get())); + + const uint8_t *content; + size_t content_len; + BIO_mem_contents(write_bio.get(), &content, &content_len); + + bssl::UniquePtr read_bio(BIO_new_mem_buf(content, content_len)); + ASSERT_TRUE(read_bio); + bssl::UniquePtr pkey_read( + PEM_read_bio_Parameters(read_bio.get(), nullptr)); + ASSERT_TRUE(pkey_read); + + EC_KEY *pkey_eckey = EVP_PKEY_get0_EC_KEY(pkey.get()); + const EC_GROUP *orig_params = EC_KEY_get0_group(pkey_eckey); + const EC_GROUP *read_params = EC_KEY_get0_group(pkey_eckey); + ASSERT_EQ(0, EC_GROUP_cmp(orig_params, read_params, nullptr)); + + // Test |PEM_read/write_bio_Parameters| with |DH|. + bssl::UniquePtr p(BN_get_rfc3526_prime_1536(nullptr)); + ASSERT_TRUE(p); + bssl::UniquePtr g(BN_new()); + ASSERT_TRUE(g); + ASSERT_TRUE(BN_set_u64(g.get(), 2)); + bssl::UniquePtr dh(DH_new()); + ASSERT_TRUE(dh); + ASSERT_TRUE(DH_set0_pqg(dh.get(), p.release(), nullptr, g.release())); + write_bio.reset(BIO_new(BIO_s_mem())); + pkey.reset(EVP_PKEY_new()); + ASSERT_TRUE(EVP_PKEY_set1_DH(pkey.get(), dh.get())); + EXPECT_TRUE(PEM_write_bio_Parameters(write_bio.get(), pkey.get())); + + BIO_mem_contents(write_bio.get(), &content, &content_len); + read_bio.reset(BIO_new_mem_buf(content, content_len)); + ASSERT_TRUE(read_bio); + pkey_read.reset(PEM_read_bio_Parameters(read_bio.get(), nullptr)); + ASSERT_TRUE(pkey_read); + + DH *pkey_dh = EVP_PKEY_get0_DH(pkey.get()); + EXPECT_EQ(0, BN_cmp(DH_get0_p(pkey_dh), DH_get0_p(dh.get()))); + EXPECT_EQ(0, BN_cmp(DH_get0_g(pkey_dh), DH_get0_g(dh.get()))); + + // Test |PEM_read/write_bio_Parameters| with |DSA|. + bssl::UniquePtr dsa(DSA_new()); + ASSERT_TRUE(dsa); + uint8_t seed[20]; + ASSERT_TRUE(RAND_bytes(seed, sizeof(seed))); + ASSERT_TRUE(DSA_generate_parameters_ex(dsa.get(), 512, seed, sizeof(seed), + nullptr, nullptr, nullptr)); + ASSERT_TRUE(DSA_generate_key(dsa.get())); + + write_bio.reset(BIO_new(BIO_s_mem())); + pkey.reset(EVP_PKEY_new()); + ASSERT_TRUE(EVP_PKEY_set1_DSA(pkey.get(), dsa.get())); + EXPECT_TRUE(PEM_write_bio_Parameters(write_bio.get(), pkey.get())); + + BIO_mem_contents(write_bio.get(), &content, &content_len); + read_bio.reset(BIO_new_mem_buf(content, content_len)); + ASSERT_TRUE(read_bio); + pkey_read.reset(PEM_read_bio_Parameters(read_bio.get(), nullptr)); + ASSERT_TRUE(pkey_read); + + DSA *pkey_dsa = EVP_PKEY_get0_DSA(pkey.get()); + EXPECT_EQ(0, BN_cmp(DSA_get0_p(pkey_dsa), DSA_get0_p(dsa.get()))); + EXPECT_EQ(0, BN_cmp(DSA_get0_g(pkey_dsa), DSA_get0_g(dsa.get()))); +} + +const char *kRubyPemDHPARAMETERS = + "-----BEGIN DH PARAMETERS-----\n" + "MIIBCAKCAQEA7E6kBrYiyvmKAMzQ7i8WvwVk9Y/+f8S7sCTN712KkK3cqd1jhJDY" + "JbrYeNV3kUIKhPxWHhObHKpD1R84UpL+s2b55+iMd6GmL7OYmNIT/FccKhTcveab" + "VBmZT86BZKYyf45hUF9FOuUM9xPzuK3Vd8oJQvfYMCd7LPC0taAEljQLR4Edf8E6" + "YoaOffgTf5qxiwkjnlVZQc3whgnEt9FpVMvQ9eknyeGB5KHfayAc3+hUAvI3/Cr3" + "1bNveX5wInh5GDx1FGhKBZ+s1H+aedudCm7sCgRwv8lKWYGiHzObSma8A86KG+MD" + "7Lo5JquQ3DlBodj3IDyPrxIv96lvRPFtAwIBAg==\n" + "-----END DH PARAMETERS-----\n"; + +TEST(ParametersTest, RubyDHFile) { + bssl::UniquePtr read_bio( + BIO_new_mem_buf(kRubyPemDHPARAMETERS, strlen(kRubyPemDHPARAMETERS))); + ASSERT_TRUE(read_bio); + bssl::UniquePtr pkey_read( + PEM_read_bio_Parameters(read_bio.get(), nullptr)); + ASSERT_TRUE(pkey_read); + bssl::UniquePtr dh(EVP_PKEY_get1_DH(pkey_read.get())); + EXPECT_TRUE(dh); + EXPECT_EQ(DH_num_bits(dh.get()), 2048u); +} diff --git a/include/openssl/pem.h b/include/openssl/pem.h index 0420abf9bc..a9807a9dbd 100644 --- a/include/openssl/pem.h +++ b/include/openssl/pem.h @@ -107,6 +107,7 @@ extern "C" { #define PEM_STRING_ECDSA_PUBLIC "ECDSA PUBLIC KEY" #define PEM_STRING_ECPARAMETERS "EC PARAMETERS" #define PEM_STRING_ECPRIVATEKEY "EC PRIVATE KEY" +#define PEM_STRING_PARAMETERS "PARAMETERS" #define PEM_STRING_CMS "CMS" // enc_type is one off @@ -473,6 +474,19 @@ OPENSSL_EXPORT int PEM_write_PKCS8PrivateKey(FILE *fp, const EVP_PKEY *x, int klen, pem_password_cb *cd, void *u); +// PEM_read_bio_Parameters is a generic PEM deserialization function that +// parses the public "parameters" in |bio| and returns a corresponding +// |EVP_PKEY|. If |*pkey| is non-null, the original |*pkey| is freed and the +// returned |EVP_PKEY| is also written to |*pkey|. This is only supported with +// |EVP_PKEY_EC|, |EVP_PKEY_DH|, and |EVP_PKEY_DSA|. +OPENSSL_EXPORT EVP_PKEY *PEM_read_bio_Parameters(BIO *bio, EVP_PKEY **pkey); + +// PEM_write_bio_Parameters is a generic PEM serialization function that parses +// the public "parameters" of |pkey| to |bio|. It returns 1 on success or 0 on +// failure. This is only supported with |EVP_PKEY_EC|, |EVP_PKEY_DH|, and +// |EVP_PKEY_DSA|. +OPENSSL_EXPORT int PEM_write_bio_Parameters(BIO *bio, EVP_PKEY *pkey); + // PEM_read_bio_ECPKParameters deserializes the PEM file written in |bio| // according to |ECPKParameters| in RFC 3279. It returns the |EC_GROUP| // corresponding to deserialized output and also writes it to |out_group|. Only