From b4cf702e8573bf72fdd41f72317f44e43660ac09 Mon Sep 17 00:00:00 2001 From: thb-sb Date: Fri, 15 Mar 2024 15:00:11 +0100 Subject: [PATCH] Fix #372: expose `hybrid_classical_` and `hybrid_pq_` `OSSL_PARAMS` for `EVP_PKEY`. This commit is an attempt to fix #372, by adding four new [`OSSL_PARAM`] to [`EVP_PKEY`]. The following [`OSSL_PARAM`] are added by this commit: - `hybrid_classical_pub`: an octet string to the classical public key. - `hybrid_classical_priv`: an octet string to the classical private key. - `hybrid_pq_pub`: an octet string to the quantum-resistant public key. - `hybrid_pq_priv`: an octet string to the quantum-resistant private key. Using [`EVP_PKEY_get_params`], OpenSSL users should be able to extract the specific subkey they want from an hybrid key. A test called `test_evp_pkey_params` has been added to ensure that it works with all hybrid algorithms, to also ensure that the output of these parameters are consistent between each other. [`OSSL_PARAM`]: https://www.openssl.org/docs/man3.2/man3/OSSL_PARAM.html [`EVP_PKEY`]: https://www.openssl.org/docs/man3.2/man7/evp.html [`EVP_PKEY_get_params`]: https://www.openssl.org/docs/man3.2/man3/EVP_PKEY_get_params.html Signed-off-by: thb-sb --- .github/workflows/linux.yml | 6 +- USAGE.md | 36 +- oqs-template/generate.py | 9 +- .../hybrid_kem_algs.fragment | 11 + .../hybrid_sig_algs.fragment | 14 + oqsprov/CMakeLists.txt | 1 + oqsprov/oqs_kmgmt.c | 102 ++- oqsprov/oqs_prov.h | 13 +- oqsprov/oqs_sig.c | 1 + oqsprov/oqsprov.c | 9 +- oqsprov/oqsprov_keys.c | 1 + test/CMakeLists.txt | 22 + test/oqs_test_evp_pkey_params.c | 608 ++++++++++++++++++ test/test_common.c | 7 + test/test_common.h | 7 + 15 files changed, 826 insertions(+), 21 deletions(-) create mode 100644 oqs-template/test/oqs_test_evp_pkey_params.c/hybrid_kem_algs.fragment create mode 100644 oqs-template/test/oqs_test_evp_pkey_params.c/hybrid_sig_algs.fragment create mode 100644 test/oqs_test_evp_pkey_params.c diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 9f4b07de..68ef0471 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -145,7 +145,11 @@ jobs: done - name: Run tests - run: ctest --test-dir build --output-on-failure + run: | + ctest --test-dir build \ + --output-on-failure \ + --extra-verbose \ + --repeat until-pass:5 linux_aarch64: name: "aarch64 cross-compilation" diff --git a/USAGE.md b/USAGE.md index f817f5c5..388ad32f 100644 --- a/USAGE.md +++ b/USAGE.md @@ -79,7 +79,7 @@ can be registered for testing. If this configuration variable is not set, the global environment variable "OPENSSL_MODULES" must point to a directory where the `oqsprovider` binary -is to be found. +is to be found. If the `oqsprovider` binary cannot be found, it simply (and silently) will not be available for use. @@ -160,15 +160,15 @@ The following section provides example commands for certain standard OpenSSL ope ### Checking provider version information - openssl list -providers -verbose + openssl list -providers -verbose ### Checking quantum safe signature algorithms available for use - openssl list -signature-algorithms -provider oqsprovider + openssl list -signature-algorithms -provider oqsprovider ### Checking quantum safe KEM algorithms available for use - openssl list -kem-algorithms -provider oqsprovider + openssl list -kem-algorithms -provider oqsprovider ### Creating keys and certificates @@ -222,7 +222,7 @@ Step 1: Create quantum-safe key pair and self-signed certificate: openssl req -x509 -new -newkey dilithium3 -keyout qsc.key -out qsc.crt -nodes -subj "/CN=oqstest" -days 365 -config openssl/apps/openssl.cnf -By changing the `-newkey` parameter algorithm name [any of the +By changing the `-newkey` parameter algorithm name [any of the supported quantum-safe or hybrid algorithms](README.md#signature-algorithms) can be utilized instead of the sample algorithm `dilithium3`. @@ -247,7 +247,7 @@ Continuing the example above, the following command verifies the CMS file `signedfile` and outputs the `outputfile`. Its contents should be identical to the original data in `inputfile` above. - openssl cms -verify -CAfile qsc.crt -inform pem -in signedfile -crlfeol -out outputfile + openssl cms -verify -CAfile qsc.crt -inform pem -in signedfile -crlfeol -out outputfile Note that it is also possible to build proper QSC certificate chains using the standard OpenSSL calls. For sample code see @@ -276,3 +276,27 @@ The `dgst` command is not tested for interoperability with [oqs-openssl111](http The OpenSSL [`EVP_PKEY_decapsulate` API](https://www.openssl.org/docs/manmaster/man3/EVP_PKEY_decapsulate.html) specifies an explicit return value for failure. For security reasons, most KEM algorithms available from liboqs do not return an error code if decapsulation failed. Successful decapsulation can instead be implicitly verified by comparing the original and the decapsulated message. +## Supported OpenSSL parameters (`OSSL_PARAM`) + +OpenSSL 3 comes with the [`OSSL_PARAM`](https://www.openssl.org/docs/man3.2/man3/OSSL_PARAM.html) API. +Through these [`OSSL_PARAM`] structures, oqs-provider can expose some useful information +about a specific object. + +### `EVP_PKEY` + +Using the [`EVP_PKEY_get_params`](https://www.openssl.org/docs/man3.2/man3/EVP_PKEY_get_params.html) +API, the following custom parameters are gettable: + + - `OQS_HYBRID_PKEY_PARAM_CLASSICAL_PUB_KEY`: points to the public key of the + classical part of an hybrid key. + - `OQS_HYBRID_PKEY_PARAM_CLASSICAL_PRIV_KEY`: points to the private key of the + classical part of an hybrid key. + - `OQS_HYBRID_PKEY_PARAM_PQ_PUB_KEY`: points to the public key of the + quantum-resistant part of an hybrid key. + - `OQS_HYBRID_PKEY_PARAM_PQ_PRIV_KEY`: points to the private key of the + quantum-resistant part of an hybrid key. + +In case of non hybrid keys, these parameters return `NULL`. + +See the [corresponding test](tests/oqs_test_evp_pkey_params.c) for an example of +how to use [`EVP_PKEY_get_params`] with custom oqs-provider parameters. diff --git a/oqs-template/generate.py b/oqs-template/generate.py index 5888df72..b36433ff 100644 --- a/oqs-template/generate.py +++ b/oqs-template/generate.py @@ -89,7 +89,7 @@ def nist_to_bits(nistlevel): return 192 elif nistlevel==5: return 256 - else: + else: return None def get_tmp_kem_oid(): @@ -100,7 +100,7 @@ def get_tmp_kem_oid(): def complete_config(config): for kem in config['kems']: bits_level = nist_to_bits(get_kem_nistlevel(kem)) - if bits_level == None: + if bits_level == None: print("Cannot find security level for {:s} {:s}".format(kem['family'], kem['name_group'])) exit(1) kem['bit_security'] = bits_level @@ -127,7 +127,7 @@ def complete_config(config): for famsig in config['sigs']: for sig in famsig['variants']: bits_level = nist_to_bits(get_sig_nistlevel(famsig, sig)) - if bits_level == None: + if bits_level == None: print("Cannot find security level for {:s} {:s}. Setting to 0.".format(famsig['family'], sig['name'])) bits_level = 0 sig['security'] = bits_level @@ -230,7 +230,7 @@ def load_config(include_disabled_sigs=False): # extend config with "hybrid_groups" array: config = load_config() # extend config with "hybrid_groups" array -# complete config with "bit_security" and "hybrid_group from +# complete config with "bit_security" and "hybrid_group from # nid_hybrid information config = complete_config(config) @@ -245,6 +245,7 @@ def load_config(include_disabled_sigs=False): populate('oqsprov/oqs_decode_der2key.c', config, '/////') populate('oqsprov/oqsprov_keys.c', config, '/////') populate('scripts/common.py', config, '#####') +populate('test/oqs_test_evp_pkey_params.c', config, '/////') config2 = load_config(include_disabled_sigs=True) config2 = complete_config(config2) diff --git a/oqs-template/test/oqs_test_evp_pkey_params.c/hybrid_kem_algs.fragment b/oqs-template/test/oqs_test_evp_pkey_params.c/hybrid_kem_algs.fragment new file mode 100644 index 00000000..31eccaa8 --- /dev/null +++ b/oqs-template/test/oqs_test_evp_pkey_params.c/hybrid_kem_algs.fragment @@ -0,0 +1,11 @@ + + +/** \brief List of hybrid KEMs. */ +const char *kHybridKEMAlgorithms[] = { +{%- for kem in config['kems'] %} + {%- for hybrid in kem['hybrids'] %} + "{{ hybrid['hybrid_group'] }}_{{ kem['name_group'] }}", + {%- endfor %} +{%- endfor %} +NULL, +}; diff --git a/oqs-template/test/oqs_test_evp_pkey_params.c/hybrid_sig_algs.fragment b/oqs-template/test/oqs_test_evp_pkey_params.c/hybrid_sig_algs.fragment new file mode 100644 index 00000000..e99c33ea --- /dev/null +++ b/oqs-template/test/oqs_test_evp_pkey_params.c/hybrid_sig_algs.fragment @@ -0,0 +1,14 @@ + + +/** \brief List of hybrid signature algorithms. */ +const char *kHybridSignatureAlgorithms[] = { +{% for sig in config['sigs'] %} + {%- for variant in sig['variants'] %} + {%- for classical_alg in variant['mix_with'] -%} + "{{ classical_alg['name'] }}_{{ variant['name'] }}", + {%- endfor -%} + {%- endfor %} +{%- endfor %} +NULL, +}; + diff --git a/oqsprov/CMakeLists.txt b/oqsprov/CMakeLists.txt index 93ba12f3..ad82e7dc 100644 --- a/oqsprov/CMakeLists.txt +++ b/oqsprov/CMakeLists.txt @@ -45,6 +45,7 @@ set_target_properties(oqsprovider PROPERTIES PREFIX "" OUTPUT_NAME "oqsprovider" + PUBLIC_HEADER "oqs_prov.h" ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" VERSION ${OQSPROVIDER_VERSION_TEXT} diff --git a/oqsprov/oqs_kmgmt.c b/oqsprov/oqs_kmgmt.c index a613a022..19a8425e 100644 --- a/oqsprov/oqs_kmgmt.c +++ b/oqsprov/oqs_kmgmt.c @@ -314,9 +314,17 @@ static int oqsx_export(void *keydata, int selection, OSSL_CALLBACK *param_cb, return ok; } -#define OQS_KEY_TYPES() \ - OSSL_PARAM_octet_string(OSSL_PKEY_PARAM_PUB_KEY, NULL, 0), \ - OSSL_PARAM_octet_string(OSSL_PKEY_PARAM_PRIV_KEY, NULL, 0) +#define OQS_HYBRID_KEY_TYPES() \ + OSSL_PARAM_octet_string(OQS_HYBRID_PKEY_PARAM_CLASSICAL_PUB_KEY, NULL, 0), \ + OSSL_PARAM_octet_string(OQS_HYBRID_PKEY_PARAM_CLASSICAL_PRIV_KEY, \ + NULL, 0), \ + OSSL_PARAM_octet_string(OQS_HYBRID_PKEY_PARAM_PQ_PUB_KEY, NULL, 0), \ + OSSL_PARAM_octet_string(OQS_HYBRID_PKEY_PARAM_PQ_PRIV_KEY, NULL, 0) + +#define OQS_KEY_TYPES() \ + OSSL_PARAM_octet_string(OSSL_PKEY_PARAM_PUB_KEY, NULL, 0), \ + OSSL_PARAM_octet_string(OSSL_PKEY_PARAM_PRIV_KEY, NULL, 0), \ + OQS_HYBRID_KEY_TYPES() static const OSSL_PARAM oqsx_key_types[] = {OQS_KEY_TYPES(), OSSL_PARAM_END}; static const OSSL_PARAM *oqs_imexport_types(int selection) @@ -327,6 +335,91 @@ static const OSSL_PARAM *oqs_imexport_types(int selection) return NULL; } +// Tells if a key (SIG, KEM, ECP_HYB_KEM, ECX_HYB_KEM or HYB_SIG) is using +// hybrid algorithm. +// +// Returns 1 if hybrid, else 0. +static int oqsx_key_is_hybrid(const OQSX_KEY *oqsxk) +{ + if ((oqsxk->keytype == KEY_TYPE_ECP_HYB_KEM + || oqsxk->keytype == KEY_TYPE_ECX_HYB_KEM + || oqsxk->keytype == KEY_TYPE_HYB_SIG) + && oqsxk->numkeys == 2 && oqsxk->classical_pkey != NULL) { + OQS_KM_PRINTF("OQSKEYMGMT: key is hybrid\n"); + return 1; + } + return 0; +} + +// Gets the classical params of an hybrid key. + +// Gets hybrid params. +// +// Returns 0 on success. +static int oqsx_get_hybrid_params(OQSX_KEY *key, OSSL_PARAM params[]) +{ + OSSL_PARAM *p; + const void *classical_pubkey = NULL; + const void *classical_privkey = NULL; + const void *pq_pubkey = NULL; + const void *pq_privkey = NULL; + int classical_pubkey_len = 0; + int classical_privkey_len = 0; + int pq_pubkey_len = 0; + int pq_privkey_len = 0; + + if (oqsx_key_is_hybrid(key) != 1) + return 0; + + if (key->numkeys != 2) { + OQS_KM_PRINTF2("OQSKEYMGMT: key is hybrid but key->numkeys = %zu\n", + key->numkeys); + ERR_raise(ERR_LIB_PROV, OQSPROV_R_INTERNAL_ERROR); + return -1; + } + + if (key->comp_pubkey != NULL && key->pubkey != NULL) { + classical_pubkey = key->comp_pubkey[0]; + DECODE_UINT32(classical_pubkey_len, key->pubkey); + } + if (key->comp_privkey != NULL && key->privkey != NULL) { + classical_privkey = key->comp_privkey[0]; + DECODE_UINT32(classical_privkey_len, key->privkey); + } + + if (key->comp_pubkey[1] != NULL) { + pq_pubkey = key->comp_pubkey[1]; + pq_pubkey_len = key->pubkeylen - classical_pubkey_len - SIZE_OF_UINT32; + } + if (key->comp_privkey != NULL) { + pq_privkey = key->comp_privkey[1]; + pq_privkey_len + = key->privkeylen - classical_privkey_len - SIZE_OF_UINT32; + } + + if ((p = OSSL_PARAM_locate(params, OQS_HYBRID_PKEY_PARAM_CLASSICAL_PUB_KEY)) + != NULL + && !OSSL_PARAM_set_octet_string(p, classical_pubkey, + classical_pubkey_len)) + return -1; + if ((p + = OSSL_PARAM_locate(params, OQS_HYBRID_PKEY_PARAM_CLASSICAL_PRIV_KEY)) + != NULL + && !OSSL_PARAM_set_octet_string(p, classical_privkey, + classical_privkey_len)) + return -1; + if ((p = OSSL_PARAM_locate(params, OQS_HYBRID_PKEY_PARAM_PQ_PUB_KEY)) + != NULL + && !OSSL_PARAM_set_octet_string(p, pq_pubkey, pq_pubkey_len)) + return -1; + if ((p = OSSL_PARAM_locate(params, OQS_HYBRID_PKEY_PARAM_PQ_PRIV_KEY)) + != NULL + && !OSSL_PARAM_set_octet_string(p, pq_privkey, pq_privkey_len)) + return -1; + + return 0; +} + // must handle param requests for KEM and SIG keys... static int oqsx_get_params(void *key, OSSL_PARAM params[]) { @@ -384,6 +477,9 @@ static int oqsx_get_params(void *key, OSSL_PARAM params[]) return 0; } + if (oqsx_get_hybrid_params(oqsxk, params)) + return 0; + // not passing in params to respond to is no error return 1; } diff --git a/oqsprov/oqs_prov.h b/oqsprov/oqs_prov.h index 847e6f65..33b666b8 100644 --- a/oqsprov/oqs_prov.h +++ b/oqsprov/oqs_prov.h @@ -16,10 +16,10 @@ #endif #include -#include - #include +#include #include +#include #define OQS_PROVIDER_VERSION_STR OQSPROVIDER_VERSION_TEXT @@ -47,6 +47,15 @@ #define OQSPROV_R_WRONG_PARAMETERS 13 #define OQSPROV_R_VERIFY_ERROR 14 #define OQSPROV_R_EVPINFO_MISSING 15 +#define OQSPROV_R_INTERNAL_ERROR 16 + +/* Extra OpenSSL parameters for hybrid EVP_PKEY. */ +#define OQS_HYBRID_PKEY_PARAM_CLASSICAL_PUB_KEY \ + "hybrid_classical_" OSSL_PKEY_PARAM_PUB_KEY +#define OQS_HYBRID_PKEY_PARAM_CLASSICAL_PRIV_KEY \ + "hybrid_classical_" OSSL_PKEY_PARAM_PRIV_KEY +#define OQS_HYBRID_PKEY_PARAM_PQ_PUB_KEY "hybrid_pq_" OSSL_PKEY_PARAM_PUB_KEY +#define OQS_HYBRID_PKEY_PARAM_PQ_PRIV_KEY "hybrid_pq_" OSSL_PKEY_PARAM_PRIV_KEY /* Extras for OQS extension */ diff --git a/oqsprov/oqs_sig.c b/oqsprov/oqs_sig.c index 86a4ae3a..3b553fc9 100644 --- a/oqsprov/oqs_sig.c +++ b/oqsprov/oqs_sig.c @@ -19,6 +19,7 @@ #include #include #include +#include #include // TBD: Review what we really need/want: For now go with OSSL settings: diff --git a/oqsprov/oqsprov.c b/oqsprov/oqsprov.c index 64ca0256..d4df4e55 100644 --- a/oqsprov/oqsprov.c +++ b/oqsprov/oqsprov.c @@ -861,8 +861,7 @@ static const OSSL_ALGORITHM oqsprovider_asym_kems[] = { ///// OQS_TEMPLATE_FRAGMENT_KEM_FUNCTIONS_END {NULL, NULL, NULL}}; -static const OSSL_ALGORITHM oqsprovider_keymgmt[] - = { +static const OSSL_ALGORITHM oqsprovider_keymgmt[] = { ///// OQS_TEMPLATE_FRAGMENT_KEYMGMT_FUNCTIONS_START // clang-format off @@ -1037,9 +1036,9 @@ static const OSSL_ALGORITHM oqsprovider_keymgmt[] KEMKMHYBALG(p521_hqc256, 256, ecp) #endif - // clang-format on - ///// OQS_TEMPLATE_FRAGMENT_KEYMGMT_FUNCTIONS_END - {NULL, NULL, NULL}}; + // clang-format on + ///// OQS_TEMPLATE_FRAGMENT_KEYMGMT_FUNCTIONS_END + {NULL, NULL, NULL}}; static const OSSL_ALGORITHM oqsprovider_encoder[] = { #define ENCODER_PROVIDER "oqsprovider" diff --git a/oqsprov/oqsprov_keys.c b/oqsprov/oqsprov_keys.c index 53e96a07..8e33ef6b 100644 --- a/oqsprov/oqsprov_keys.c +++ b/oqsprov/oqsprov_keys.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 1c5fd96a..9c4c0854 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -110,11 +110,33 @@ set_tests_properties(oqs_endecode ) endif() +add_executable(oqs_test_evp_pkey_params oqs_test_evp_pkey_params.c test_common.c) +target_include_directories(oqs_test_evp_pkey_params PRIVATE "../oqsprov") +target_link_libraries(oqs_test_evp_pkey_params PRIVATE ${OPENSSL_CRYPTO_LIBRARY} ${OQS_ADDL_SOCKET_LIBS}) +add_test( + NAME oqs_evp_pkey_params + COMMAND oqs_test_evp_pkey_params + "oqsprovider" + "${CMAKE_CURRENT_SOURCE_DIR}/openssl-ca.cnf" +) +# openssl under MSVC seems to have a bug registering NIDs: +# It only works when setting OPENSSL_CONF, not when loading the same cnf file: +if (MSVC) +set_tests_properties(oqs_evp_pkey_params + PROPERTIES ENVIRONMENT "OPENSSL_MODULES=${OQS_PROV_BINARY_DIR};OPENSSL_CONF=${CMAKE_CURRENT_SOURCE_DIR}/openssl-ca.cnf" +) +else() +set_tests_properties(oqs_evp_pkey_params + PROPERTIES ENVIRONMENT "OPENSSL_MODULES=${OQS_PROV_BINARY_DIR}" +) +endif() + if (OQS_PROVIDER_BUILD_STATIC) targets_set_static_provider(oqs_test_signatures oqs_test_kems oqs_test_groups oqs_test_tlssig oqs_test_endecode + oqs_test_evp_pkey_params ) endif() diff --git a/test/oqs_test_evp_pkey_params.c b/test/oqs_test_evp_pkey_params.c new file mode 100644 index 00000000..a1650f8c --- /dev/null +++ b/test/oqs_test_evp_pkey_params.c @@ -0,0 +1,608 @@ +// SPDX-License-Identifier: Apache-2.0 AND MIT + +#undef USE_ENCODING_LIB +#include "oqs_prov.h" +#include "test_common.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +///// OQS_TEMPLATE_FRAGMENT_HYBRID_SIG_ALGS_START + +/** \brief List of hybrid signature algorithms. */ +const char *kHybridSignatureAlgorithms[] = { + "p256_dilithium2", + "rsa3072_dilithium2", + "p384_dilithium3", + "p521_dilithium5", + "p256_mldsa44", + "rsa3072_mldsa44", + "p384_mldsa65", + "p521_mldsa87", + "p256_falcon512", + "rsa3072_falcon512", + "p256_falconpadded512", + "rsa3072_falconpadded512", + "p521_falcon1024", + "p521_falconpadded1024", + "p256_sphincssha2128fsimple", + "rsa3072_sphincssha2128fsimple", + "p256_sphincssha2128ssimple", + "rsa3072_sphincssha2128ssimple", + "p384_sphincssha2192fsimple", + "p256_sphincsshake128fsimple", + "rsa3072_sphincsshake128fsimple", + NULL, +}; +///// OQS_TEMPLATE_FRAGMENT_HYBRID_SIG_ALGS_END + +///// OQS_TEMPLATE_FRAGMENT_HYBRID_KEM_ALGS_START + +/** \brief List of hybrid KEMs. */ +const char *kHybridKEMAlgorithms[] = { + "p256_frodo640aes", "x25519_frodo640aes", "p256_frodo640shake", + "x25519_frodo640shake", "p384_frodo976aes", "x448_frodo976aes", + "p384_frodo976shake", "x448_frodo976shake", "p521_frodo1344aes", + "p521_frodo1344shake", "p256_kyber512", "x25519_kyber512", + "p384_kyber768", "x448_kyber768", "x25519_kyber768", + "p256_kyber768", "p521_kyber1024", "p256_mlkem512", + "x25519_mlkem512", "p384_mlkem768", "x448_mlkem768", + "x25519_mlkem768", "p256_mlkem768", "p521_mlkem1024", + "p384_mlkem1024", "p256_bikel1", "x25519_bikel1", + "p384_bikel3", "x448_bikel3", "p521_bikel5", + "p256_hqc128", "x25519_hqc128", "p384_hqc192", + "x448_hqc192", "p521_hqc256", NULL, +}; ///// OQS_TEMPLATE_FRAGMENT_HYBRID_KEM_ALGS_END + +/** \brief Indicates if a string is in a given list of strings. + * + * \param list List of strings. + * \param s String to test. + * + * \return 1 if `s` is in `list`, else 0. */ +static int is_string_in_list(const char **list, const char *s) +{ + for (; *list != NULL && strcmp(*list, s) != 0; ++list) + ; + if (*list != NULL) { + return 1; + } + return 0; +} + +/** \brief Indicates if a signature algorithm is hybrid or not. + * + * \param alg Algorithm name. + * + * \returns 1 if hybrid, else 0. */ +#define is_signature_algorithm_hybrid(_alg_) \ + is_string_in_list(kHybridSignatureAlgorithms, (_alg_)) + +/** \brief Indicates if an kem algorithm is hybrid or not. + * + * \param alg Algorithm name. + * + * \returns 1 if hybrid, else 0. */ +#define is_kem_algorithm_hybrid(_alg_) \ + is_string_in_list(kHybridKEMAlgorithms, (_alg_)) + +/** \brief A pair of keys. */ +struct KeyPair { + /** \brief The public key. */ + uint8_t *pubkey; + + /** \brief The public key length, in bytes. */ + size_t pubkey_len; + + /** \brief The private key. */ + uint8_t *privkey; + + /** \brief The private key length, in bytes. */ + size_t privkey_len; + + /** \brief Indicates if the pair of keys is from a quantum-resistant + * algorithm (1) or not (0). */ + int is_pq; +}; + +/** \brief Frees the memory occupied by a KeyPair. + * + * \param kp Keypair to free. */ +static void keypair_free(struct KeyPair *kp) +{ + free(kp->pubkey); + free(kp->privkey); +} + +/** \brief Initializes an OpenSSL top-level context. + * + * \returns The top-level context, or `NULL` if an error occurred. */ +static OSSL_LIB_CTX *init_openssl(void) +{ + OSSL_LIB_CTX *ctx; + + if (!(ctx = OSSL_LIB_CTX_new())) { + fputs(cRED "failed to initialize a new `OSSL_LIB_CTX` " cNORM "\n", + stderr); + } + + return ctx; +} + +/** \brief Initializes a context for the EVP_PKEY API. + * + * \param libctx Top-level OpenSSL context. + * \paran alg The algorithm to use. + * + * \returns The EVP_PKEY context, or `NULL` if an error occurred. */ +static EVP_PKEY_CTX *init_EVP_PKEY_CTX(OSSL_LIB_CTX *libctx, const char *alg) +{ + EVP_PKEY_CTX *ctx; + + if (!(ctx = EVP_PKEY_CTX_new_from_name(libctx, alg, NULL))) { + fprintf(stderr, + cRED "`EVP_PKEY_CTX_new_from_name` failed with algorithm %s: ", + alg); + ERR_print_errors_fp(stderr); + fputs(cNORM "\n", stderr); + } + + return ctx; +} + +/** \brief Initializes the keygen operation on an EVP_PKEY context. + * + * \param ctx EVP_PKEY context. + * + * \returns 0 on success. */ +static int init_keygen(EVP_PKEY_CTX *ctx) +{ + int err; + + if ((err = EVP_PKEY_keygen_init(ctx)) == -2) { + fputs(cRED + "`EVP_PKEY_keygen_init` failed, couldn't initialize keygen: not " + "supported" cNORM "\n", + stderr); + } else if (err <= 0) { + fputs(cRED + "`EVP_PKEY_keygen_init` failed, couldn't initialize keygen: ", + stderr); + ERR_print_errors_fp(stderr); + fputs(cNORM "\n", stderr); + } + + return err; +} + +/** \brief Generates the private key. + * + * \param ctx EVP_PKEY context. + * + * \returns The private key, or `NULL` if an error occurred. */ +static EVP_PKEY *generate_private_key(EVP_PKEY_CTX *ctx) +{ + EVP_PKEY *private_key = NULL; + int err; + + if ((err = EVP_PKEY_generate(ctx, &private_key)) == -2) { + fputs( + cRED + "`EVP_PKEY_generate` failed, couldn't generate: not supported" cNORM + "\n", + stderr); + } else if (err <= 0) { + fputs(cRED "`EVP_PKEY_generate` failed, couldn't generate: ", stderr); + ERR_print_errors_fp(stderr); + fputs(cNORM "\n", stderr); + } + + return private_key; +} + +/** \brief Extracts an octet string from a parameter of an EVP_PKEY. + * + * \param key The EVP_PKEY; + * \param param_name Name of the parameter. + * \param[out] buf Out buffer. + * \param[out] buf_len Size of out buffer. + * + * \returns 0 on success. */ +static int get_param_octet_string(const EVP_PKEY *key, const char *param_name, + uint8_t **buf, size_t *buf_len) +{ + *buf = NULL; + *buf_len = 0; + int ret = -1; + + if (EVP_PKEY_get_octet_string_param(key, param_name, NULL, 0, buf_len) + != 1) { + fprintf(stderr, + cRED + "`EVP_PKEY_get_octet_string_param` failed with param `%s`: ", + param_name); + ERR_print_errors_fp(stderr); + fputs(cNORM "\n", stderr); + goto out; + } + if (!(*buf = malloc(*buf_len))) { + fprintf(stderr, "failed to allocate %#zx byte(s)\n", *buf_len); + goto out; + } + if (EVP_PKEY_get_octet_string_param(key, param_name, *buf, *buf_len, + buf_len) + != 1) { + fprintf(stderr, + cRED + "`EVP_PKEY_get_octet_string_param` failed with param `%s`: ", + param_name); + ERR_print_errors_fp(stderr); + fputs(cNORM "\n", stderr); + free(*buf); + *buf = NULL; + } else { + ret = 0; + } + +out: + return ret; +} + +/** \brief Extracts the classical keys from an hybrid key. + * + * \param private_key The private key. + * \param[out] out Key pair where to write the keys. + * + * \returns 0 on success. */ +static int private_key_params_get_classical_keys(const EVP_PKEY *private_key, + struct KeyPair *out) +{ + int ret = -1; + + if (get_param_octet_string(private_key, + OQS_HYBRID_PKEY_PARAM_CLASSICAL_PUB_KEY, + &out->pubkey, &out->pubkey_len)) { + goto out; + } + if (get_param_octet_string(private_key, + OQS_HYBRID_PKEY_PARAM_CLASSICAL_PRIV_KEY, + &out->privkey, &out->privkey_len)) { + goto free_pubkey; + } + ret = 0; + goto out; + +free_pubkey: + free(out->pubkey); + +out: + return ret; +} + +/** \brief Extracts the quantum-resistant keys from an hybrid key. + * + * \param private_key The private key. + * \param[out] out Key pair where to write the keys. + * + * \returns 0 on success. */ +static int private_key_params_get_pq_keys(const EVP_PKEY *private_key, + struct KeyPair *out) +{ + int ret = -1; + + if (get_param_octet_string(private_key, OQS_HYBRID_PKEY_PARAM_PQ_PUB_KEY, + &out->pubkey, &out->pubkey_len)) { + goto out; + } + if (get_param_octet_string(private_key, OQS_HYBRID_PKEY_PARAM_PQ_PRIV_KEY, + &out->privkey, &out->privkey_len)) { + goto free_pubkey; + } + ret = 0; + goto out; + +free_pubkey: + free(out->pubkey); + +out: + return ret; +} + +/** \brief Extracts the combination of classical+hybrid keys from an hybrid key. + * + * \param private_key The private key. + * \param[out] out Key pair where to write the keys. + * + * \returns 0 on success. */ +static int private_key_params_get_full_keys(const EVP_PKEY *private_key, + struct KeyPair *out) +{ + int ret = -1; + + if (get_param_octet_string(private_key, OSSL_PKEY_PARAM_PUB_KEY, + &out->pubkey, &out->pubkey_len)) { + goto out; + } + if (get_param_octet_string(private_key, OSSL_PKEY_PARAM_PRIV_KEY, + &out->privkey, &out->privkey_len)) { + goto free_pubkey; + } + ret = 0; + goto out; + +free_pubkey: + free(out->pubkey); + +out: + return ret; +} + +/** \brief Reconstitutes the combination of a classical key and a + * quantum-resistant key. + * + * \param classical Classical key. + * \param classical_n Length in bytes of `classical`. + * \param pq Quantum-resistant key. + * \param pq_n Length in bytes of `pq`. + * \param[out] buf Out buffer. + * \param[out] buf_n Length in bytes of `buf`. + * + * \returns 0 on success. */ +static int reconstitute_keys(const uint8_t *classical, const size_t classical_n, + const uint8_t *pq, const size_t pq_n, + uint8_t **buf, size_t *buf_len) +{ + uint32_t header; + int ret = -1; + + *buf_len = sizeof(uint32_t) + classical_n + pq_n; + if (!(*buf = malloc(*buf_len))) { + fprintf(stderr, cRED "failed to allocate %#zx byte(s)" cNORM "\n", + *buf_len); + goto out; + } + header = classical_n; + (*buf)[0] = header >> 0x18; + (*buf)[1] = header >> 0x10; + (*buf)[2] = header >> 0x8; + (*buf)[3] = header; + memcpy(*buf + sizeof(header), classical, classical_n); + memcpy(*buf + sizeof(header) + classical_n, pq, pq_n); + ret = 0; + +out: + return ret; +} + +/** \brief Verifies the consistency between pairs of keys. + * + * \param classical The classical keypair. + * \param pq The quantum-resistant keypair. + * \param comb The combination of both classical+quantum-resistant keypairs. + * + * \returns 0 on success. */ +static int keypairs_verify_consistency(const struct KeyPair *classical, + const struct KeyPair *pq, + const struct KeyPair *comb) +{ + uint8_t *reconstitution; + size_t n; + int ret = -1; + + if (reconstitute_keys(classical->pubkey, classical->pubkey_len, pq->pubkey, + pq->pubkey_len, &reconstitution, &n)) { + goto out; + } + if (n != comb->pubkey_len) { + fprintf( + stderr, + cRED + "expected %#zx byte(s) for reconstitution of pubkey, got %#zx" cNORM + "\n", + comb->pubkey_len, n); + goto free_reconstitute; + } + if (memcmp(reconstitution, comb->pubkey, n)) { + fputs(cRED "pubkey and comb->pubkey differ " cNORM "\n", stderr); + fputs(cRED "pubkey: ", stderr); + hexdump(reconstitution, n); + fputs("\ncomb->pubkey: ", stderr); + hexdump(comb->pubkey, n); + fputs(cNORM "\n", stderr); + goto free_reconstitute; + } + free(reconstitution); + + if (reconstitute_keys(classical->privkey, classical->privkey_len, + pq->privkey, pq->privkey_len, &reconstitution, &n)) { + goto out; + } + if (n != comb->privkey_len) { + fprintf( + stderr, + "expected %#zx byte(s) for reconstitution of privkey, got %#zx\n", + comb->privkey_len, n); + goto free_reconstitute; + } + if (memcmp(reconstitution, comb->privkey, n)) { + fputs(cRED "privkey and comb->privkey differ" cNORM "\n", stderr); + fputs(cRED "privkey: ", stderr); + hexdump(reconstitution, n); + fputs("\ncomb->privkey: ", stderr); + hexdump(comb->privkey, n); + fputs(cNORM "\n", stderr); + goto free_reconstitute; + } + puts("consistency is OK"); + ret = 0; + +free_reconstitute: + free(reconstitution); + +out: + return ret; +} + +/** \brief Tests an algorithm. + * + * \param libctx Top-level OpenSSL context. + * \param algname Algorithm name. + * + * \returns 0 on success. */ +static int test_algorithm(OSSL_LIB_CTX *libctx, const char *algname) +{ + EVP_PKEY_CTX *evp_pkey_ctx; + EVP_PKEY *private_key; + struct KeyPair classical_keypair; + struct KeyPair pq_keypair; + struct KeyPair full_keypair; + int ret = -1; + + if (!(evp_pkey_ctx = init_EVP_PKEY_CTX(libctx, algname))) { + goto out; + } + + if (init_keygen(evp_pkey_ctx) != 1) { + goto free_evp_pkey_ctx; + } + + if (!(private_key = generate_private_key(evp_pkey_ctx))) { + goto free_evp_pkey_ctx; + } + + if (private_key_params_get_classical_keys(private_key, + &classical_keypair)) { + goto free_private_key; + } + + if (private_key_params_get_pq_keys(private_key, &pq_keypair)) { + goto free_classical_keypair; + } + + if (private_key_params_get_full_keys(private_key, &full_keypair)) { + goto free_pq_keypair; + } + + if (!keypairs_verify_consistency(&classical_keypair, &pq_keypair, + &full_keypair)) { + ret = 0; + } + + keypair_free(&full_keypair); + +free_pq_keypair: + keypair_free(&pq_keypair); + +free_classical_keypair: + keypair_free(&classical_keypair); + +free_private_key: + EVP_PKEY_free(private_key); + +free_evp_pkey_ctx: + EVP_PKEY_CTX_free(evp_pkey_ctx); + +out: + return ret; +} + +int main(int argc, char **argv) +{ + OSSL_LIB_CTX *libctx; + OSSL_PROVIDER *default_provider; + OSSL_PROVIDER *oqs_provider; + const char *modulename; + const char *configfile; + const OSSL_ALGORITHM *algs; + int query_nocache; + int errcnt; + int ret = EXIT_FAILURE; + + if (!(libctx = init_openssl())) { + goto end; + } + + if (!(default_provider = load_default_provider(libctx))) { + goto free_libctx; + } + + T(argc == 3); + modulename = argv[1]; + configfile = argv[2]; + + load_oqs_provider(libctx, modulename, configfile); + if (!(oqs_provider = OSSL_PROVIDER_load(libctx, modulename))) { + fputs(cRED " `oqs_provider` is NULL " cNORM "\n", stderr); + goto unload_default_provider; + } + + errcnt = 0; + algs = OSSL_PROVIDER_query_operation(oqs_provider, OSSL_OP_SIGNATURE, + &query_nocache); + if (!algs) { + fprintf(stderr, cRED " No signature algorithms found" cNORM "\n"); + ERR_print_errors_fp(stderr); + ++errcnt; + } + + for (; algs->algorithm_names != NULL; ++algs) { + if (!is_signature_algorithm_hybrid(algs->algorithm_names)) { + continue; + } + if (test_algorithm(libctx, algs->algorithm_names)) { + fprintf(stderr, cRED " failed for %s " cNORM "\n", + algs->algorithm_names); + ++errcnt; + } else { + fprintf(stderr, cGREEN "%s succeeded" cNORM "\n", + algs->algorithm_names); + } + } + + algs = OSSL_PROVIDER_query_operation(oqs_provider, OSSL_OP_KEM, + &query_nocache); + if (!algs) { + fprintf(stderr, cRED " No KEM algorithms found" cNORM "\n"); + ERR_print_errors_fp(stderr); + ++errcnt; + goto unload_oqs_provider; + } + for (; algs->algorithm_names != NULL; ++algs) { + if (!is_kem_algorithm_hybrid(algs->algorithm_names)) { + continue; + } + fprintf(stderr, "testing %s\n", algs->algorithm_names); + if (test_algorithm(libctx, algs->algorithm_names)) { + fprintf(stderr, cRED " failed for %s " cNORM "\n", + algs->algorithm_names); + ++errcnt; + } else { + fprintf(stderr, cGREEN "%s succeeded" cNORM "\n", + algs->algorithm_names); + } + } + + if (errcnt == 0) { + ret = EXIT_SUCCESS; + } + +unload_oqs_provider: + OSSL_PROVIDER_unload(oqs_provider); + +unload_default_provider: + OSSL_PROVIDER_unload(default_provider); + +free_libctx: + OSSL_LIB_CTX_free(libctx); + +end: + return ret; +} \ No newline at end of file diff --git a/test/test_common.c b/test/test_common.c index 19382c9c..cee953d9 100644 --- a/test/test_common.c +++ b/test/test_common.c @@ -35,6 +35,13 @@ int alg_is_enabled(const char *algname) return strstr(algname, alglist) == NULL; } +OSSL_PROVIDER *load_default_provider(OSSL_LIB_CTX *libctx) +{ + OSSL_PROVIDER *provider; + T((provider = OSSL_PROVIDER_load(libctx, "default"))); + return provider; +} + #ifdef OQS_PROVIDER_STATIC # define OQS_PROVIDER_ENTRYPOINT_NAME oqs_provider_init #else diff --git a/test/test_common.h b/test/test_common.h index 844796a0..5cb9161d 100644 --- a/test/test_common.h +++ b/test/test_common.h @@ -36,6 +36,13 @@ void hexdump(const void *ptr, size_t len); int alg_is_enabled(const char *algname); +/** \brief Loads the default provider. + * + * \param libctx Top-level OpenSSL context. + * + * \returns The default provider. */ +OSSL_PROVIDER *load_default_provider(OSSL_LIB_CTX *libctx); + /* Loads the oqs-provider. */ void load_oqs_provider(OSSL_LIB_CTX *libctx, const char *modulename, const char *configfile);