Skip to content

Comments

Simultaneous derivation of several EVP_SKEY objects#29160

Closed
beldmit wants to merge 1 commit intoopenssl:masterfrom
beldmit:multi_skey_design
Closed

Simultaneous derivation of several EVP_SKEY objects#29160
beldmit wants to merge 1 commit intoopenssl:masterfrom
beldmit:multi_skey_design

Conversation

@beldmit
Copy link
Member

@beldmit beldmit commented Nov 17, 2025

A proposed design for using EVP_SKEY objects in the TLS stack

Checklist
  • documentation is added or updated
  • tests are added or updated

@beldmit beldmit added branch: master Applies to master branch triaged: design The issue/pr deals with a design document tests: exempted The PR is exempt from requirements for testing labels Nov 17, 2025
@beldmit beldmit requested review from simo5 and t-j-h November 17, 2025 12:07
@paulidale
Copy link
Contributor

I think that the returned keys should include both their data and length rather than assuming the caller necessarily knows this. This means a public structure or accessor functions.

An SKEY that contains other SKEYs might be a neat solution as hinted at in the document.

@t8m t8m added the approval: review pending This pull request needs review by a committer label Nov 18, 2025
@beldmit
Copy link
Member Author

beldmit commented Nov 18, 2025

think that the returned keys should include both their data and length rather than assuming the caller necessarily knows this. This means a public structure or accessor functions.

If they are handles (PKCS#11), it doesn't apply.

@beldmit
Copy link
Member Author

beldmit commented Nov 18, 2025

An SKEY that contains other SKEYs might be a neat solution as hinted at in the document.

I had this idea on my mind, but what's next?

Smth like EVP_SKEY_get(EVP_SKEY *compose_key, char * purpose) looks ugly and also would create a mess with refcount

@beldmit
Copy link
Member Author

beldmit commented Nov 18, 2025

I adjusted the proposed function signature according to Simo's proposal about returning IVs as raw bytes array and providing the algorithm information, please re-review

@levitte
Copy link
Member

levitte commented Nov 18, 2025

Not sure if I understand. Is there a reason the caller can't make multiple EVP_KDF_derive_SKEY() calls with the same context?

@beldmit
Copy link
Member Author

beldmit commented Nov 18, 2025

@levitte thanks! It gives a different approach - but it means that the context should be able to store the information got from a call, right?

@simo5
Copy link
Contributor

simo5 commented Nov 18, 2025

Not sure if I understand. Is there a reason the caller can't make multiple EVP_KDF_derive_SKEY() calls with the same context?

It is super slow and for TLS you have a single HKDF whose output is chopped in different secrets. So you would repeat the same computation multiple time and then just pick a different part of it and throw away the rest, which in the PKCS#11 case is multiple session keys being created and then thrown away.

A function in PKCS#11 that does this is for example:
https://docs.oasis-open.org/pkcs11/pkcs11-spec/v3.2/pkcs11-spec-v3.2.html#_Toc195693549

Which returns 4 key handles and 2 IVs (see CK_SSL3_KEY_MAT_OUT in https://docs.oasis-open.org/pkcs11/pkcs11-spec/v3.2/pkcs11-spec-v3.2.html#_Toc195693536)

Another similar derivation is the SSH KDF which also produces 4 keys and 4 IVs from the same base material. Being able to do this in a single operation from a token is a major performance and usability benefit.

@beldmit
Copy link
Member Author

beldmit commented Nov 18, 2025

@simo5 if you can store the result in the KDF context, it wouldn't require operations on token - you pass the result between libcrypto and the provider and return the next element.

@simo5
Copy link
Contributor

simo5 commented Nov 18, 2025

Not sure if I understand. Is there a reason the caller can't make multiple EVP_KDF_derive_SKEY() calls with the same context?

It is super slow and for TLS you have a single HKDF whose output is chopped in different secrets. So you would repeat the same computation multiple time and then just pick a different part of it and throw away the rest, which in the PKCS#11 case is multiple session keys being created and then thrown away.

A function in PKCS#11 that does this is for example: https://docs.oasis-open.org/pkcs11/pkcs11-spec/v3.2/pkcs11-spec-v3.2.html#_Toc195693549

Which returns 4 key handles and 2 IVs (see CK_SSL3_KEY_MAT_OUT in https://docs.oasis-open.org/pkcs11/pkcs11-spec/v3.2/pkcs11-spec-v3.2.html#_Toc195693536)

Another similar derivation is the SSH KDF which also produces 4 keys and 4 IVs from the same base material. Being able to do this in a single operation from a token is a major performance and usability benefit.

Ok discussed this a bit with @beldmit we can definitely store multiple key handles internally in the provider's KDF context, and use the normal EVP_PKEY_derive_SKEY() function also for multi-key derives.

Then use an accessor function named something like:
EVP_SKEY *EVP_KDF_CTX_get_key(ctx, name)
to pull individual keys off.

This means either using a get_param dispatcher function to return a pointer, or better add a get_key dispatcher function to return a keydata pointer

@simo5
Copy link
Contributor

simo5 commented Nov 18, 2025

We already have a EVP_KDF_CTX_set_SKEY() which internally does:

    if (ctx->meth->set_skey != NULL && ctx->meth->prov == key->skeymgmt->prov)
        return ctx->meth->set_skey(ctx->algctx, key->keydata, ckey.name);

Adding a EVP_KDF_CTX_get_SKEY() that does something like:

    if (ctx->meth->get_skey != NULL)
        return ctx->meth->get_skey(ctx->algctx, name);

sounds easy enough

@levitte
Copy link
Member

levitte commented Nov 18, 2025

Another thought could be to make it an iterator instead, where a callback is called for every iteration (new EVP_SKEY created), and still leave it to the caller to figure out where to store them all.

"Make me {n} keys and IVs, pass each to this callback", kinda?

I'm thinking that's a viable way to avoid having a STACK_OF() return value (which appears to be desirable)

@simo5
Copy link
Contributor

simo5 commented Nov 18, 2025

A callback could work, but I do not see it as necessary, a getter works just fine, after all you need to know what to do with the keys anyway otherwise you do not need all the churn of going through creating EVP_SKEY structures for them.

@beldmit beldmit requested review from levitte and simo5 November 19, 2025 14:20
@beldmit beldmit requested review from levitte and simo5 December 1, 2025 12:50
@beldmit
Copy link
Member Author

beldmit commented Dec 1, 2025

Updated version pushed

@simo5
Copy link
Contributor

simo5 commented Dec 2, 2025

Just "thinking loudly" here.
The definition of the function still looks heavy handed and not really intuitive to use.

I have been thinking for a while about what information is really needed to perform these kind of KDFs (I am using mostly knowledge of TLS and SSH key derivation to inform my opinion).

Fundamentally we need this kind of info:

Encryption key type needed (AES, ChaCha-Poly, ..) this info applies to both keys, and should not be disjoint.
Along with the type there is also a need to specify length in some cases (AES-128 vs AES-256, etc..)
MAC Key type needed (HMAC, etc..)
As for the encryption keys both MAC keys must be of the same type and same length (which depends on the type)
IVs lengths also go in pair (same length for both, generally length depends on the key type used too).

So basically 3 somewhat interrelated sets:
[ (key type, key length), (mac type), (IV length) ]

So at most 4 parameters (at least for TLS and SSH key derivation).
If we assume that defaults can be implied we can reduce the needed parameters from 4 down to 0 in some cases.

So we could simplify the design of the API to be like this:

int EVP_KDF_derive_SKEYs(EVP_KDF_CTX *ctx, EVP_SKEYMGMT *mgmt,
                         const char *propquery, const OSSL_PARAM params[]);

Where we use params to change defaults as needed.
For example in those KDFs where MAC keys are optional, setting the mac key type to NULL in a parameter would suppress the creation of those keys (similarly setting 0 for Encryption keys or IVs lengths if that is an option).

If all parameters need to be tweaked from defaults from whatever reason the "worst case" code would look something like:

int key_size = 32;
int iv_length = 48;
const OSSL_PARAM params[5] = {
    OSSL_PARAM_construct_utf8_string(OSSL_SKEY_PARAM_CIPHER_KEY_TYPE, "AES"),
    OSSL_PARAM_construct_int(OSSL_SKEY_PARAM_KEY_LENGTH, &key_size),
    OSSL_PARAM_construct_utf8_string(OSSL_SKEY_PARAM_MAC_KEY_TYPE, "HMAC-SHA3-384"),
    OSSL_PARAM_construct_int(OSSL_SKEY_PARAM_IV_LENGTH, &iv_length),
    OSSL_PARAM_construct_end()
};

ctx = EVP_KDF_fetch(libctx, OSSL_KDF_TLS1_PRF, propq);
ret = EVP_KDF_derive_SKEYs(ctx, skeymgmt, propq, params);

I think I would prefer this method of setting up the key derivation because in the case the defaults are sufficient then the above potentially reduces to just:

ctx = EVP_KDF_fetch(libctx, OSSL_KDF_TLS1_PRF, propq);
ret = EVP_KDF_derive_SKEYs(ctx, skeymgmt, propq, NULL);

We could reduce the parameters to just three if we define key type that include the size (ie "AES-128" instead of just "AES") which I would favor.

Of course these could also be explicit arguments to the function (assumed types fully specified including size):

int EVP_KDF_derive_SKEYs(EVP_KDF_CTX *ctx, EVP_SKEYMGMT *mgmt,
                         const char *key_type, const char *mac_type, int ivlen,
                         const char *propquery, const OSSL_PARAM params[]);

The reason why I am hesitant about the above is that if a future KDF comes around that requires something different we'll have a poorly matching function and will need either a EVP_KDF_derive_SKEYs_ex, or start using parameters anyway to provide the additional info ...

@beldmit
Copy link
Member Author

beldmit commented Dec 18, 2025

I pushed the simplified API design inspired by @simo5 comment, please review

A proposed design for using EVP_SKEY objects in the TLS stack
@beldmit beldmit requested review from t-j-h and t8m December 18, 2025 15:42
@beldmit
Copy link
Member Author

beldmit commented Dec 18, 2025

Ping @openssl/committers for the 2nd review and let's put this PR under the Christmas tree

@levitte levitte added approval: done This pull request has the required number of approvals and removed approval: review pending This pull request needs review by a committer labels Jan 14, 2026
@openssl-machine openssl-machine added approval: ready to merge The 24 hour grace period has passed, ready to merge and removed approval: done This pull request has the required number of approvals labels Jan 15, 2026
@openssl-machine
Copy link
Collaborator

This pull request is ready to merge

openssl-machine pushed a commit that referenced this pull request Jan 15, 2026
A proposed design for using EVP_SKEY objects in the TLS stack

Reviewed-by: Richard Levitte <[email protected]>
Reviewed-by: Simo Sorce <[email protected]>
(Merged from #29160)
@beldmit
Copy link
Member Author

beldmit commented Jan 15, 2026

Merged. Thanks for review, it made the design as simple as possible

@beldmit beldmit closed this Jan 15, 2026
@beldmit beldmit deleted the multi_skey_design branch January 15, 2026 13:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approval: ready to merge The 24 hour grace period has passed, ready to merge branch: master Applies to master branch tests: exempted The PR is exempt from requirements for testing triaged: design The issue/pr deals with a design document

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants