Skip to content

Instantly share code, notes, and snippets.

@thb-sb
Created March 14, 2024 13:56
Show Gist options
  • Save thb-sb/1b14a1aadeb381a778b4cdf8cfaf2cd6 to your computer and use it in GitHub Desktop.
Save thb-sb/1b14a1aadeb381a778b4cdf8cfaf2cd6 to your computer and use it in GitHub Desktop.
PoC OpenSSL params for extracting quantum-resistant keys and classical keys from an hybrid pair of keys.
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/core_names.h>
#include <openssl/crypto.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/provider.h>
/** \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. */
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. */
OSSL_LIB_CTX *init_openssl(void) {
OSSL_LIB_CTX *ctx;
if (!(ctx = OSSL_LIB_CTX_new())) {
fputs("failed to initialize a new `OSSL_LIB_CTX`\n", stderr);
}
return ctx;
}
/** \brief Loads the default provider.
*
* \param libctx Top-level OpenSSL context.
*
* \return The default provider, or `NULL` if an error occurred. */
OSSL_PROVIDER *load_default_provider(OSSL_LIB_CTX *libctx) {
OSSL_PROVIDER *provider;
if (!(provider = OSSL_PROVIDER_load(libctx, "default"))) {
fputs("failed to load the `default` provider: ", stderr);
ERR_print_errors_fp(stderr);
fputc('\n', stderr);
}
return provider;
}
/** \brief Loads the oqsprovider provider.
*
* \param libctx Top-level OpenSSL context.
*
* \return The oqsprovider provider, or `NULL` if an error occurred. */
OSSL_PROVIDER *load_oqs_provider(OSSL_LIB_CTX *libctx) {
OSSL_PROVIDER *provider;
if (!(provider = OSSL_PROVIDER_load(libctx, "oqsprovider"))) {
fputs("failed to load the `oqsprovider` provider: ", stderr);
ERR_print_errors_fp(stderr);
fputc('\n', stderr);
}
return provider;
}
/** \brief Returns the signature algorithm to use.
*
* If the `ALG` environment variable is not set, `p521_dilithium5` is returned.
*
* \returns The signature algorithm to use. */
const char *get_signature_algorithm(void) {
const char *alg;
if (!(alg = getenv("ALG"))) {
alg = "p521_dilithium5";
}
return alg;
}
/** \brief Initializes a context for the EVP_PKEY API.
*
* \param libctx Top-level OpenSSL context.
* \paran alg The signature algorithm to use.
*
* \returns The EVP_PKEY context, or `NULL` if an error occurred. */
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,
"`EVP_PKEY_CTX_new_from_name` failed with algorithm %s: ", alg);
ERR_print_errors_fp(stderr);
fputc('\n', stderr);
}
return ctx;
}
/** \brief Initializes the keygen operation on an EVP_PKEY context.
*
* \param ctx EVP_PKEY context.
*
* \returns 0 on success. */
int init_keygen(EVP_PKEY_CTX *ctx) {
int err;
if ((err = EVP_PKEY_keygen_init(ctx)) == -2) {
fputs("`EVP_PKEY_keygen_init` failed, couldn't initialize keygen: not "
"supported\n",
stderr);
} else if (err <= 0) {
fputs("`EVP_PKEY_keygen_init` failed, couldn't initialize keygen: ",
stderr);
ERR_print_errors_fp(stderr);
fputc('\n', stderr);
}
return err;
}
/** \brief Generates the private key.
*
* \param ctx EVP_PKEY context.
*
* \returns The private key, or `NULL` if an error occurred. */
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("`EVP_PKEY_generate` failed, couldn't generate: not supported\n",
stderr);
} else if (err <= 0) {
fputs("`EVP_PKEY_generate` failed, couldn't generate: ", stderr);
ERR_print_errors_fp(stderr);
fputc('\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.
*
* \return 0 on success. */
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,
"`EVP_PKEY_get_octet_string_param` failed with param `%s`: ",
param_name);
ERR_print_errors_fp(stderr);
fputc('\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,
"`EVP_PKEY_get_octet_string_param` failed with param `%s`: ",
param_name);
ERR_print_errors_fp(stderr);
fputc('\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.
*
* \return 0 on success. */
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, "hybrid_classical_pub", &out->pubkey,
&out->pubkey_len)) {
goto out;
}
if (get_param_octet_string(private_key, "hybrid_classical_priv",
&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.
*
* \return 0 on success. */
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, "hybrid_pq_pub", &out->pubkey,
&out->pubkey_len)) {
goto out;
}
if (get_param_octet_string(private_key, "hybrid_pq_priv", &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.
*
* \return 0 on success. */
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 Write output key to file.
*
* \param path Path where to write the key.
* \param key The key to write.
* \param n Size in bytes of `key`.
*
* \return 0 on success. */
int write_key(const char *path, const uint8_t *key, const size_t n) {
FILE *f;
int ret = -1;
if (!(f = fopen(path, "w+"))) {
fprintf(stderr, "failed to open %s\n", path);
goto out;
}
if (fwrite(key, 1, n, f) != n) {
fprintf(stderr, "failed to write %#zx byte(s) to %s\n", n, path);
} else {
ret = 0;
}
fclose(f);
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`.
*
* \return 0 on success. */
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, "failed to allocate %#zx byte(s)\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 Dump a buffer in hex.
*
* \param buf Buffer to dump.
* \param n Length in bytes of `buf`.
* \param stream Stream where to write the dump. */
void dump_buffer(const uint8_t *buf, const size_t n, FILE *stream) {
const uint8_t *end = buf + n;
for (; buf != end; ++buf) {
fprintf(stream, "%02hhx", *buf);
}
}
/** \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.
*
* \return 0 on success. */
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,
"expected %#zx byte(s) for reconstitution of pubkey, got %#zx\n",
comb->pubkey_len, n);
goto free_reconstitute;
}
if (memcmp(reconstitution, comb->pubkey, n)) {
fputs("pubkey and comb->pubkey differ\n", stderr);
fputs("pubkey: ", stderr);
dump_buffer(reconstitution, n, stderr);
fputs("\ncomb->pubkey: ", stderr);
dump_buffer(comb->pubkey, n, stderr);
fputc('\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("privkey and comb->privkey differ\n", stderr);
fputs("privkey: ", stderr);
dump_buffer(reconstitution, n, stderr);
fputs("\ncomb->privkey: ", stderr);
dump_buffer(comb->privkey, n, stderr);
fputc('\n', stderr);
goto free_reconstitute;
}
puts("consistency is OK");
ret = 0;
free_reconstitute:
free(reconstitution);
out:
return ret;
}
int main() {
OSSL_LIB_CTX *libctx;
OSSL_PROVIDER *default_provider;
OSSL_PROVIDER *oqs_provider;
const char *signature_algorithm;
EVP_PKEY_CTX *evp_pkey_ctx;
EVP_PKEY *private_key;
struct KeyPair classical_keypair;
struct KeyPair pq_keypair;
struct KeyPair full_keypair;
int ret = EXIT_FAILURE;
if (!(libctx = init_openssl())) {
goto end;
}
if (!(default_provider = load_default_provider(libctx))) {
goto free_libctx;
}
if (!(oqs_provider = load_oqs_provider(libctx))) {
goto unload_default_provider;
}
signature_algorithm = get_signature_algorithm();
if (!(evp_pkey_ctx = init_EVP_PKEY_CTX(libctx, signature_algorithm))) {
goto unload_oqs_provider;
}
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 (getenv("DUMP_KEYS")) {
write_key("/tmp/classical_pub", classical_keypair.pubkey,
classical_keypair.pubkey_len);
write_key("/tmp/classical_priv", classical_keypair.privkey,
classical_keypair.privkey_len);
write_key("/tmp/pq_pub", pq_keypair.pubkey, pq_keypair.pubkey_len);
write_key("/tmp/pq_priv", pq_keypair.privkey, pq_keypair.privkey_len);
write_key("/tmp/full_pub", full_keypair.pubkey, full_keypair.pubkey_len);
write_key("/tmp/full_priv", full_keypair.privkey, full_keypair.privkey_len);
}
if (!(keypairs_verify_consistency(&classical_keypair, &pq_keypair,
&full_keypair))) {
puts("success");
ret = EXIT_SUCCESS;
}
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);
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;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment