&& ossl_ml_dsa_sk_encode(out);
err:
- if (out->seed != NULL && (out->prov_flags & ML_DSA_KEY_RETAIN_SEED) == 0) {
- OPENSSL_clear_free(out->seed, ML_DSA_SEED_BYTES);
- out->seed = NULL;
- }
EVP_MD_CTX_free(md_ctx);
OPENSSL_cleanse(augmented_seed, sizeof(augmented_seed));
OPENSSL_cleanse(expanded_seed, sizeof(expanded_seed));
/* Save |z| portion of seed for "implicit rejection" on failure. */
memcpy(key->z, seed + ML_KEM_RANDOM_BYTES, ML_KEM_RANDOM_BYTES);
- /* Optionally save the |d| portion of the seed */
+ /* Save the |d| portion of the seed */
key->d = key->z + ML_KEM_RANDOM_BYTES;
- if (key->prov_flags & ML_KEM_KEY_RETAIN_SEED) {
- memcpy(key->d, seed, ML_KEM_RANDOM_BYTES);
- } else {
- OPENSSL_cleanse(key->d, ML_KEM_RANDOM_BYTES);
- key->d = NULL;
- }
+ memcpy(key->d, seed, ML_KEM_RANDOM_BYTES);
ret = 1;
end:
Use L<EVP_PKEY_CTX_set_params(3)> after calling L<EVP_PKEY_keygen_init(3)>.
+=head2 Encoder Parameters
+
+=over 4
+
+=item "output_formats" (B<OSSL_PKEY_PARAM_OUTPUT_FORMATS>) <UTF8 string>
+
+This parameter can be used with L<OSSL_ENCODER_CTX_new_for_pkey(3)>
+and L<OSSL_ENCODER_CTX_set_params(3)> to select the preferred B<PKCS#8> output
+formats of a private key.
+When this parameter is either not specified or empty, the behaviour is
+determined by the provider configuration parameters described below.
+Otherwise, it has the same syntax and behaviour as the provider configuration
+B<ml-dsa.output_formats> parameter, except that the B<ml-dsa.retain-seed>
+parameter is then ignored, i.e. the C<seed-only> and C<seed-priv> formats
+remain available so long as the seed was initially known, regardless of any
+setting of B<ml-dsa.retain-seed>.
+
+=back
+
=head2 Common ML-DSA parameters
In addition to the common parameters that all keytypes should support (see
=item C<ml-dsa.retain_seed> (B<OSSL_PKEY_PARAM_ML_DSA_RETAIN_SEED>) <UTF8 string>
When set to a string representing a false boolean value (see
-L<OSSL_PROVIDER_conf_get_bool(3)>), the seed will not be retained after key
-generation or key import from a seed value.
-If the resulting key is then written to a PKCS#8 object, it will contain
-only the FIPS 204 C<sk> key.
+L<OSSL_PROVIDER_conf_get_bool(3)>), the seed will not be included when
+keys are encoded to B<PKCS#8> form, unless the C<output_formats> encoder
+parameter is set to a nonempty value, as described above.
+The output B<PKCS#8> object will contain only the FIPS 204 C<sk> key.
=item C<ml-dsa.prefer_seed> (B<OSSL_PKEY_PARAM_ML_DSA_PREFER_SEED>) <UTF8 string>
L<EVP_PKEY_get_raw_private_key(3)>,
L<EVP_PKEY_get_raw_public_key(3)>,
L<EVP_PKEY_get1_encoded_public_key(3)>,
+L<OSSL_ENCODER_CTX_new_for_pkey(3)>,
+L<OSSL_ENCODER_CTX_set_params(3)>,
L<OSSL_PROVIDER_add_conf_parameter(3)>,
L<provider-keymgmt(7)>,
L<EVP_SIGNATURE-ML-DSA(7)>
=head1 HISTORY
This functionality was added in OpenSSL 3.5.
+The C<output_formats> B<OSSL_ENCODER_CTX> parameter was added in OpenSSL 4.0.
=head1 COPYRIGHT
=back
+=head2 Encoder Parameters
+
+=over 4
+
+=item "output_formats" (B<OSSL_PKEY_PARAM_OUTPUT_FORMATS>) <UTF8 string>
+
+This parameter can be used with L<OSSL_ENCODER_CTX_new_for_pkey(3)>
+and L<OSSL_ENCODER_CTX_set_params(3)> to select the preferred B<PKCS#8> output
+formats of a private key.
+When this parameter is either not specified or empty, the behaviour is
+determined by the provider configuration parameters described below.
+Otherwise, it has the same syntax and behaviour as the provider configuration
+B<ml-kem.output_formats> parameter, except that the B<ml-kem.retain-seed>
+parameter is then ignored, i.e. the C<seed-only> and C<seed-priv> formats
+remain available so long as the seed was initially known, regardless of any
+setting of B<ml-kem.retain-seed>.
+
+=back
+
=head2 Common parameters
In addition to the common parameters that all keytypes should support (see
L<provider-keymgmt(7)/Common Information Parameters>), B<ML-KEM> keys
-keys support the parameters listed below.
+support the parameters listed below.
These are gettable using
L<EVP_PKEY_get_octet_string_param(3)> or L<EVP_PKEY_get_params(3)>.
They can be initialised via L<EVP_PKEY_fromdata(3)>, and are returned by
=item C<ml-kem.retain_seed> (B<OSSL_PKEY_PARAM_ML_KEM_RETAIN_SEED>) <UTF8 string>
When set to a string representing a false boolean value (see
-L<OSSL_PROVIDER_conf_get_bool(3)>), the seed will not be retained after key
-generation or key import from a seed value.
-If the resulting key is then written to a PKCS#8 object, it will contain
-only the FIPS 203 C<dk> key.
+L<OSSL_PROVIDER_conf_get_bool(3)>), the seed will not be included when
+keys are encoded to B<PKCS#8> form, unless the C<output_formats> encoder
+parameter is set to a nonempty value, as described above.
+The output B<PKCS#8> object will contain only the FIPS 203 C<dk> key.
=item C<ml-kem.prefer_seed> (B<OSSL_PKEY_PARAM_ML_KEM_PREFER_SEED>) <UTF8 string>
=item C<bare-seed>:
This format represents B<PKCS#8> objects in which the private key contains
-the 64-byte FIPS 204 seed B<(d, z)> without any ASN.1 encapsulation.
+the 64-byte FIPS 203 seed B<(d, z)> without any ASN.1 encapsulation.
If the C<bare-seed> format is not included in the list, this format will not be
recognised on input.
=item C<bare-priv>:
This format represents B<PKCS#8> objects in which the private key contains
-the FIPS 204 decapsulation key B<dk> without any ASN.1 encapsulation.
+the FIPS 203 decapsulation key B<dk> without any ASN.1 encapsulation.
If the C<bare-priv> format is not included in the list, this format will not be
recognised on input.
L<EVP_PKEY_get_raw_private_key(3)>,
L<EVP_PKEY_get_raw_public_key(3)>,
L<EVP_PKEY_get1_encoded_public_key(3)>,
+L<OSSL_ENCODER_CTX_new_for_pkey(3)>,
+L<OSSL_ENCODER_CTX_set_params(3)>,
L<OSSL_PROVIDER_add_conf_parameter(3)>,
L<provider-keymgmt(7)>,
L<EVP_KEM-ML-KEM(7)>
=head1 HISTORY
This functionality was added in OpenSSL 3.5.
+The C<output_formats> B<OSSL_ENCODER_CTX> parameter was added in OpenSSL 4.0.
=head1 COPYRIGHT
#define OSSL_KEM_PARAM_OPERATION_RSASVE "RSASVE"
#define OSSL_KEM_PARAM_OPERATION_DHKEM "DHKEM"
-/* Provider configuration variables */
-#define OSSL_PKEY_RETAIN_SEED "pkey_retain_seed"
-
/* Parameter name definitions - generated by util/perl/OpenSSL/paramnames.pm */
{- generate_public_macros(); -}
EVP_CIPHER *cipher;
struct ossl_passphrase_data_st pwdata;
+
+ /* Just-in-time ML-KEM and ML-DSA output format override */
+ char *output_formats;
} KEY2ANY_CTX;
typedef int check_key_type_fn(const void *key, int nid);
{
KEY2ANY_CTX *ctx = vctx;
- return ossl_ml_dsa_i2d_prvkey(vkey, pder, ctx->provctx);
+ return ossl_ml_dsa_i2d_prvkey(vkey, pder,
+ ctx->provctx, ctx->output_formats);
}
# define ml_dsa_epki_priv_to_der ml_dsa_pki_priv_to_der
{
KEY2ANY_CTX *ctx = vctx;
- return ossl_ml_kem_i2d_prvkey(vkey, pder, ctx->provctx);
+ return ossl_ml_kem_i2d_prvkey(vkey, pder,
+ ctx->provctx, ctx->output_formats);
}
# define ml_kem_epki_priv_to_der ml_kem_pki_priv_to_der
ossl_pw_clear_passphrase_data(&ctx->pwdata);
EVP_CIPHER_free(ctx->cipher);
+ OPENSSL_free(ctx->output_formats);
OPENSSL_free(ctx);
}
if (p.svprm != NULL && !OSSL_PARAM_get_int(p.svprm, &ctx->save_parameters))
return 0;
+ if (p.output_formats != NULL) {
+ char *val = NULL;
+
+ if (!OSSL_PARAM_get_utf8_string(p.output_formats, &val, 0))
+ return 0;
+ OPENSSL_free(ctx->output_formats);
+ ctx->output_formats = *val != '\0' ? val : NULL;
+ }
+
return 1;
}
-}
{- produce_param_decoder('key2any_set_ctx_params',
- (['OSSL_ENCODER_PARAM_CIPHER', 'cipher', 'utf8_string'],
- ['OSSL_ENCODER_PARAM_PROPERTIES', 'propq', 'utf8_string'],
- ['OSSL_ENCODER_PARAM_SAVE_PARAMETERS', 'svprm', 'int'],
+ (['OSSL_ENCODER_PARAM_CIPHER', 'cipher', 'utf8_string'],
+ ['OSSL_ENCODER_PARAM_PROPERTIES', 'propq', 'utf8_string'],
+ ['OSSL_ENCODER_PARAM_SAVE_PARAMETERS', 'svprm', 'int'],
+ ['OSSL_PKEY_PARAM_OUTPUT_FORMATS', 'output_formats', 'utf8_string'],
)); -}
/* Allocate and encode PKCS#8 private key payload. */
int ossl_ml_dsa_i2d_prvkey(const ML_DSA_KEY *key, uint8_t **out,
- PROV_CTX *provctx)
+ PROV_CTX *provctx, const char *formats)
{
const ML_DSA_PARAMS *params = ossl_ml_dsa_key_params(key);
const ML_COMMON_CODEC *codec;
ML_COMMON_PKCS8_FMT_PREF *fmt_slots, *slot;
const ML_COMMON_PKCS8_FMT *p8fmt;
uint8_t *buf = NULL, *pos;
- const char *formats;
int len = ML_DSA_SEED_BYTES;
int ret = 0;
const uint8_t *seed = ossl_ml_dsa_key_get_seed(key);
return 0;
}
- formats = ossl_prov_ctx_get_param(
- provctx, OSSL_PKEY_PARAM_ML_DSA_OUTPUT_FORMATS, NULL);
+ if (formats == NULL) {
+ if ((ossl_ml_dsa_key_get_prov_flags(key) & ML_DSA_KEY_RETAIN_SEED) == 0)
+ seed = NULL;
+ formats = ossl_prov_ctx_get_param(provctx,
+ OSSL_PKEY_PARAM_ML_DSA_OUTPUT_FORMATS,
+ NULL);
+ }
fmt_slots = ossl_ml_common_pkcs8_fmt_order(params->alg, codec->p8fmt,
"output", formats);
if (fmt_slots == NULL)
/* Allocate and encode PKCS#8 private key payload. */
int ossl_ml_kem_i2d_prvkey(const ML_KEM_KEY *key, uint8_t **out,
- PROV_CTX *provctx)
+ PROV_CTX *provctx, const char *formats)
{
const ML_KEM_VINFO *v = key->vinfo;
const ML_COMMON_CODEC *codec;
ML_COMMON_PKCS8_FMT_PREF *fmt_slots, *slot;
const ML_COMMON_PKCS8_FMT *p8fmt;
uint8_t *buf = NULL, *pos;
- const char *formats;
int len = ML_KEM_SEED_BYTES;
+ int have_seed = ossl_ml_kem_have_seed(key);
int ret = 0;
/* Not ours to handle */
return 0;
}
- formats = ossl_prov_ctx_get_param(
- provctx, OSSL_PKEY_PARAM_ML_KEM_OUTPUT_FORMATS, NULL);
+ if (formats == NULL) {
+ if ((key->prov_flags & ML_KEM_KEY_RETAIN_SEED) == 0)
+ have_seed = 0;
+ formats = ossl_prov_ctx_get_param(provctx,
+ OSSL_PKEY_PARAM_ML_KEM_OUTPUT_FORMATS,
+ NULL);
+ }
fmt_slots = ossl_ml_common_pkcs8_fmt_order(v->algorithm_name, codec->p8fmt,
"output", formats);
if (fmt_slots == NULL)
/* If we don't have a seed, skip seedful entries */
for (slot = fmt_slots; (p8fmt = slot->fmt) != NULL; ++slot)
- if (ossl_ml_kem_have_seed(key) || p8fmt->seed_length == 0)
+ if (have_seed || p8fmt->seed_length == 0)
break;
/* No matching table entries, give up */
if (p8fmt == NULL
__owur
__owur
int ossl_ml_dsa_i2d_prvkey(const ML_DSA_KEY *key, unsigned char **out,
- PROV_CTX *provctx);
+ PROV_CTX *provctx, const char *formats);
# endif /* OPENSSL_NO_ML_DSA */
#endif /* PROV_ML_DSA_CODECS_H */
__owur
__owur
int ossl_ml_kem_i2d_prvkey(const ML_KEM_KEY *key, unsigned char **out,
- PROV_CTX *provctx);
+ PROV_CTX *provctx, const char *formats);
# endif /* OPENSSL_NO_ML_KEM */
#endif /* PROV_ML_KEM_CODECS_H */
#include <openssl/dh.h>
#include <openssl/aes.h>
#include <openssl/decoder.h>
+#include <openssl/encoder.h>
#include <openssl/rsa.h>
#include <openssl/proverr.h>
#include <openssl/rand.h>
# endif
#endif
+#ifndef OPENSSL_NO_ML_KEM
+/*
+ * openssl genpkey -provparam ml-kem.output_formats=seed-only \
+ * -algorithm ml-kem-512 -outform DER |
+ * xxd -i
+ */
+static const unsigned char kMLKEMSeedOnlyDER[] = {
+ 0x30, 0x54, 0x02, 0x01, 0x00, 0x30, 0x0b, 0x06, 0x09, 0x60, 0x86, 0x48,
+ 0x01, 0x65, 0x03, 0x04, 0x04, 0x01, 0x04, 0x42, 0x80, 0x40, 0x21, 0xe4,
+ 0x30, 0x56, 0x08, 0x64, 0xd3, 0xd4, 0x37, 0x33, 0x1c, 0xe5, 0xc9, 0xd8,
+ 0x26, 0x10, 0x9b, 0x4d, 0x58, 0xb4, 0xe2, 0x57, 0x70, 0x0f, 0x28, 0xe2,
+ 0xd2, 0xa8, 0x6b, 0xb6, 0x2b, 0x85, 0x1b, 0x25, 0x39, 0x29, 0xca, 0x6d,
+ 0x9a, 0xf0, 0x11, 0x5d, 0xca, 0xf4, 0xf7, 0x9a, 0x50, 0x39, 0xa7, 0x52,
+ 0x39, 0x5d, 0x84, 0xa9, 0xb9, 0x84, 0x4c, 0xa2, 0xe5, 0x49, 0xd7, 0x81,
+ 0xd1, 0x6d
+};
+#endif
+
+#ifndef OPENSSL_NO_ML_DSA
+/*
+ * openssl genpkey -provparam ml-dsa.output_formats=seed-only \
+ * -algorithm ml-dsa-44 -outform DER |
+ * xxd -i
+ */
+static const unsigned char kMLDSASeedOnlyDER[] = {
+ 0x30, 0x34, 0x02, 0x01, 0x00, 0x30, 0x0b, 0x06, 0x09, 0x60, 0x86, 0x48,
+ 0x01, 0x65, 0x03, 0x04, 0x03, 0x11, 0x04, 0x22, 0x80, 0x20, 0xc5, 0xbb,
+ 0x12, 0x9c, 0x76, 0xac, 0x6f, 0x03, 0x6c, 0x56, 0x32, 0xf4, 0x66, 0xb8,
+ 0x8c, 0x77, 0x4b, 0xe0, 0xaa, 0x4a, 0xd6, 0xa0, 0x96, 0x12, 0xc3, 0x8c,
+ 0xe7, 0x71, 0xbe, 0xf8, 0xba, 0xbe
+};
+#endif
+
static const unsigned char kCFBDefaultKey[] = {
0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88,
0x09, 0xCF, 0x4F, 0x3C
}
#endif
+#if !defined(OPENSSL_NO_ML_KEM) || !defined(OPENSSL_NO_ML_DSA)
+static EVP_PKEY *load_ml_key(const char *keytype, const char *input_type,
+ const unsigned char *data, size_t data_len)
+{
+ const unsigned char **pdata = &data;
+ EVP_PKEY *pkey = NULL;
+ OSSL_DECODER_CTX *dctx =
+ OSSL_DECODER_CTX_new_for_pkey(&pkey, input_type, "PrivateKeyInfo",
+ keytype, 0, testctx, testpropq);
+
+ if (!TEST_ptr(dctx))
+ return NULL;
+ /* |pkey| will be NULL on error */
+ (void)OSSL_DECODER_from_data(dctx, pdata, &data_len);
+ OSSL_DECODER_CTX_free(dctx);
+ return pkey;
+}
+
+static int
+store_ml_key(EVP_PKEY *pkey, const char *input_type, const char *fmts,
+ const unsigned char *expect, size_t expectlen)
+{
+ OSSL_PARAM params[2] = { OSSL_PARAM_END, OSSL_PARAM_END };
+ OSSL_ENCODER_CTX *ectx = NULL;
+ unsigned char *buf = NULL, *der = NULL;
+ size_t len = 0;
+ long derlen;
+ int ret = 0;
+
+ /* Just-in-time encoding format selection */
+ params[0] = OSSL_PARAM_construct_utf8_string(OSSL_PKEY_PARAM_OUTPUT_FORMATS,
+ (char *)fmts, 0);
+
+ ectx = OSSL_ENCODER_CTX_new_for_pkey(pkey, EVP_PKEY_KEYPAIR, input_type,
+ "PrivateKeyInfo", testpropq);
+ if (!TEST_ptr(ectx))
+ return 0;
+ if (!TEST_true(OSSL_ENCODER_CTX_set_params(ectx, params))
+ || !TEST_true(OSSL_ENCODER_to_data(ectx, &buf, &len)))
+ goto end;
+
+ if (strcmp(input_type, "PEM") == 0) {
+ BIO *pembio = BIO_new_mem_buf(buf, len);
+ char *name = NULL, *header = NULL;
+
+ if (!TEST_ptr(pembio))
+ goto end;
+ ret = PEM_read_bio(pembio, &name, &header, &der, &derlen);
+ BIO_free(pembio);
+ if (TEST_true(ret)) {
+ if (!TEST_int_eq(strcmp(name, PEM_STRING_PKCS8INF), 0)
+ || !TEST_true(header == NULL || *header == '\0'))
+ ret = 0;
+ OPENSSL_free(name);
+ OPENSSL_free(header);
+ if (!ret)
+ goto end;
+ }
+ } else {
+ der = buf;
+ derlen = len;
+ }
+ ret = expect != NULL ?
+ TEST_mem_eq(der, (size_t) derlen, expect, expectlen) :
+ TEST_size_t_eq((size_t) derlen, expectlen);
+
+ end:
+ OSSL_ENCODER_CTX_free(ectx);
+ if (der != buf)
+ OPENSSL_free(der);
+ OPENSSL_free(buf);
+ return ret;
+}
+
+static int test_ml_seed_only(int idx)
+{
+ const char *alg;
+ const unsigned char *seedonly;
+ EVP_PKEY *pkey = NULL;
+ size_t seedonlysz, privonlysz;
+ const char *outform = (idx & 1) ? "DER" : "PEM";
+ int ret = 0;
+
+ if (idx & 2) {
+# ifndef OPENSSL_NO_ML_DSA
+ alg = "ML-DSA-44";
+ seedonly = kMLDSASeedOnlyDER;
+ seedonlysz = sizeof(kMLDSASeedOnlyDER);
+ privonlysz = 2588;
+# else
+ return 0;
+# endif
+ } else {
+# ifndef OPENSSL_NO_ML_KEM
+ alg = "ML-KEM-512";
+ seedonly = kMLKEMSeedOnlyDER;
+ seedonlysz = sizeof(kMLKEMSeedOnlyDER);
+ privonlysz = 1660;
+# else
+ return 0;
+# endif
+ }
+
+ pkey = load_ml_key(alg, "DER", seedonly, seedonlysz);
+ if (!TEST_ptr(pkey))
+ return 0;
+
+ /*
+ * Check that the "output_formats" parameter is behaving as expected.
+ * With "seed-only" check full payload, otherwise just the DER length.
+ */
+ if (store_ml_key(pkey, outform, "seed-only", seedonly, seedonlysz)
+ && store_ml_key(pkey, outform, "bare-seed", NULL, seedonlysz - 2)
+ && store_ml_key(pkey, outform, "priv-only", NULL, privonlysz))
+ ret = 1;
+
+ EVP_PKEY_free(pkey);
+ return ret;
+}
+#endif
+
+#ifndef OPENSSL_NO_ML_KEM
+static int test_ml_kem_seed_only(int idx)
+{
+ return test_ml_seed_only(idx);
+}
+#endif
+#ifndef OPENSSL_NO_ML_DSA
+static int test_ml_dsa_seed_only(int idx)
+{
+ return test_ml_seed_only(idx + 2);
+}
+#endif
+
#ifndef OPENSSL_NO_DEPRECATED_3_0
# ifndef OPENSSL_NO_DH
static EVP_PKEY *load_example_dh_key(void)
ADD_TEST(test_evp_cipher_pipeline);
+#ifndef OPENSSL_NO_ML_KEM
+ ADD_ALL_TESTS(test_ml_kem_seed_only, 2);
+#endif
+#ifndef OPENSSL_NO_ML_DSA
+ ADD_ALL_TESTS(test_ml_dsa_seed_only, 2);
+#endif
+
return 1;
}
'OSSL_PKEY_PARAM_DIST_ID' => "distid",
'OSSL_PKEY_PARAM_PUB_KEY' => "pub",
'OSSL_PKEY_PARAM_PRIV_KEY' => "priv",
+ 'OSSL_PKEY_PARAM_OUTPUT_FORMATS' => "output_formats",
# PKEY_PARAM_IMPLICIT_REJECTION isn't actually used, or meaningful. We keep
# it for API stability, but please use ASYM_CIPHER_PARAM_IMPLICIT_REJECTION
# instead.