]> git.ipfire.org Git - thirdparty/openssl.git/commitdiff
decoders: Fix prioritization of decoders via property query
authorDaniel Van Geest <daniel.vangeest@cryptonext-security.com>
Mon, 23 Jun 2025 11:45:59 +0000 (12:45 +0100)
committerTomas Mraz <tomas@openssl.org>
Tue, 1 Jul 2025 17:06:57 +0000 (19:06 +0200)
When a property query string was used, it was not being applied to decoders.
When multiple providers supporting the same algorithm were loaded, it was
undefined which provider would be used when decoding a key, even when a
propquery string was provided.  This fix scores decoder instances based on
property query matching and selects the highest scored decoder instance when
building the decoder chain.

The fake_rsa test provider is updated to support basic encoding and decoding.
A test is added using the fake_rsa provider to ensure that property query
strings are respected when loading decoders.

Reviewed-by: Paul Dale <ppzgs1@gmail.com>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/27876)

crypto/encode_decode/decoder_lib.c
crypto/encode_decode/decoder_pkey.c
crypto/encode_decode/encoder_local.h
test/fake_rsaprov.c
test/provider_pkey_test.c

index ffcf3cde11558109de6ce6e8f66d2ef7fa7ea6bf..dedfb24e569e2f1e8194411567372b322721a6dc 100644 (file)
@@ -537,6 +537,14 @@ static void collect_extra_decoder(OSSL_DECODER *decoder, void *arg)
     }
 }
 
+static int decoder_sk_cmp(const OSSL_DECODER_INSTANCE *const *a,
+                          const OSSL_DECODER_INSTANCE *const *b)
+{
+    if ((*a)->score == (*b)->score)
+        return (*a)->order - (*b)->order;
+    return (*a)->score - (*b)->score;
+}
+
 int OSSL_DECODER_CTX_add_extra(OSSL_DECODER_CTX *ctx,
                                OSSL_LIB_CTX *libctx, const char *propq)
 {
@@ -595,6 +603,26 @@ int OSSL_DECODER_CTX_add_extra(OSSL_DECODER_CTX *ctx,
     OSSL_DECODER_do_all_provided(libctx, collect_all_decoders, skdecoders);
     numdecoders = sk_OSSL_DECODER_num(skdecoders);
 
+    /*
+     * If there are provided or default properties, sort the initial decoder list
+     * by property matching score so that the highest scored provider is selected
+     * first.
+     */
+    if (propq != NULL || ossl_ctx_global_properties(libctx, 0) != NULL) {
+        int num_decoder_insts = sk_OSSL_DECODER_INSTANCE_num(ctx->decoder_insts);
+        int i;
+        OSSL_DECODER_INSTANCE *di;
+        sk_OSSL_DECODER_INSTANCE_compfunc old_cmp =
+            sk_OSSL_DECODER_INSTANCE_set_cmp_func(ctx->decoder_insts, decoder_sk_cmp);
+
+        for (i = 0; i < num_decoder_insts; i++) {
+            di = sk_OSSL_DECODER_INSTANCE_value(ctx->decoder_insts, i);
+            di->order = i;
+        }
+        sk_OSSL_DECODER_INSTANCE_sort(ctx->decoder_insts);
+        sk_OSSL_DECODER_INSTANCE_set_cmp_func(ctx->decoder_insts, old_cmp);
+    }
+
     memset(&data, 0, sizeof(data));
     data.ctx = ctx;
     data.w_prev_start = 0;
index f99566bde7441cf6d82dce7c5319ea24055f3ecf..9fc4e231233121fa21e2326d6877cc4199d23294 100644 (file)
@@ -222,15 +222,21 @@ struct collect_data_st {
     int total;      /* number of matching results */
     char error_occurred;
     char keytype_resolved;
+    OSSL_PROPERTY_LIST *pq;
 
     STACK_OF(EVP_KEYMGMT) *keymgmts;
 };
 
-static void collect_decoder_keymgmt(EVP_KEYMGMT *keymgmt, OSSL_DECODER *decoder,
-                                    void *provctx, struct collect_data_st *data)
+/*
+ * Add decoder instance to the decoder context if it is compatible. Returns 1
+ * if a decoder was added, 0 otherwise.
+ */
+static int collect_decoder_keymgmt(EVP_KEYMGMT *keymgmt, OSSL_DECODER *decoder,
+                                   void *provctx, struct collect_data_st *data)
 {
     void *decoderctx = NULL;
     OSSL_DECODER_INSTANCE *di = NULL;
+    const OSSL_PROPERTY_LIST *props;
 
     /*
      * We already checked the EVP_KEYMGMT is applicable in check_keymgmt so we
@@ -239,17 +245,17 @@ static void collect_decoder_keymgmt(EVP_KEYMGMT *keymgmt, OSSL_DECODER *decoder,
 
     if (keymgmt->name_id != decoder->base.id)
         /* Mismatch is not an error, continue. */
-        return;
+        return 0;
 
     if ((decoderctx = decoder->newctx(provctx)) == NULL) {
         data->error_occurred = 1;
-        return;
+        return 0;
     }
 
     if ((di = ossl_decoder_instance_new(decoder, decoderctx)) == NULL) {
         decoder->freectx(decoderctx);
         data->error_occurred = 1;
-        return;
+        return 0;
     }
 
     /*
@@ -263,7 +269,7 @@ static void collect_decoder_keymgmt(EVP_KEYMGMT *keymgmt, OSSL_DECODER *decoder,
             || OPENSSL_strcasecmp(data->ctx->start_input_type, "PEM") != 0)) {
         /* Mismatch is not an error, continue. */
         ossl_decoder_instance_free(di);
-        return;
+        return 0;
     }
 
     OSSL_TRACE_BEGIN(DECODER) {
@@ -275,13 +281,30 @@ static void collect_decoder_keymgmt(EVP_KEYMGMT *keymgmt, OSSL_DECODER *decoder,
                    OSSL_DECODER_get0_properties(decoder));
     } OSSL_TRACE_END(DECODER);
 
+    /*
+     * Get the property match score so the decoders can be prioritized later.
+     */
+    props = ossl_decoder_parsed_properties(decoder);
+    if (data->pq != NULL && props != NULL) {
+        di->score = ossl_property_match_count(data->pq, props);
+        /*
+         * Mismatch of mandatory properties is not an error, the decoder is just
+         * ignored, continue.
+         */
+        if (di->score < 0) {
+            ossl_decoder_instance_free(di);
+            return 0;
+        }
+    }
+
     if (!ossl_decoder_ctx_add_decoder_inst(data->ctx, di)) {
         ossl_decoder_instance_free(di);
         data->error_occurred = 1;
-        return;
+        return 0;
     }
 
     ++data->total;
+    return 1;
 }
 
 static void collect_decoder(OSSL_DECODER *decoder, void *arg)
@@ -321,7 +344,9 @@ static void collect_decoder(OSSL_DECODER *decoder, void *arg)
     for (i = 0; i < end_i; ++i) {
         keymgmt = sk_EVP_KEYMGMT_value(keymgmts, i);
 
-        collect_decoder_keymgmt(keymgmt, decoder, provctx, data);
+        /* Only add this decoder once */
+        if (collect_decoder_keymgmt(keymgmt, decoder, provctx, data))
+            break;
         if (data->error_occurred)
             return;
     }
@@ -407,6 +432,8 @@ static int ossl_decoder_ctx_setup_for_pkey(OSSL_DECODER_CTX *ctx,
     struct decoder_pkey_data_st *process_data = NULL;
     struct collect_data_st collect_data = { NULL };
     STACK_OF(EVP_KEYMGMT) *keymgmts = NULL;
+    OSSL_PROPERTY_LIST **plp;
+    OSSL_PROPERTY_LIST *pq = NULL, *p2 = NULL;
 
     OSSL_TRACE_BEGIN(DECODER) {
         const char *input_type = ctx->start_input_type;
@@ -442,6 +469,25 @@ static int ossl_decoder_ctx_setup_for_pkey(OSSL_DECODER_CTX *ctx,
     process_data->selection = ctx->selection;
     process_data->keymgmts  = keymgmts;
 
+    /*
+     * Collect passed and default properties to prioritize the decoders.
+     */
+    if (propquery != NULL)
+        p2 = pq = ossl_parse_query(libctx, propquery, 1);
+
+    plp = ossl_ctx_global_properties(libctx, 0);
+    if (plp != NULL && *plp != NULL) {
+        if (pq == NULL) {
+            pq = *plp;
+        } else {
+            p2 = ossl_property_merge(pq, *plp);
+            ossl_property_free(pq);
+            if (p2 == NULL)
+                goto err;
+            pq = p2;
+        }
+    }
+
     /*
      * Enumerate all keymgmts into a stack.
      *
@@ -457,10 +503,11 @@ static int ossl_decoder_ctx_setup_for_pkey(OSSL_DECODER_CTX *ctx,
      * upfront, as this ensures that the names for all loaded providers have
      * been registered by the time we try to resolve the keytype string.
      */
-    collect_data.ctx        = ctx;
-    collect_data.libctx     = libctx;
-    collect_data.keymgmts   = keymgmts;
-    collect_data.keytype    = keytype;
+    collect_data.ctx            = ctx;
+    collect_data.libctx         = libctx;
+    collect_data.keymgmts       = keymgmts;
+    collect_data.keytype        = keytype;
+    collect_data.pq             = pq;
     EVP_KEYMGMT_do_all_provided(libctx, collect_keymgmt, &collect_data);
 
     if (collect_data.error_occurred)
@@ -496,6 +543,7 @@ static int ossl_decoder_ctx_setup_for_pkey(OSSL_DECODER_CTX *ctx,
     ok = 1;
  err:
     decoder_clean_pkey_construct_arg(process_data);
+    ossl_property_free(p2);
     return ok;
 }
 
index a2846d309ea8aa3d9c1cc7e4b459fd3cc1645699..11e52cfeec75d4effff5e1d483387f0fa8dcc854 100644 (file)
@@ -109,6 +109,8 @@ struct ossl_decoder_instance_st {
     const char *input_type;      /* Never NULL */
     const char *input_structure; /* May be NULL */
     int input_type_id;
+    int order;                   /* For stable ordering of decoders wrt proqs */
+    int score;                   /* For ordering decoders wrt proqs */
 
     unsigned int flag_input_structure_was_set : 1;
 };
index c1b8e28286143f4994dc5e17924e6ab6221dc9d4..46fc9104ef955dcaa542f00403be1655f20e2bb5 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2021-2023 The OpenSSL Project Authors. All Rights Reserved.
+ * Copyright 2021-2025 The OpenSSL Project Authors. All Rights Reserved.
  *
  * Licensed under the Apache License 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -9,12 +9,15 @@
  */
 
 #include <string.h>
+#include <openssl/asn1.h>
+#include <openssl/asn1t.h>
 #include <openssl/core_names.h>
 #include <openssl/core_object.h>
 #include <openssl/rand.h>
 #include <openssl/provider.h>
 #include "testutil.h"
 #include "fake_rsaprov.h"
+#include "internal/asn1.h"
 
 static OSSL_FUNC_keymgmt_new_fn fake_rsa_keymgmt_new;
 static OSSL_FUNC_keymgmt_free_fn fake_rsa_keymgmt_free;
@@ -32,6 +35,16 @@ static int exptypes_selection;
 static int query_id;
 static int key_deleted;
 
+typedef struct {
+    OSSL_LIB_CTX *libctx;
+} PROV_FAKE_RSA_CTX;
+
+#define PROV_FAKE_RSA_LIBCTX_OF(provctx) (((PROV_FAKE_RSA_CTX *)provctx)->libctx)
+
+#define FAKE_RSA_STATUS_IMPORTED    1
+#define FAKE_RSA_STATUS_GENERATED   2
+#define FAKE_RSA_STATUS_DECODED     3
+
 struct fake_rsa_keydata {
     int selection;
     int status;
@@ -86,7 +99,7 @@ static int fake_rsa_keymgmt_import(void *keydata, int selection,
     struct fake_rsa_keydata *fake_rsa_key = keydata;
 
     /* key was imported */
-    fake_rsa_key->status = 1;
+    fake_rsa_key->status = FAKE_RSA_STATUS_IMPORTED;
 
     return 1;
 }
@@ -219,11 +232,11 @@ static void *fake_rsa_keymgmt_load(const void *reference, size_t reference_sz)
 {
     struct fake_rsa_keydata *key = NULL;
 
-    if (reference_sz != sizeof(*key))
+    if (reference_sz != sizeof(key))
         return NULL;
 
     key = *(struct fake_rsa_keydata **)reference;
-    if (key->status != 1)
+    if (key->status != FAKE_RSA_STATUS_IMPORTED && key->status != FAKE_RSA_STATUS_DECODED)
         return NULL;
 
     /* detach the reference */
@@ -258,7 +271,7 @@ static void *fake_rsa_gen(void *genctx, OSSL_CALLBACK *osslcb, void *cbarg)
     if (!TEST_ptr(keydata = fake_rsa_keymgmt_new(NULL)))
         return NULL;
 
-    keydata->status = 2;
+    keydata->status = FAKE_RSA_STATUS_GENERATED;
     return keydata;
 }
 
@@ -638,7 +651,7 @@ static int fake_rsa_st_load(void *loaderctx,
         /* The address of the key becomes the octet string */
         params[2] =
             OSSL_PARAM_construct_octet_string(OSSL_OBJECT_PARAM_REFERENCE,
-                                              &key, sizeof(*key));
+                                              &key, sizeof(key));
         params[3] = OSSL_PARAM_construct_end();
         rv = object_cb(params, object_cbarg);
         *storectx = 1;
@@ -702,6 +715,502 @@ static const OSSL_ALGORITHM fake_rsa_store_algs[] = {
     { NULL, NULL, NULL }
 };
 
+struct der2key_ctx_st;           /* Forward declaration */
+typedef int check_key_fn(void *, struct der2key_ctx_st *ctx);
+typedef void adjust_key_fn(void *, struct der2key_ctx_st *ctx);
+typedef void free_key_fn(void *);
+typedef void *d2i_PKCS8_fn(void **, const unsigned char **, long,
+                           struct der2key_ctx_st *);
+struct keytype_desc_st {
+    const char *keytype_name;
+    const OSSL_DISPATCH *fns; /* Keymgmt (to pilfer functions from) */
+
+    /* The input structure name */
+    const char *structure_name;
+
+    /*
+     * The EVP_PKEY_xxx type macro.  Should be zero for type specific
+     * structures, non-zero when the outermost structure is PKCS#8 or
+     * SubjectPublicKeyInfo.  This determines which of the function
+     * pointers below will be used.
+     */
+    int evp_type;
+
+    /* The selection mask for OSSL_FUNC_decoder_does_selection() */
+    int selection_mask;
+
+    /* For type specific decoders, we use the corresponding d2i */
+    d2i_of_void *d2i_private_key; /* From type-specific DER */
+    d2i_of_void *d2i_public_key;  /* From type-specific DER */
+    d2i_of_void *d2i_key_params;  /* From type-specific DER */
+    d2i_PKCS8_fn *d2i_PKCS8;      /* Wrapped in a PrivateKeyInfo */
+    d2i_of_void *d2i_PUBKEY;      /* Wrapped in a SubjectPublicKeyInfo */
+
+    /*
+     * For any key, we may need to check that the key meets expectations.
+     * This is useful when the same functions can decode several variants
+     * of a key.
+     */
+    check_key_fn *check_key;
+
+    /*
+     * For any key, we may need to make provider specific adjustments, such
+     * as ensure the key carries the correct library context.
+     */
+    adjust_key_fn *adjust_key;
+    /* {type}_free() */
+    free_key_fn *free_key;
+};
+
+/*
+ * Start blatant code steal. Alternative: Open up d2i_X509_PUBKEY_INTERNAL
+ * as per https://github.com/openssl/openssl/issues/16697 (TBD)
+ * Code from openssl/crypto/x509/x_pubkey.c as
+ * ossl_d2i_X509_PUBKEY_INTERNAL is presently not public
+ */
+struct X509_pubkey_st {
+    X509_ALGOR *algor;
+    ASN1_BIT_STRING *public_key;
+
+    EVP_PKEY *pkey;
+
+    /* extra data for the callback, used by d2i_PUBKEY_ex */
+    OSSL_LIB_CTX *libctx;
+    char *propq;
+};
+
+ASN1_SEQUENCE(X509_PUBKEY_INTERNAL) = {
+        ASN1_SIMPLE(X509_PUBKEY, algor, X509_ALGOR),
+        ASN1_SIMPLE(X509_PUBKEY, public_key, ASN1_BIT_STRING)
+} static_ASN1_SEQUENCE_END_name(X509_PUBKEY, X509_PUBKEY_INTERNAL)
+
+static X509_PUBKEY *fake_rsa_d2i_X509_PUBKEY_INTERNAL(const unsigned char **pp,
+                                                      long len, OSSL_LIB_CTX *libctx)
+{
+    X509_PUBKEY *xpub = OPENSSL_zalloc(sizeof(*xpub));
+
+    if (xpub == NULL)
+        return NULL;
+    return (X509_PUBKEY *)ASN1_item_d2i_ex((ASN1_VALUE **)&xpub, pp, len,
+                                           ASN1_ITEM_rptr(X509_PUBKEY_INTERNAL),
+                                           libctx, NULL);
+}
+/* end steal https://github.com/openssl/openssl/issues/16697 */
+
+/*
+ * Context used for DER to key decoding.
+ */
+struct der2key_ctx_st {
+    PROV_FAKE_RSA_CTX *provctx;
+    struct keytype_desc_st *desc;
+    /* The selection that is passed to fake_rsa_der2key_decode() */
+    int selection;
+    /* Flag used to signal that a failure is fatal */
+    unsigned int flag_fatal : 1;
+};
+
+static int fake_rsa_read_der(PROV_FAKE_RSA_CTX *provctx, OSSL_CORE_BIO *cin,
+                             unsigned char **data, long *len)
+{
+    BUF_MEM *mem = NULL;
+    BIO *in = BIO_new_from_core_bio(provctx->libctx, cin);
+    int ok = (asn1_d2i_read_bio(in, &mem) >= 0);
+
+    if (ok) {
+        *data = (unsigned char *)mem->data;
+        *len = (long)mem->length;
+        OPENSSL_free(mem);
+    }
+    BIO_free(in);
+    return ok;
+}
+
+typedef void *key_from_pkcs8_t(const PKCS8_PRIV_KEY_INFO *p8inf,
+                               OSSL_LIB_CTX *libctx, const char *propq);
+static void *fake_rsa_der2key_decode_p8(const unsigned char **input_der,
+                                        long input_der_len, struct der2key_ctx_st *ctx,
+                                        key_from_pkcs8_t *key_from_pkcs8)
+{
+    PKCS8_PRIV_KEY_INFO *p8inf = NULL;
+    const X509_ALGOR *alg = NULL;
+    void *key = NULL;
+
+    if ((p8inf = d2i_PKCS8_PRIV_KEY_INFO(NULL, input_der, input_der_len)) != NULL
+        && PKCS8_pkey_get0(NULL, NULL, NULL, &alg, p8inf)
+        && OBJ_obj2nid(alg->algorithm) == ctx->desc->evp_type)
+        key = key_from_pkcs8(p8inf, PROV_FAKE_RSA_LIBCTX_OF(ctx->provctx), NULL);
+    PKCS8_PRIV_KEY_INFO_free(p8inf);
+
+    return key;
+}
+
+static struct fake_rsa_keydata *fake_rsa_d2i_PUBKEY(struct fake_rsa_keydata **a,
+                                                    const unsigned char **pp, long length)
+{
+    struct fake_rsa_keydata *key = NULL;
+    X509_PUBKEY *xpk;
+
+    xpk = fake_rsa_d2i_X509_PUBKEY_INTERNAL(pp, length, NULL);
+    if (xpk == NULL)
+        goto err_exit;
+
+    key = fake_rsa_keymgmt_new(NULL);
+    if (key == NULL)
+        goto err_exit;
+
+    key->status = FAKE_RSA_STATUS_DECODED;
+
+    if (a != NULL) {
+        fake_rsa_keymgmt_free(*a);
+        *a = key;
+    }
+
+err_exit:
+    X509_PUBKEY_free(xpk);
+    return key;
+}
+
+/* ---------------------------------------------------------------------- */
+
+static OSSL_FUNC_decoder_freectx_fn der2key_freectx;
+static OSSL_FUNC_decoder_decode_fn fake_rsa_der2key_decode;
+static OSSL_FUNC_decoder_export_object_fn der2key_export_object;
+
+static struct der2key_ctx_st *
+der2key_newctx(void *provctx, struct keytype_desc_st *desc, const char *tls_name)
+{
+    struct der2key_ctx_st *ctx = OPENSSL_zalloc(sizeof(*ctx));
+
+    if (ctx != NULL) {
+        ctx->provctx = provctx;
+        ctx->desc = desc;
+        if (desc->evp_type == 0)
+            ctx->desc->evp_type = OBJ_sn2nid(tls_name);
+    }
+    return ctx;
+}
+
+static void der2key_freectx(void *vctx)
+{
+    struct der2key_ctx_st *ctx = vctx;
+
+    OPENSSL_free(ctx);
+}
+
+static int der2key_check_selection(int selection,
+                                   const struct keytype_desc_st *desc)
+{
+    /*
+     * The selections are kinda sorta "levels", i.e. each selection given
+     * here is assumed to include those following.
+     */
+    int checks[] = {
+        OSSL_KEYMGMT_SELECT_PRIVATE_KEY,
+        OSSL_KEYMGMT_SELECT_PUBLIC_KEY,
+        OSSL_KEYMGMT_SELECT_ALL_PARAMETERS
+    };
+    size_t i;
+
+    /* The decoder implementations made here support guessing */
+    if (selection == 0)
+        return 1;
+
+    for (i = 0; i < OSSL_NELEM(checks); i++) {
+        int check1 = (selection & checks[i]) != 0;
+        int check2 = (desc->selection_mask & checks[i]) != 0;
+
+        /*
+         * If the caller asked for the currently checked bit(s), return
+         * whether the decoder description says it's supported.
+         */
+        if (check1)
+            return check2;
+    }
+
+    /* This should be dead code, but just to be safe... */
+    return 0;
+}
+
+static int fake_rsa_der2key_decode(void *vctx, OSSL_CORE_BIO *cin, int selection,
+                                   OSSL_CALLBACK *data_cb, void *data_cbarg,
+                                   OSSL_PASSPHRASE_CALLBACK *pw_cb, void *pw_cbarg)
+{
+    struct der2key_ctx_st *ctx = vctx;
+    unsigned char *der = NULL;
+    const unsigned char *derp;
+    long der_len = 0;
+    void *key = NULL;
+    int ok = 0;
+
+    ctx->selection = selection;
+    /*
+     * The caller is allowed to specify 0 as a selection mark, to have the
+     * structure and key type guessed.  For type-specific structures, this
+     * is not recommended, as some structures are very similar.
+     * Note that 0 isn't the same as OSSL_KEYMGMT_SELECT_ALL, as the latter
+     * signifies a private key structure, where everything else is assumed
+     * to be present as well.
+     */
+    if (selection == 0)
+        selection = ctx->desc->selection_mask;
+    if ((selection & ctx->desc->selection_mask) == 0) {
+        ERR_raise(ERR_LIB_PROV, ERR_R_PASSED_INVALID_ARGUMENT);
+        return 0;
+    }
+
+    ok = fake_rsa_read_der(ctx->provctx, cin, &der, &der_len);
+    if (!ok)
+        goto next;
+
+    ok = 0;                      /* Assume that we fail */
+
+    if ((selection & OSSL_KEYMGMT_SELECT_PRIVATE_KEY) != 0) {
+        derp = der;
+        if (ctx->desc->d2i_PKCS8 != NULL) {
+            key = ctx->desc->d2i_PKCS8(NULL, &derp, der_len, ctx);
+            if (ctx->flag_fatal)
+                goto end;
+        } else if (ctx->desc->d2i_private_key != NULL) {
+            key = ctx->desc->d2i_private_key(NULL, &derp, der_len);
+        }
+        if (key == NULL && ctx->selection != 0)
+            goto next;
+    }
+    if (key == NULL && (selection & OSSL_KEYMGMT_SELECT_PUBLIC_KEY) != 0) {
+        derp = der;
+        if (ctx->desc->d2i_PUBKEY != NULL)
+            key = ctx->desc->d2i_PUBKEY(NULL, &derp, der_len);
+        else
+            key = ctx->desc->d2i_public_key(NULL, &derp, der_len);
+        if (key == NULL && ctx->selection != 0)
+            goto next;
+    }
+    if (key == NULL && (selection & OSSL_KEYMGMT_SELECT_ALL_PARAMETERS) != 0) {
+        derp = der;
+        if (ctx->desc->d2i_key_params != NULL)
+            key = ctx->desc->d2i_key_params(NULL, &derp, der_len);
+        if (key == NULL && ctx->selection != 0)
+            goto next;
+    }
+
+    /*
+     * Last minute check to see if this was the correct type of key.  This
+     * should never lead to a fatal error, i.e. the decoding itself was
+     * correct, it was just an unexpected key type.  This is generally for
+     * classes of key types that have subtle variants, like RSA-PSS keys as
+     * opposed to plain RSA keys.
+     */
+    if (key != NULL
+        && ctx->desc->check_key != NULL
+        && !ctx->desc->check_key(key, ctx)) {
+        ctx->desc->free_key(key);
+        key = NULL;
+    }
+
+    if (key != NULL && ctx->desc->adjust_key != NULL)
+        ctx->desc->adjust_key(key, ctx);
+
+ next:
+    /*
+     * Indicated that we successfully decoded something, or not at all.
+     * Ending up "empty handed" is not an error.
+     */
+    ok = 1;
+
+    /*
+     * We free memory here so it's not held up during the callback, because
+     * we know the process is recursive and the allocated chunks of memory
+     * add up.
+     */
+    OPENSSL_free(der);
+    der = NULL;
+
+    if (key != NULL) {
+        OSSL_PARAM params[4];
+        int object_type = OSSL_OBJECT_PKEY;
+
+        params[0] =
+            OSSL_PARAM_construct_int(OSSL_OBJECT_PARAM_TYPE, &object_type);
+        params[1] =
+            OSSL_PARAM_construct_utf8_string(OSSL_OBJECT_PARAM_DATA_TYPE,
+                                             (char *)ctx->desc->keytype_name,
+                                             0);
+        /* The address of the key becomes the octet string */
+        params[2] =
+            OSSL_PARAM_construct_octet_string(OSSL_OBJECT_PARAM_REFERENCE,
+                                              &key, sizeof(key));
+        params[3] = OSSL_PARAM_construct_end();
+
+        ok = data_cb(params, data_cbarg);
+    }
+
+ end:
+    ctx->desc->free_key(key);
+    OPENSSL_free(der);
+
+    return ok;
+}
+
+static OSSL_FUNC_keymgmt_export_fn *
+fake_rsa_prov_get_keymgmt_export(const OSSL_DISPATCH *fns)
+{
+    /* Pilfer the keymgmt dispatch table */
+    for (; fns->function_id != 0; fns++)
+        if (fns->function_id == OSSL_FUNC_KEYMGMT_EXPORT)
+            return OSSL_FUNC_keymgmt_export(fns);
+
+    return NULL;
+}
+
+static int der2key_export_object(void *vctx,
+                                 const void *reference, size_t reference_sz,
+                                 OSSL_CALLBACK *export_cb, void *export_cbarg)
+{
+    struct der2key_ctx_st *ctx = vctx;
+    OSSL_FUNC_keymgmt_export_fn *export = fake_rsa_prov_get_keymgmt_export(ctx->desc->fns);
+    void *keydata;
+
+    if (reference_sz == sizeof(keydata) && export != NULL) {
+        /* The contents of the reference is the address to our object */
+        keydata = *(void **)reference;
+
+        return export(keydata, ctx->selection, export_cb, export_cbarg);
+    }
+    return 0;
+}
+
+/* ---------------------------------------------------------------------- */
+
+static struct fake_rsa_keydata *fake_rsa_key_from_pkcs8(const PKCS8_PRIV_KEY_INFO *p8inf,
+                                                        OSSL_LIB_CTX *libctx, const char *propq)
+{
+    struct fake_rsa_keydata *key = fake_rsa_keymgmt_new(NULL);
+
+    if (key)
+        key->status = FAKE_RSA_STATUS_DECODED;
+    return key;
+}
+
+#define rsa_evp_type EVP_PKEY_RSA
+
+static void *fake_rsa_d2i_PKCS8(void **key, const unsigned char **der, long der_len,
+                                struct der2key_ctx_st *ctx)
+{
+    return fake_rsa_der2key_decode_p8(der, der_len, ctx,
+                                      (key_from_pkcs8_t *)fake_rsa_key_from_pkcs8);
+}
+
+static void fake_rsa_key_adjust(void *key, struct der2key_ctx_st *ctx)
+{
+}
+
+/* ---------------------------------------------------------------------- */
+
+#define DO_PrivateKeyInfo(keytype)                      \
+    "PrivateKeyInfo", keytype##_evp_type,               \
+        (OSSL_KEYMGMT_SELECT_PRIVATE_KEY),              \
+        NULL,                                           \
+        NULL,                                           \
+        NULL,                                           \
+        fake_rsa_d2i_PKCS8,                             \
+        NULL,                                           \
+        NULL,                                           \
+        fake_rsa_key_adjust,                            \
+        (free_key_fn *)fake_rsa_keymgmt_free
+
+#define DO_SubjectPublicKeyInfo(keytype)                \
+    "SubjectPublicKeyInfo", keytype##_evp_type,         \
+        (OSSL_KEYMGMT_SELECT_PUBLIC_KEY),               \
+        NULL,                                           \
+        NULL,                                           \
+        NULL,                                           \
+        NULL,                                           \
+        (d2i_of_void *)fake_rsa_d2i_PUBKEY,             \
+        NULL,                                           \
+        fake_rsa_key_adjust,                            \
+        (free_key_fn *)fake_rsa_keymgmt_free
+
+/*
+ * MAKE_DECODER is the single driver for creating OSSL_DISPATCH tables.
+ * It takes the following arguments:
+ *
+ * keytype_name The implementation key type as a string.
+ * keytype      The implementation key type.  This must correspond exactly
+ *              to our existing keymgmt keytype names...  in other words,
+ *              there must exist an ossl_##keytype##_keymgmt_functions.
+ * type         The type name for the set of functions that implement the
+ *              decoder for the key type.  This isn't necessarily the same
+ *              as keytype.  For example, the key types ed25519, ed448,
+ *              x25519 and x448 are all handled by the same functions with
+ *              the common type name ecx.
+ * kind         The kind of support to implement.  This translates into
+ *              the DO_##kind macros above, to populate the keytype_desc_st
+ *              structure.
+ */
+#define MAKE_DECODER(keytype_name, keytype, type, kind)                 \
+    static struct keytype_desc_st kind##_##keytype##_desc =             \
+        { keytype_name, fake_rsa_keymgmt_funcs,                         \
+          DO_##kind(keytype) };                                         \
+                                                                        \
+    static OSSL_FUNC_decoder_newctx_fn kind##_der2##keytype##_newctx;   \
+                                                                        \
+    static void *kind##_der2##keytype##_newctx(void *provctx)           \
+    {                                                                   \
+        return der2key_newctx(provctx, &kind##_##keytype##_desc, keytype_name);\
+    }                                                                   \
+    static int kind##_der2##keytype##_does_selection(void *provctx,     \
+                                                     int selection)     \
+    {                                                                   \
+        return der2key_check_selection(selection,                       \
+                                       &kind##_##keytype##_desc);       \
+    }                                                                   \
+    static const OSSL_DISPATCH                                          \
+    fake_rsa_##kind##_der_to_##keytype##_decoder_functions[] = {        \
+        { OSSL_FUNC_DECODER_NEWCTX,                                     \
+          (void (*)(void))kind##_der2##keytype##_newctx },              \
+        { OSSL_FUNC_DECODER_FREECTX,                                    \
+          (void (*)(void))der2key_freectx },                            \
+        { OSSL_FUNC_DECODER_DOES_SELECTION,                             \
+          (void (*)(void))kind##_der2##keytype##_does_selection },      \
+        { OSSL_FUNC_DECODER_DECODE,                                     \
+          (void (*)(void))fake_rsa_der2key_decode },                    \
+        { OSSL_FUNC_DECODER_EXPORT_OBJECT,                              \
+          (void (*)(void))der2key_export_object },                      \
+        OSSL_DISPATCH_END                                               \
+    }
+
+MAKE_DECODER("RSA", rsa, rsa, PrivateKeyInfo);
+MAKE_DECODER("RSA", rsa, rsa, SubjectPublicKeyInfo);
+
+static const OSSL_ALGORITHM fake_rsa_decoder_algs[] = {
+#define DECODER_PROVIDER "fake-rsa"
+#define DECODER_STRUCTURE_SubjectPublicKeyInfo          "SubjectPublicKeyInfo"
+#define DECODER_STRUCTURE_PrivateKeyInfo                "PrivateKeyInfo"
+
+/* Arguments are prefixed with '_' to avoid build breaks on certain platforms */
+/*
+ * Obviously this is not FIPS approved, but in order to test in conjunction
+ * with the FIPS provider we pretend that it is.
+ */
+
+#define DECODER(_name, _input, _output)                         \
+    { _name,                                                    \
+      "provider=" DECODER_PROVIDER ",fips=yes,input=" #_input,  \
+      (fake_rsa_##_input##_to_##_output##_decoder_functions)    \
+    }
+#define DECODER_w_structure(_name, _input, _structure, _output) \
+    { _name,                                                    \
+      "provider=" DECODER_PROVIDER ",fips=yes,input=" #_input   \
+      ",structure=" DECODER_STRUCTURE_##_structure,             \
+      (fake_rsa_##_structure##_##_input##_to_##_output##_decoder_functions) \
+    }
+
+DECODER_w_structure("RSA:rsaEncryption", der, PrivateKeyInfo, rsa),
+DECODER_w_structure("RSA:rsaEncryption", der, SubjectPublicKeyInfo, rsa),
+#undef DECODER_PROVIDER
+    { NULL, NULL, NULL }
+};
+
 static const OSSL_ALGORITHM *fake_rsa_query(void *provctx,
                                             int operation_id,
                                             int *no_cache)
@@ -716,13 +1225,24 @@ static const OSSL_ALGORITHM *fake_rsa_query(void *provctx,
 
     case OSSL_OP_STORE:
         return fake_rsa_store_algs;
+
+    case OSSL_OP_DECODER:
+        return fake_rsa_decoder_algs;
     }
     return NULL;
 }
 
+static void fake_rsa_prov_teardown(void *provctx)
+{
+    PROV_FAKE_RSA_CTX *pctx = (PROV_FAKE_RSA_CTX *)provctx;
+
+    OSSL_LIB_CTX_free(pctx->libctx);
+    OPENSSL_free(pctx);
+}
+
 /* Functions we provide to the core */
 static const OSSL_DISPATCH fake_rsa_method[] = {
-    { OSSL_FUNC_PROVIDER_TEARDOWN, (void (*)(void))OSSL_LIB_CTX_free },
+    { OSSL_FUNC_PROVIDER_TEARDOWN, (void (*)(void))fake_rsa_prov_teardown },
     { OSSL_FUNC_PROVIDER_QUERY_OPERATION, (void (*)(void))fake_rsa_query },
     OSSL_DISPATCH_END
 };
@@ -731,8 +1251,20 @@ static int fake_rsa_provider_init(const OSSL_CORE_HANDLE *handle,
                                   const OSSL_DISPATCH *in,
                                   const OSSL_DISPATCH **out, void **provctx)
 {
-    if (!TEST_ptr(*provctx = OSSL_LIB_CTX_new()))
+    OSSL_LIB_CTX *libctx;
+    PROV_FAKE_RSA_CTX *prov_ctx;
+
+    if (!TEST_ptr(libctx = OSSL_LIB_CTX_new_from_dispatch(handle, in)))
+        return 0;
+
+    if (!TEST_ptr(prov_ctx = OPENSSL_malloc(sizeof(*prov_ctx)))) {
+        OSSL_LIB_CTX_free(libctx);
         return 0;
+    }
+
+    prov_ctx->libctx = libctx;
+
+    *provctx = prov_ctx;
     *out = fake_rsa_method;
     return 1;
 }
index 4abbdd33ec4d6a1077c0bc05fe839abbf974481d..26e49be4ece2526313b4da9e017389856641c691 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2021-2023 The OpenSSL Project Authors. All Rights Reserved.
+ * Copyright 2021-2025 The OpenSSL Project Authors. All Rights Reserved.
  *
  * Licensed under the Apache License 2.0 (the "License").  You may not use
  * this file except in compliance with the License.  You can obtain a copy
@@ -424,6 +424,146 @@ end:
     return ret;
 }
 
+#define DEFAULT_PROVIDER_IDX    0
+#define FAKE_RSA_PROVIDER_IDX   1
+
+static int reset_ctx_providers(OSSL_LIB_CTX **ctx, OSSL_PROVIDER *providers[2], const char *prop)
+{
+    OSSL_PROVIDER_unload(providers[DEFAULT_PROVIDER_IDX]);
+    providers[DEFAULT_PROVIDER_IDX] = NULL;
+    fake_rsa_finish(providers[FAKE_RSA_PROVIDER_IDX]);
+    providers[FAKE_RSA_PROVIDER_IDX] = NULL;
+    OSSL_LIB_CTX_free(*ctx);
+    *ctx = NULL;
+
+    if (!TEST_ptr(*ctx = OSSL_LIB_CTX_new())
+        || !TEST_ptr(providers[DEFAULT_PROVIDER_IDX] = OSSL_PROVIDER_load(*ctx, "default"))
+        || !TEST_ptr(providers[FAKE_RSA_PROVIDER_IDX] = fake_rsa_start(*ctx))
+        || !TEST_true(EVP_set_default_properties(*ctx, prop)))
+        return 0;
+    return 1;
+}
+
+struct test_pkey_decoder_properties_t {
+    const char *provider_props;
+    const char *explicit_props;
+    int curr_provider_idx;
+};
+
+static int test_pkey_provider_decoder_props(void)
+{
+    OSSL_LIB_CTX *my_libctx = NULL;
+    OSSL_PROVIDER *providers[2] = { NULL };
+    struct test_pkey_decoder_properties_t properties_test[] = {
+        { "?provider=fake-rsa", NULL, FAKE_RSA_PROVIDER_IDX },
+        { "?provider=default", NULL, DEFAULT_PROVIDER_IDX },
+        { NULL, "?provider=fake-rsa", FAKE_RSA_PROVIDER_IDX },
+        { NULL, "?provider=default", DEFAULT_PROVIDER_IDX },
+        { NULL, "provider=fake-rsa", FAKE_RSA_PROVIDER_IDX },
+        { NULL, "provider=default", DEFAULT_PROVIDER_IDX },
+    };
+    EVP_PKEY *pkey = NULL;
+    BIO *bio_priv = NULL;
+    unsigned char *encoded_pub = NULL;
+    int len_pub;
+    const unsigned char *p;
+    PKCS8_PRIV_KEY_INFO *p8 = NULL;
+    size_t i;
+    int ret = 0;
+    const char pem_rsa_priv_key[] =
+        "-----BEGIN PRIVATE KEY-----\n"
+        "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDEkC4ZWv3ucFbU\n"
+        "F8YwlUrmQlLCZwAgr4DPUAFVHl+wFcXypVgScVY4K7QmdWKsYqb8tpOxqw0NwZWX\n"
+        "O+ta4+y27COufoOhRTMwNyN2LwSNTPN3eEk4ee5QnppEyDrqoCgvTlAAdToFaXvj\n"
+        "x13Ybj7jfhwN74qKdqsSEtPWyggeotiQSPy6KyBIuWtIxPAA8jAvfAnQj1eXhghF\n"
+        "N2NxkqgxvBYdNy1m3+jXACPLRzc11ZbNHKiwhCY1/HiSBkwHlIK+/VLj2smCKdUQ\n"
+        "gvLXSnnVgQulHioD6UgY8xA2a4M1rhYuTV8BrPRZ4BFx2o0jYWvGbA/Hlp7fTOy+\n"
+        "F5OkiHS7AgMBAAECggEAYgCu81ZiQBVDvWiDGKr+1pIf2CxprGJEm1h86ZcEx3L7\n"
+        "qFDW+g8HGWd04S3qvh9LublAJzetAPxRXL9zx3PXjJZs7e3HLEuny3TaWez0XI0O\n"
+        "4LSY8S8d6pVBPmUEtwGWN4vYqHnKLXOb4QQAXs4MzfkM/Me/b+zdu1umwjMl3Dud\n"
+        "5rVhkgvt8uhDUG3XSHeoJYBMbT9ikJDVMJ51rre/1Riddgxp8SktVkvGmMl9kQR8\n"
+        "8dv3Px/kTN94EuRg0CkXBhHpoGo4qnM3Q3B5PlmSK5gkuPvWy9l8L/TVt8Lb6/zL\n"
+        "ByQW+g02wxeNGhw1fkD+XFH7KkSeWl+QnrLcePM0hQKBgQDxoqUk0PLOY5WgOkgr\n"
+        "umgie/K1WKs+izTtApjzcM76sza63b5R9w+P+NssMV4aeV9epEGZO68IUmi0QjvQ\n"
+        "npluQoadFYweFwSQ11BXHoeQBA4nNpkrV58hgzZN3m9JLR7JxyrIqXsRnUzl13Kj\n"
+        "GzZBCJxCpJjfTze/yme8d3pa5QKBgQDQP5mB4jI+g3XH3MuLyBjMoTIvoy7CYMhZ\n"
+        "6/+Kkpw132J16mqkLrwUOZfT0e1rJBsCUkEoBmgKNtRkHo3/SjUI/9fHj3uStPHV\n"
+        "oPcfXj/gFRUkDDzY+auB3dHONF1U1z06EANkkPCC3a538UANBIaPjwpRdBzNw1xl\n"
+        "bvn5aCt3HwKBgBfOl4jGEXYmN6K+u0ebqRDkt2gIoW6bFo7Xd6xci/gFWjoVCOBY\n"
+        "gC8GLMnw3z2qgav4cQIg8EDYpbpE4FHQnntPkKW/bru0Nt3yaNb8igy1aZORfIvZ\n"
+        "qTMLE3melcZW7LaiqeN1V0vH/MCUdpX9Y14K9CJYxzsROgPqdEgMWYDFAoGAAe9l\n"
+        "XMieUOhl0sqhdZYRbO1eiwTILXQ6yGMiB8ae/v0pbBEWlpn8k2+JkqVTwHggbCAZ\n"
+        "jOaqVtX1mUyTYzjsTz4ZYjhaHJ3j1WlegoMcstdfT+txMU74ogdOqMzhxSUO45g8\n"
+        "f9W89mpa8bBjOPu+yFy66tDaZ6sWE7c5SXEHXl8CgYEAtAWwFPoDSTdzoXAwRof0\n"
+        "QMO08+PnQGoPbMJTqrgxrHYCS8u4cYSHdDMJDCOMo5gFXyC+5FfTiGwBhy58z5b7\n"
+        "gBwFKI9RgRfV1D/NimxPrlj3WHyec1/Cs+Br+/vekMVFg5gekeHr4aGSF4bk0AjV\n"
+        "Tv/pQjyRuZAt66IbRZdl2II=\n"
+        "-----END PRIVATE KEY-----";
+
+    /* Load private key BIO, DER-encoded public key and PKCS#8 private key for testing */
+    if (!TEST_ptr(bio_priv = BIO_new(BIO_s_mem()))
+        || !TEST_int_gt(BIO_write(bio_priv, pem_rsa_priv_key, sizeof(pem_rsa_priv_key)), 0)
+        || !TEST_ptr(pkey = PEM_read_bio_PrivateKey_ex(bio_priv, NULL, NULL, NULL, NULL, NULL))
+        || !TEST_int_ge(BIO_seek(bio_priv, 0), 0)
+        || !TEST_int_gt((len_pub = i2d_PUBKEY(pkey, &encoded_pub)), 0)
+        || !TEST_ptr(p8 = EVP_PKEY2PKCS8(pkey)))
+        goto end;
+    EVP_PKEY_free(pkey);
+    pkey = NULL;
+
+    for (i = 0; i < OSSL_NELEM(properties_test); i++) {
+        const char *libctx_prop = properties_test[i].provider_props;
+        const char *explicit_prop = properties_test[i].explicit_props;
+        /* *curr_provider will be updated in reset_ctx_providers */
+        OSSL_PROVIDER **curr_provider = &providers[properties_test[i].curr_provider_idx];
+
+        /*
+         * Decoding a PEM-encoded key uses the properties to select the right provider.
+         * Using a PEM-encoding adds an extra decoder before the key is created.
+         */
+        if (!TEST_int_eq(reset_ctx_providers(&my_libctx, providers, libctx_prop), 1))
+            goto end;
+        if (!TEST_int_ge(BIO_seek(bio_priv, 0), 0)
+            || !TEST_ptr(pkey = PEM_read_bio_PrivateKey_ex(bio_priv, NULL, NULL, NULL, my_libctx,
+                                                           explicit_prop))
+            || !TEST_ptr_eq(EVP_PKEY_get0_provider(pkey), *curr_provider))
+            goto end;
+        EVP_PKEY_free(pkey);
+        pkey = NULL;
+
+        /* Decoding a DER-encoded X509_PUBKEY uses the properties to select the right provider */
+        if (!TEST_int_eq(reset_ctx_providers(&my_libctx, providers, libctx_prop), 1))
+            goto end;
+        p = encoded_pub;
+        if (!TEST_ptr(pkey = d2i_PUBKEY_ex(NULL, &p, len_pub, my_libctx, explicit_prop))
+            || !TEST_ptr_eq(EVP_PKEY_get0_provider(pkey), *curr_provider))
+            goto end;
+        EVP_PKEY_free(pkey);
+        pkey = NULL;
+
+        /* Decoding a PKCS8_PRIV_KEY_INFO uses the properties to select the right provider */
+        if (!TEST_int_eq(reset_ctx_providers(&my_libctx, providers, libctx_prop), 1))
+            goto end;
+        if (!TEST_ptr(pkey = EVP_PKCS82PKEY_ex(p8, my_libctx, explicit_prop))
+            || !TEST_ptr_eq(EVP_PKEY_get0_provider(pkey), *curr_provider))
+            goto end;
+        EVP_PKEY_free(pkey);
+        pkey = NULL;
+    }
+
+    ret = 1;
+
+end:
+    PKCS8_PRIV_KEY_INFO_free(p8);
+    BIO_free(bio_priv);
+    OPENSSL_free(encoded_pub);
+    EVP_PKEY_free(pkey);
+    OSSL_PROVIDER_unload(providers[DEFAULT_PROVIDER_IDX]);
+    fake_rsa_finish(providers[FAKE_RSA_PROVIDER_IDX]);
+    OSSL_LIB_CTX_free(my_libctx);
+    return ret;
+}
+
 int setup_tests(void)
 {
     libctx = OSSL_LIB_CTX_new();
@@ -436,6 +576,7 @@ int setup_tests(void)
     ADD_ALL_TESTS(test_pkey_store, 2);
     ADD_TEST(test_pkey_delete);
     ADD_TEST(test_pkey_store_open_ex);
+    ADD_TEST(test_pkey_provider_decoder_props);
 
     return 1;
 }