From 1695e10e402a2d25e57df2ac709d6265f3a2533f Mon Sep 17 00:00:00 2001 From: Richard Levitte Date: Wed, 3 Feb 2021 20:40:37 +0100 Subject: [PATCH] DOCS: Update the internal documentation on EVP_PKEY. Reviewed-by: Tomas Mraz (Merged from https://github.com/openssl/openssl/pull/14059) --- doc/internal/man7/EVP_PKEY.pod | 195 ++++++++++++++++++++++++++++++--- 1 file changed, 178 insertions(+), 17 deletions(-) diff --git a/doc/internal/man7/EVP_PKEY.pod b/doc/internal/man7/EVP_PKEY.pod index 00d4df57f5..7e7c292f85 100644 --- a/doc/internal/man7/EVP_PKEY.pod +++ b/doc/internal/man7/EVP_PKEY.pod @@ -8,37 +8,198 @@ EVP_PKEY - an internal description #include "crypto/evp.h" - struct evp_pkey_st; + typedef struct evp_pkey_st EVP_PKEY; =head1 DESCRIPTION I B is a complex type that's essentially a container for -private/public key key pairs, but has had other uses as well. +private/public key pairs, but has had other uses as well. =for comment "uses" could as well be "abuses"... -It can contain the legacy form of keys -- i.e. pointers to the low-level key types, such as B, B and B --, but also the -provided form of keys -- i.e. pointers to provider side key data. -Those two forms are mutually exclusive; an B instance can't -contain both a key in legacy form and in provided form. Regardless of -form, this key is commonly referred to as the "origin". +The private/public key pair that an B contains is refered to +as its "internal key" or "origin" (the reason for "origin" is +explained further down, in L), +and it can take one of the following forms: -An B also contains a cache of provider side copies of the -key, each adapted for the provider that is going to use that copy to -perform some operation. -For a legacy "origin", the B's functions -export_to() and dirty_cnt() must be implemented for such caching to be -possible. For a provider side "origin", the B's function -OP_keymgmt_export() must be implemented. In all cases, the receiving -B must have an implemented OP_keygmt_import(). +=over 4 + +=item legacy origin + +This is the form that an B in OpenSSL prior to 3.0 had. The +internal key in the B is a pointer to the low-level key +types, such as B, B and B, or an engine driven +structure, and is governed by an associated L and +an L. + +The functions available through those two method structures get full +access to the B and therefore have a lot of freedom to +modify whatever they want. This also means that an B is a +shared structure between libcrypto and any ENGINE that serves such +methods. + +=item provider-native origin + +This is a new form in OpenSSL 3.0, which permits providers to hold the +key data (see L). The internal key in the +B is a pointer to that key data held by the provider, and +is governed by an associated L method structure. + +The functions available through the L have no access +to the B, and can therefore not make any direct changes. +Similarly, the key data that the B points at is only known +to the functions pointed at in the L. + +=back + +These two forms can never co-exist in the same B, the main +reason being that having both at the same time will create problems +with synchronising between the two forms, and potentially make it +confusing which one of the two is the origin. + +=head2 Key mutability + +The B internal keys are mutable. + +This is especially visible with internal legacy keys, since they can +be extracted with functions like L and then +modified at will with functions like L. + +Internal provider native keys are also possible to be modified, if the +associated L implementation allows it. This is done +with L and its specialised derivatives. The +OpenSSL providers allow it for the following: + +=over 4 + +=item DH, EC, X25519, X448: + +It's possible to set the encoded public key. This is supported in +particular through L. + +=item EC: + +It's possible to flip the ECDH cofactor mode. + +=back + +Every time the B internal key mutates, an internal dirty +count is incremented. The need for a dirty count is explained further +in L. + +For provider native origin keys, this doesn't require any help from +the L, the dirty count is maintained in the B +itself, and is incremented every time L or its +specialised derivatives are called. +For legacy origin keys, this requires the associated +L to implement the dirty_cnt() function. All +of OpenSSL's built-in L implement this +function. + +=head2 Export cache for provider operations + +OpenSSL 3.0 can handle operations such as signing, encrypting, etc in +diverse providers, potentially others than the provider of the +L. Two providers, possibly from different vendors, +can't be expected to share internal key structures. There are +therefore instances where key data will need to be exported to the +provider that is going to perform the operation (this also implies +that every provider that implements a key pair based operation must +also implement an L). + +For performance reasons, libcrypto tries to minimize the need to +perform such an export, so it maintains a cache of such exports in the +B. Each cache entry has two items, a pointer to the +provider side key data and the associated L. + +I + +The export to the operation key cache can be performed independent of +what form the origin has. +For a legacy origin, this requires that the associated +L implements the functions export_to() and +dirty_cnt(). +For a provider native origin, this requires that the associated +L implements the OSSL_FUNC_keymgmt_export() function +(see L). +In all cases, the receiving L (the one associated with +the exported key data) must implement OSSL_FUNC_keymgmt_import(). If such caching isn't supported, the operations that can be performed -with that key are limited to the same backend as the "origin" key -(ENGINE for legacy "origin" keys, provider for provider side "origin" +with that key are limited to the same backend as the origin key +(ENGINE for legacy origin keys, provider for provider side origin keys). +=head3 Exporting implementation details + + +Exporting a key to the operation cache involves the following: + +=over 4 + +=item 1. + +Check if the dirty count for the internal origin key has changed since +the previous time. This is done by comparing it with a copy of the +dirty count, which is maintained by the export function. + +If the dirty count has changed, the export cache is cleared. + +=item 2. + +Check if there's an entry in the export cache with the same +L that's the same provider that an export is to be +made to (which is the provider that's going to perform an operation +for which the current B is going to be used). + +If such an entry is found, nothing more is done, the key data and +L found in that export cache entry will be used for +the operation to be performed. + +=item 3. + +Export the internal origin key to the provider, using the appropriate +method. + +For legacy origin keys, that's done with the help of the +L export_to() function. + +For provider native origin keys, that's done by retrieving the key +data in L form from the origin keys, using the +OSSL_FUNC_keymgmt_export() functions of the associated +L, and sending that data to the L of +the provider that's to perform the operation, using its +OSSL_FUNC_keymgmt_import() function. + +=back + +=head2 Upgrading and downgrading a key + +An B with a legacy origin will I be upgraded to +become an B with a provider native origin. Instead, we have +the operation cache as described above, that takes care of the needs +of the diverse operation the application may want to perform. + +An B with a provider native origin, I be downgraded to +be I into an B with a legacy origin. Because +an B can't have two origins, it means that it stops having a +provider native origin. The previous provider native key data is +moved to the operation cache. Downgrading is performed with the +internal function L. + +I, and possibly surprising, +and should therefore be done I, but is needed +to be able to support functions like L. +The general recommendation is to use L +whenever possible, which it should be if the need for a legacy origin +is only internal, or better yet, to remove the need for downgrade at +all. + =head1 SEE ALSO L