From: Viktor Dukhovni Date: Wed, 9 Apr 2025 07:55:03 +0000 (+1000) Subject: Implement i2d_PKCS8PrivateKey X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=8d2e4d6d8c927f05948e048fcbf62982feaf11b4;p=thirdparty%2Fopenssl.git Implement i2d_PKCS8PrivateKey Added `i2d_PKCS8PrivateKey(3)` API to complement `i2d_PrivateKey(3)`, the former always outputs PKCS#8. Extended endecoder_test.c to check that `i2d_PKCS8PrivateKey()` produces the expected PKCS#8 output. Reviewed-by: Shane Lontis Reviewed-by: Matt Caswell (Merged from https://github.com/openssl/openssl/pull/27309) --- diff --git a/crypto/asn1/i2d_evp.c b/crypto/asn1/i2d_evp.c index 106ea152733..70af08ab0fc 100644 --- a/crypto/asn1/i2d_evp.c +++ b/crypto/asn1/i2d_evp.c @@ -91,20 +91,25 @@ int i2d_KeyParams_bio(BIO *bp, const EVP_PKEY *pkey) return ASN1_i2d_bio_of(EVP_PKEY, i2d_KeyParams, bp, pkey); } -int i2d_PrivateKey(const EVP_PKEY *a, unsigned char **pp) +static int +i2d_PrivateKey_impl(const EVP_PKEY *a, unsigned char **pp, int traditional) { if (evp_pkey_is_provided(a)) { - static const struct type_and_structure_st output_info[] = { + static const struct type_and_structure_st trad_output_info[] = { { "DER", "type-specific" }, { "DER", "PrivateKeyInfo" }, { NULL, } }; + const struct type_and_structure_st *oi = trad_output_info; - return i2d_provided(a, EVP_PKEY_KEYPAIR, output_info, pp); + if (!traditional) + ++oi; + return i2d_provided(a, EVP_PKEY_KEYPAIR, oi, pp); } - if (a->ameth != NULL && a->ameth->old_priv_encode != NULL) { + + if (traditional && a->ameth != NULL && a->ameth->old_priv_encode != NULL) return a->ameth->old_priv_encode(a, pp); - } + if (a->ameth != NULL && a->ameth->priv_encode != NULL) { PKCS8_PRIV_KEY_INFO *p8 = EVP_PKEY2PKCS8(a); int ret = 0; @@ -119,6 +124,16 @@ int i2d_PrivateKey(const EVP_PKEY *a, unsigned char **pp) return -1; } +int i2d_PrivateKey(const EVP_PKEY *a, unsigned char **pp) +{ + return i2d_PrivateKey_impl(a, pp, 1); +} + +int i2d_PKCS8PrivateKey(const EVP_PKEY *a, unsigned char **pp) +{ + return i2d_PrivateKey_impl(a, pp, 0); +} + int i2d_PublicKey(const EVP_PKEY *a, unsigned char **pp) { if (evp_pkey_is_provided(a)) { diff --git a/doc/man3/d2i_PKCS8PrivateKey_bio.pod b/doc/man3/d2i_PKCS8PrivateKey_bio.pod index 55ec3465750..0408f6d4d90 100644 --- a/doc/man3/d2i_PKCS8PrivateKey_bio.pod +++ b/doc/man3/d2i_PKCS8PrivateKey_bio.pod @@ -41,9 +41,12 @@ corresponding B function as described in L. These functions are currently the only way to store encrypted private keys using DER format. -Currently all the functions use BIOs or FILE pointers, there are no functions which -work directly on memory: this can be readily worked around by converting the buffers -to memory BIOs, see L for details. +Currently all the functions use BIOs or FILE pointers, there are no functions +which support password-protection and work directly on memory: this can be +readily worked around by converting the buffers to memory BIOs, see +L for details. +If password-protection is not required, the L function +encodes a private key to an unencrypted DER B form. These functions make no assumption regarding the pass phrase received from the password callback. @@ -59,9 +62,14 @@ and i2d_PKCS8PrivateKey_nid_fp() return 1 on success or 0 on error. =head1 SEE ALSO +L, L, L +=head1 HISTORY + +L was added in OpenSSL 3.6. + =head1 COPYRIGHT Copyright 2002-2018 The OpenSSL Project Authors. All Rights Reserved. diff --git a/doc/man3/d2i_PrivateKey.pod b/doc/man3/d2i_PrivateKey.pod index fe78d5bc6f1..492a5cc2e4e 100644 --- a/doc/man3/d2i_PrivateKey.pod +++ b/doc/man3/d2i_PrivateKey.pod @@ -3,10 +3,10 @@ =head1 NAME d2i_PrivateKey_ex, d2i_PrivateKey, d2i_PublicKey, d2i_KeyParams, -d2i_AutoPrivateKey_ex, d2i_AutoPrivateKey, i2d_PrivateKey, i2d_PublicKey, -i2d_KeyParams, i2d_KeyParams_bio, d2i_PrivateKey_ex_bio, d2i_PrivateKey_bio, -d2i_PrivateKey_ex_fp, d2i_PrivateKey_fp, d2i_KeyParams_bio, i2d_PrivateKey_bio, -i2d_PrivateKey_fp +d2i_AutoPrivateKey_ex, d2i_AutoPrivateKey, i2d_PrivateKey, +i2d_PKCS8PrivateKey, i2d_PublicKey, i2d_KeyParams, i2d_KeyParams_bio, +d2i_PrivateKey_ex_bio, d2i_PrivateKey_bio, d2i_PrivateKey_ex_fp, +d2i_PrivateKey_fp, d2i_KeyParams_bio, i2d_PrivateKey_bio, i2d_PrivateKey_fp - decode and encode functions for reading and saving EVP_PKEY structures =head1 SYNOPSIS @@ -29,6 +29,7 @@ i2d_PrivateKey_fp long length); int i2d_PrivateKey(const EVP_PKEY *a, unsigned char **pp); + int i2d_PKCS8PrivateKey(const EVP_PKEY *a, unsigned char **pp); int i2d_PublicKey(const EVP_PKEY *a, unsigned char **pp); int i2d_KeyParams(const EVP_PKEY *a, unsigned char **pp); int i2d_KeyParams_bio(BIO *bp, const EVP_PKEY *pkey); @@ -77,6 +78,8 @@ to automatically detect the private key format. i2d_PrivateKey() encodes I. It uses a key specific format or, if none is defined for that key type, PKCS#8 unencrypted PrivateKeyInfo format. +i2d_PKCS8PrivateKey() does the same using only the PKCS#8 unencrypted +PrivateKeyInfo format. i2d_PublicKey() does the same for public keys. i2d_KeyParams() does the same for key parameters. These functions are similar to the d2i_X509() functions; see L. @@ -106,9 +109,9 @@ d2i_PrivateKey_ex_fp(), d2i_PrivateKey_fp(), d2i_PublicKey(), d2i_KeyParams() and d2i_KeyParams_bio() functions return a valid B structure or NULL if an error occurs. The error code can be obtained by calling L. -i2d_PrivateKey(), i2d_PublicKey() and i2d_KeyParams() return the number of -bytes successfully encoded or a negative value if an error occurs. The error -code can be obtained by calling L. +i2d_PrivateKey(), i2d_PKCS8PrivateKey(), i2d_PublicKey() and i2d_KeyParams() +return the number of bytes successfully encoded or a negative value if an error +occurs. The error code can be obtained by calling L. i2d_PrivateKey_bio(), i2d_PrivateKey_fp() and i2d_KeyParams_bio() return 1 if successfully encoded or zero if an error occurs. @@ -123,6 +126,8 @@ L d2i_PrivateKey_ex(), d2i_PrivateKey_ex_bio(), d2i_PrivateKey_ex_fp(), and d2i_AutoPrivateKey_ex() were added in OpenSSL 3.0. +i2d_PKCS8PrivateKey() was added in OpenSSL 3.6. + =head1 COPYRIGHT Copyright 2017-2021 The OpenSSL Project Authors. All Rights Reserved. diff --git a/include/openssl/evp.h b/include/openssl/evp.h index e5da1e64151..8e38ab29ea3 100644 --- a/include/openssl/evp.h +++ b/include/openssl/evp.h @@ -1457,6 +1457,7 @@ EVP_PKEY *d2i_AutoPrivateKey_ex(EVP_PKEY **a, const unsigned char **pp, EVP_PKEY *d2i_AutoPrivateKey(EVP_PKEY **a, const unsigned char **pp, long length); int i2d_PrivateKey(const EVP_PKEY *a, unsigned char **pp); +int i2d_PKCS8PrivateKey(const EVP_PKEY *a, unsigned char **pp); int i2d_KeyParams(const EVP_PKEY *a, unsigned char **pp); EVP_PKEY *d2i_KeyParams(int type, EVP_PKEY **a, const unsigned char **pp, diff --git a/test/endecode_test.c b/test/endecode_test.c index 028deb4ed13..d6ee5a59b91 100644 --- a/test/endecode_test.c +++ b/test/endecode_test.c @@ -286,6 +286,34 @@ static int encode_EVP_PKEY_prov(const char *file, const int line, return ok; } +static int encode_EVP_PKEY_i2d(const char *unused_file, + const int unused_line, + void **encoded, long *encoded_len, + void *object, int unused_selection, + const char *unused_output_type, + const char *unused_output_structure, + const char *unused_pass, + const char *unused_pcipher) +{ + EVP_PKEY *pkey = object; + unsigned char *buf = NULL, *p; + int len, ok = 0; + + if (!TEST_int_gt((len = i2d_PKCS8PrivateKey(pkey, NULL)), 0) + || !TEST_ptr(p = buf = OPENSSL_malloc(len)) + || !TEST_int_eq(i2d_PKCS8PrivateKey(pkey, &p), len) + || !TEST_int_eq((int)(p - buf), len)) + goto end; + + *encoded = buf; + *encoded_len = len; + buf = NULL; + ok = 1; + end: + OPENSSL_free(buf); + return ok; +} + static int decode_EVP_PKEY_prov(const char *file, const int line, void **object, void *encoded, long encoded_len, const char *input_type, @@ -567,6 +595,17 @@ static int test_unprotected_via_DER(const char *type, EVP_PKEY *key, int fips) dump_der, fips ? 0 : FLAG_FAIL_IF_FIPS); } +static int test_unprotected_via_i2d(const char *type, EVP_PKEY *key, int fips) +{ + return test_encode_decode(__FILE__, __LINE__, type, key, + OSSL_KEYMGMT_SELECT_KEYPAIR + | OSSL_KEYMGMT_SELECT_ALL_PARAMETERS, + "DER", "PrivateKeyInfo", NULL, NULL, + encode_EVP_PKEY_i2d, decode_EVP_PKEY_prov, + test_mem, check_unprotected_PKCS8_DER, + dump_der, fips ? 0 : FLAG_FAIL_IF_FIPS); +} + static int check_unprotected_PKCS8_PEM(const char *file, const int line, const char *type, const void *data, size_t data_len) @@ -906,6 +945,10 @@ static int test_public_via_MSBLOB(const char *type, EVP_PKEY *key) { \ return test_unprotected_via_DER(KEYTYPEstr, key_##KEYTYPE, fips); \ } \ + static int test_unprotected_##KEYTYPE##_via_i2d(void) \ + { \ + return test_unprotected_via_i2d(KEYTYPEstr, key_##KEYTYPE, fips); \ + } \ static int test_unprotected_##KEYTYPE##_via_PEM(void) \ { \ return test_unprotected_via_PEM(KEYTYPEstr, key_##KEYTYPE, fips); \ @@ -929,6 +972,7 @@ static int test_public_via_MSBLOB(const char *type, EVP_PKEY *key) #define ADD_TEST_SUITE(KEYTYPE) \ ADD_TEST(test_unprotected_##KEYTYPE##_via_DER); \ + ADD_TEST(test_unprotected_##KEYTYPE##_via_i2d); \ ADD_TEST(test_unprotected_##KEYTYPE##_via_PEM); \ ADD_TEST(test_protected_##KEYTYPE##_via_DER); \ ADD_TEST(test_protected_##KEYTYPE##_via_PEM); \ diff --git a/util/libcrypto.num b/util/libcrypto.num index 322c7b42d61..0242023c01f 100644 --- a/util/libcrypto.num +++ b/util/libcrypto.num @@ -5925,3 +5925,4 @@ OSSL_AA_DIST_POINT_new ? 3_5_0 EXIST::FUNCTION: OSSL_AA_DIST_POINT_it ? 3_5_0 EXIST::FUNCTION: PEM_ASN1_write_bio_ctx ? 3_5_0 EXIST::FUNCTION: OPENSSL_sk_set_thunks ? 3_6_0 EXIST::FUNCTION: +i2d_PKCS8PrivateKey ? 3_6_0 EXIST::FUNCTION: