Skip to content

Commit

Permalink
Fix #372: expose hybrid_classical_ and hybrid_pq_ OSSL_PARAMS f…
Browse files Browse the repository at this point in the history
…or `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 <[email protected]>
  • Loading branch information
thb-sb committed Mar 18, 2024
1 parent f08657b commit 8b278d4
Show file tree
Hide file tree
Showing 14 changed files with 822 additions and 16 deletions.
6 changes: 5 additions & 1 deletion .github/workflows/linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
36 changes: 30 additions & 6 deletions USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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`.

Expand All @@ -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
Expand Down Expand Up @@ -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.
9 changes: 5 additions & 4 deletions oqs-template/generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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)

Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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,
};
Original file line number Diff line number Diff line change
@@ -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,
};

1 change: 1 addition & 0 deletions oqsprov/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
102 changes: 99 additions & 3 deletions oqsprov/oqs_kmgmt.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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[])
{
Expand Down Expand Up @@ -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;
}
Expand Down
13 changes: 11 additions & 2 deletions oqsprov/oqs_prov.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@
#endif

#include <openssl/bio.h>
#include <openssl/opensslconf.h>

#include <openssl/core.h>
#include <openssl/core_names.h>
#include <openssl/e_os2.h>
#include <openssl/opensslconf.h>

#define OQS_PROVIDER_VERSION_STR OQSPROVIDER_VERSION_TEXT

Expand Down Expand Up @@ -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 */

Expand Down
1 change: 1 addition & 0 deletions oqsprov/oqs_sig.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/params.h>
#include <openssl/rsa.h>
#include <openssl/x509.h>

// TBD: Review what we really need/want: For now go with OSSL settings:
Expand Down
1 change: 1 addition & 0 deletions oqsprov/oqsprov_keys.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/params.h>
#include <openssl/rsa.h>
#include <openssl/x509.h>
#include <string.h>

Expand Down
22 changes: 22 additions & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Loading

0 comments on commit 8b278d4

Please sign in to comment.