diff --git a/configure.ac b/configure.ac index 0b494c301e..51ce4c1113 100644 --- a/configure.ac +++ b/configure.ac @@ -156,6 +156,7 @@ ARG_DISBL_SET([nonce], [disable nonce generation plugin.]) ARG_ENABL_SET([frodo], [enable FrodoKEM Post Quantum Safe plugin.]) ARG_ENABL_SET([oqs], [enable Open Quantum Safe (liboqs) plugin.]) ARG_ENABL_SET([qkd], [enable QKD plugin.]) +ARG_ENABL_SET([qkd-kem], [enable QKD-KEM plugin.]) ARG_DISBL_SET([openssl], [disable the OpenSSL crypto plugin.]) ARG_ENABL_SET([wolfssl], [enables the wolfSSL crypto plugin.]) ARG_ENABL_SET([padlock], [enables VIA Padlock crypto plugin.]) @@ -1600,6 +1601,7 @@ ADD_PLUGIN([gcm], [s charon scripts nm cmd]) ADD_PLUGIN([frodo], [s charon scripts nm cmd]) ADD_PLUGIN([oqs], [s charon scripts nm cmd]) ADD_PLUGIN([qkd], [s charon scripts nm cmd]) +ADD_PLUGIN([qkd-kem], [s charon scripts nm cmd]) ADD_PLUGIN([drbg], [s charon pki scripts nm cmd]) ADD_PLUGIN([curl], [s charon pki scripts nm cmd]) ADD_PLUGIN([files], [s charon pki scripts nm cmd]) @@ -1770,6 +1772,7 @@ AM_CONDITIONAL(USE_AF_ALG, test x$af_alg = xtrue) AM_CONDITIONAL(USE_DRBG, test x$drbg = xtrue) AM_CONDITIONAL(USE_OQS, test x$oqs = xtrue) AM_CONDITIONAL(USE_QKD, test x$qkd = xtrue) +AM_CONDITIONAL(USE_QKD_KEM, test x$qkd_kem = xtrue) AM_CONDITIONAL(USE_FRODO, test x$frodo = xtrue) # charon plugins @@ -2054,6 +2057,7 @@ AC_CONFIG_FILES([ src/libstrongswan/plugins/oqs/Makefile src/libstrongswan/plugins/oqs/tests/Makefile src/libstrongswan/plugins/qkd/Makefile + src/libstrongswan/plugins/qkd-kem/Makefile src/libstrongswan/plugins/test_vectors/Makefile src/libstrongswan/tests/Makefile src/libipsec/Makefile diff --git a/src/libstrongswan/Makefile.am b/src/libstrongswan/Makefile.am index 047ad30fc2..c4332caadc 100644 --- a/src/libstrongswan/Makefile.am +++ b/src/libstrongswan/Makefile.am @@ -686,6 +686,13 @@ if MONOLITHIC endif endif +if USE_QKD_KEM + SUBDIRS += plugins/qkd-kem +if MONOLITHIC + libstrongswan_la_LIBADD += plugins/qkd-kem/libstrongswan-qkd_kem.la +endif +endif + if USE_FRODO SUBDIRS += plugins/frodo if MONOLITHIC diff --git a/src/libstrongswan/crypto/key_exchange.c b/src/libstrongswan/crypto/key_exchange.c index f78bceafd7..b850fd3bd7 100644 --- a/src/libstrongswan/crypto/key_exchange.c +++ b/src/libstrongswan/crypto/key_exchange.c @@ -733,6 +733,7 @@ bool key_exchange_verify_pubkey(key_exchange_method_t ke, chunk_t value) case KE_HQC_L3: case KE_HQC_L5: case KE_QKD: // TODO_QKD: implement verification in plugin + case KE_QKD_KYBER_L3: /* verification currently not supported, do in plugin */ valid = FALSE; break; diff --git a/src/libstrongswan/crypto/key_exchange.h b/src/libstrongswan/crypto/key_exchange.h index 7ef4acf7ad..0033979f50 100644 --- a/src/libstrongswan/crypto/key_exchange.h +++ b/src/libstrongswan/crypto/key_exchange.h @@ -88,6 +88,7 @@ enum key_exchange_method_t { KE_HQC_L5 = 1094, /** QKD KEX */ KE_QKD = 1095, + KE_QKD_KYBER_L3 = 1096, /** MODP group with custom generator/prime */ /** internally used DH group with additional parameters g and p, outside * of PRIVATE USE (i.e. IKEv2 DH group range) so it can't be negotiated */ diff --git a/src/libstrongswan/crypto/proposal/proposal_keywords_static.txt b/src/libstrongswan/crypto/proposal/proposal_keywords_static.txt index 58c5377b2c..d3f3bf2e32 100644 --- a/src/libstrongswan/crypto/proposal/proposal_keywords_static.txt +++ b/src/libstrongswan/crypto/proposal/proposal_keywords_static.txt @@ -192,5 +192,6 @@ hqc1, KEY_EXCHANGE_METHOD, KE_HQC_L1, 0 hqc3, KEY_EXCHANGE_METHOD, KE_HQC_L3, 0 hqc5, KEY_EXCHANGE_METHOD, KE_HQC_L5, 0 qkd, KEY_EXCHANGE_METHOD, KE_QKD, 0 +qkd_kyber3, KEY_EXCHANGE_METHOD, KE_QKD_KYBER_L3, 0 noesn, EXTENDED_SEQUENCE_NUMBERS, NO_EXT_SEQ_NUMBERS, 0 esn, EXTENDED_SEQUENCE_NUMBERS, EXT_SEQ_NUMBERS, 0 \ No newline at end of file diff --git a/src/libstrongswan/plugins/qkd-kem/Makefile.am b/src/libstrongswan/plugins/qkd-kem/Makefile.am new file mode 100644 index 0000000000..8e7b5b96db --- /dev/null +++ b/src/libstrongswan/plugins/qkd-kem/Makefile.am @@ -0,0 +1,19 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/libstrongswan + +AM_CFLAGS = \ + $(PLUGIN_CFLAGS) + +if MONOLITHIC +noinst_LTLIBRARIES = libstrongswan-qkd-kem.la +else +plugin_LTLIBRARIES = libstrongswan-qkd-kem.la +endif + +libstrongswan_qkd_kem_la_SOURCES = \ + qkd_kem_plugin.h qkd_kem_plugin.c \ + qkd_kem.h qkd_kem.c + +libstrongswan_qkd_kem_la_LDFLAGS = -module -avoid-version + +libstrongswan_qkd_kem_la_LIBADD = $(OPENSSL_LIB) \ No newline at end of file diff --git a/src/libstrongswan/plugins/qkd-kem/qkd_kem.c b/src/libstrongswan/plugins/qkd-kem/qkd_kem.c new file mode 100644 index 0000000000..a3b5d73148 --- /dev/null +++ b/src/libstrongswan/plugins/qkd-kem/qkd_kem.c @@ -0,0 +1,331 @@ +/* + * Copyright (C) 2018-2020 Andreas Steffen + * + * Copyright (C) secunet Security Networks AG + * + * Copyright (C) 2024 Javier Blanco-Romero @fj-blanco (UC3M, QURSA project) + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/* + * qkd_kem.c + */ + +#include "qkd_kem.h" +#include +#include +#include +#include + +typedef struct private_qkd_kem_t private_qkd_kem_t; + +/** + * Private data of an qkd_kem_t object. + */ +struct private_qkd_kem_t { + /** + * Public qkd_kem_t interface. + */ + qkd_kem_t public; + + /** + * Key exchange method + */ + key_exchange_method_t method; + + /** + * OpenSSL library context + */ + OSSL_LIB_CTX *libctx; + + /** + * EVP_PKEY context for KEM operations + */ + EVP_PKEY_CTX *ctx; + + /** + * Key pair + */ + EVP_PKEY *key; + + /** + * Ciphertext buffer + */ + unsigned char *ciphertext; + size_t ciphertext_len; + + /** + * Shared secret buffer + */ + unsigned char *shared_secret; + size_t shared_secret_len; + + /** + * OpenSSL OQS provider + */ + OSSL_PROVIDER *oqs_provider; +}; + +/** + * Get algorithm name for KEM method + */ +static const char* get_kem_name(key_exchange_method_t method) +{ + DBG1(DBG_LIB, "QKD-KEM plugin: get_kem_name called with method %d", method); + switch (method) + { + case KE_QKD_KYBER_L3: + return "qkd_kyber768"; + // TODO_QKD: Add other hybrid methods as needed + default: + return NULL; + } +} + +static OSSL_PROVIDER* load_qkd_kem_provider(OSSL_LIB_CTX *libctx, const char *modulename) +{ + OSSL_PROVIDER *provider; + + DBG1(DBG_LIB, "QKD-KEM plugin: loading provider '%s'", modulename); + + if (!libctx) + { + DBG1(DBG_LIB, "QKD-KEM: OpenSSL library context is NULL"); + return NULL; + } + + /* Try to load the provider */ + provider = OSSL_PROVIDER_load(libctx, modulename); + if (!provider) + { + DBG1(DBG_LIB, "QKD-KEM: failed to load provider '%s'", modulename); + //ERR_print_errors_fp(stderr); + return NULL; + } + + /* Verify provider is available */ + if (!OSSL_PROVIDER_available(libctx, modulename)) + { + DBG1(DBG_LIB, "QKD-KEM: provider '%s' is not available after loading", modulename); + OSSL_PROVIDER_unload(provider); + return NULL; + } + + DBG2(DBG_LIB, "QKD-KEM: successfully loaded provider '%s'", modulename); + return provider; +} + + +METHOD(key_exchange_t, get_public_key, bool, + private_qkd_kem_t *this, chunk_t *value) +{ + unsigned char *pubkey = NULL; + size_t pubkey_len; + + DBG1(DBG_LIB, "QKD-KEM plugin: retrieving public key"); + + if (this->ciphertext) { + *value = chunk_clone(chunk_create(this->ciphertext, this->ciphertext_len)); + return TRUE; + } + + if (!this->key) { + EVP_PKEY_CTX *gen_ctx = EVP_PKEY_CTX_new_from_name(this->libctx, + get_kem_name(this->method), NULL); + if (!gen_ctx || !EVP_PKEY_keygen_init(gen_ctx) || + !EVP_PKEY_generate(gen_ctx, &this->key)) { + EVP_PKEY_CTX_free(gen_ctx); + return FALSE; + } + EVP_PKEY_CTX_free(gen_ctx); + } + + if (!EVP_PKEY_get_raw_public_key(this->key, NULL, &pubkey_len)) { + return FALSE; + } + + pubkey = malloc(pubkey_len); + if (!EVP_PKEY_get_raw_public_key(this->key, pubkey, &pubkey_len)) { + free(pubkey); + return FALSE; + } + + *value = chunk_clone(chunk_create(pubkey, pubkey_len)); + free(pubkey); + return TRUE; +} + +METHOD(key_exchange_t, set_public_key, bool, + private_qkd_kem_t *this, chunk_t value) +{ + DBG1(DBG_LIB, "QKD-KEM plugin: setting public key (size: %d bytes)", value.len); + + // Initiator (Alice) - has her own key pair, receives ciphertext from Bob + if (this->key) { + DBG1(DBG_LIB, "QKD-KEM plugin: Initiator (Alice) processing ciphertext"); + + if (!this->shared_secret) { + this->shared_secret = OPENSSL_malloc(this->shared_secret_len); + } + + if (!EVP_PKEY_decapsulate_init(this->ctx, NULL)) { + DBG1(DBG_LIB, "QKD-KEM plugin: Initiator decapsulate init failed"); + return FALSE; + } + + if (!EVP_PKEY_decapsulate(this->ctx, this->shared_secret, + &this->shared_secret_len, value.ptr, value.len)) { + DBG1(DBG_LIB, "QKD-KEM plugin: Initiator decapsulation failed"); + return FALSE; + } + + DBG1(DBG_LIB, "QKD-KEM plugin: Initiator decapsulation successful"); + return TRUE; + } + + // Responder (Bob) - receives Alice's public key and generates ciphertext + DBG1(DBG_LIB, "QKD-KEM plugin: Responder (Bob) processing public key"); + + const char* kem_name = get_kem_name(this->method); + EVP_PKEY_CTX *tmp_ctx = EVP_PKEY_CTX_new_from_name(this->libctx, kem_name, NULL); + EVP_PKEY *peer_key = NULL; + + if (!tmp_ctx || !EVP_PKEY_fromdata_init(tmp_ctx)) { + DBG1(DBG_LIB, "QKD-KEM plugin: Responder context initialization failed"); + EVP_PKEY_CTX_free(tmp_ctx); + return FALSE; + } + + OSSL_PARAM params[] = { + OSSL_PARAM_octet_string(OSSL_PKEY_PARAM_PUB_KEY, value.ptr, value.len), + OSSL_PARAM_END + }; + + if (!EVP_PKEY_fromdata(tmp_ctx, &peer_key, EVP_PKEY_PUBLIC_KEY, params)) { + DBG1(DBG_LIB, "QKD-KEM plugin: Responder public key import failed"); + EVP_PKEY_CTX_free(tmp_ctx); + return FALSE; + } + + EVP_PKEY_CTX_free(tmp_ctx); + + EVP_PKEY_CTX_free(this->ctx); + this->ctx = EVP_PKEY_CTX_new_from_pkey(this->libctx, peer_key, NULL); + if (!this->ctx) { + EVP_PKEY_free(peer_key); + return FALSE; + } + + // Encapsulate the shared secret + if (!EVP_PKEY_encapsulate_init(this->ctx, NULL)) { + DBG1(DBG_LIB, "QKD-KEM plugin: Responder encapsulate init failed"); + EVP_PKEY_free(peer_key); + return FALSE; + } + + // Get buffer sizes first + if (!EVP_PKEY_encapsulate(this->ctx, NULL, &this->ciphertext_len, + NULL, &this->shared_secret_len)) { + EVP_PKEY_free(peer_key); + return FALSE; + } + + this->ciphertext = OPENSSL_malloc(this->ciphertext_len); + this->shared_secret = OPENSSL_malloc(this->shared_secret_len); + if (!this->ciphertext || !this->shared_secret) { + EVP_PKEY_free(peer_key); + return FALSE; + } + + if (!EVP_PKEY_encapsulate(this->ctx, this->ciphertext, &this->ciphertext_len, + this->shared_secret, &this->shared_secret_len)) { + DBG1(DBG_LIB, "QKD-KEM plugin: Responder encapsulation failed"); + EVP_PKEY_free(peer_key); + return FALSE; + } + + DBG1(DBG_LIB, "QKD-KEM plugin: Responder encapsulation successful"); + EVP_PKEY_free(peer_key); + return TRUE; +} + +METHOD(key_exchange_t, get_shared_secret, bool, + private_qkd_kem_t *this, chunk_t *secret) +{ + DBG1(DBG_LIB, "QKD-KEM plugin: retrieving shared secret"); + *secret = chunk_clone(chunk_create(this->shared_secret, this->shared_secret_len)); + return TRUE; +} + +METHOD(key_exchange_t, get_method, key_exchange_method_t, + private_qkd_kem_t *this) +{ + DBG1(DBG_LIB, "QKD-KEM plugin: get_method()"); + return this->method; +} + +METHOD(key_exchange_t, destroy, void, + private_qkd_kem_t *this) +{ + DBG1(DBG_LIB, "QKD-KEM plugin: destroy()"); + if (this->shared_secret) { + OPENSSL_clear_free(this->shared_secret, this->shared_secret_len); + } + free(this->ciphertext); + EVP_PKEY_free(this->key); + EVP_PKEY_CTX_free(this->ctx); + OSSL_PROVIDER_unload(this->oqs_provider); + OSSL_LIB_CTX_free(this->libctx); + free(this); +} + +/* + * Described in header + */ +qkd_kem_t *qkd_kem_create(key_exchange_method_t method) +{ + DBG1(DBG_LIB, "QKD-KEM plugin: qkd_kem_create called with method %d", method); + private_qkd_kem_t *this; + const char *kem_name = get_kem_name(method); + + if (!kem_name) { + return NULL; + } + + INIT(this, + .public = { + .ke = { + .get_method = _get_method, + .get_public_key = _get_public_key, + .set_public_key = _set_public_key, + .get_shared_secret = _get_shared_secret, + .destroy = _destroy, + }, + }, + .method = method, + .libctx = OSSL_LIB_CTX_new() + ); + + this->oqs_provider = load_qkd_kem_provider(this->libctx, "qkdkemprovider"); + if (!this->oqs_provider) { + destroy(this); + return NULL; + } + + this->ctx = EVP_PKEY_CTX_new_from_name(this->libctx, kem_name, NULL); + if (!this->ctx) { + destroy(this); + return NULL; + } + + return &this->public; +} \ No newline at end of file diff --git a/src/libstrongswan/plugins/qkd-kem/qkd_kem.h b/src/libstrongswan/plugins/qkd-kem/qkd_kem.h new file mode 100644 index 0000000000..18f4cc65b5 --- /dev/null +++ b/src/libstrongswan/plugins/qkd-kem/qkd_kem.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2018-2020 Andreas Steffen + * + * Copyright (C) secunet Security Networks AG + * + * Copyright (C) 2024 Javier Blanco-Romero @fj-blanco (UC3M, QURSA project) + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/* + * qkd_kem.h + */ +#ifndef QKD_KEX_H_ +#define QKD_KEX_H_ + +typedef struct qkd_kem_t qkd_kem_t; + +#include + +struct qkd_kem_t { + key_exchange_t ke; +}; + +qkd_kem_t *qkd_kem_create(key_exchange_method_t method); + +#endif /** QKD_KEX_H_ @}*/ \ No newline at end of file diff --git a/src/libstrongswan/plugins/qkd-kem/qkd_kem_plugin.c b/src/libstrongswan/plugins/qkd-kem/qkd_kem_plugin.c new file mode 100644 index 0000000000..396c5694a6 --- /dev/null +++ b/src/libstrongswan/plugins/qkd-kem/qkd_kem_plugin.c @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2018-2020 Andreas Steffen + * + * Copyright (C) secunet Security Networks AG + * + * Copyright (C) 2024 Javier Blanco-Romero @fj-blanco (UC3M, QURSA project) + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/* + * qkd_kem_plugin.c + */ + +#include +#include "qkd_kem_plugin.h" +#include "qkd_kem.h" + +typedef struct private_qkd_kem_plugin_t private_qkd_kem_plugin_t; + +/** + * Private data of QKD-KEM_plugin + */ +struct private_qkd_kem_plugin_t { + /** + * public functions + */ + qkd_kem_plugin_t public; +}; + +METHOD(plugin_t, get_name, char*, + private_qkd_kem_plugin_t *this) +{ + return "qkd-kem"; +} + +METHOD(plugin_t, get_features, int, + private_qkd_kem_plugin_t *this, plugin_feature_t *features[]) +{ + static plugin_feature_t f[] = { + /* QKD-KEM key exchange method */ + PLUGIN_REGISTER(KE, qkd_kem_create), + PLUGIN_PROVIDE(KE, KE_QKD_KYBER_L3), + /* private/public keys */ + /* TODO_QKD: add additional features here */ + }; + + *features = f; + DBG1(DBG_LIB, "QKD-KEM plugin: QKD plugin providing %d features", countof(f)); + return countof(f); +} + +METHOD(plugin_t, destroy, void, + private_qkd_kem_plugin_t *this) +{ + DBG2(DBG_LIB, "QKD-KEM plugin: destroying QKD plugin"); + /* TODO_QKD: add necessary cleanup */ + free(this); +} + +plugin_t *qkd_kem_plugin_create() +{ + private_qkd_kem_plugin_t *this; + + DBG1(DBG_LIB, "QKD-KEM plugin: plugin_create called"); + + INIT(this, + .public = { + .plugin = { + .get_name = _get_name, + .get_features = _get_features, + .destroy = _destroy, + }, + }, + ); + + if (!this) { + DBG1(DBG_LIB, "QKD-KEM plugin: INIT failed"); + return NULL; + } + + DBG1(DBG_LIB, "QKD-KEM plugin: plugin initialized successfully"); + return &this->public.plugin; +} \ No newline at end of file diff --git a/src/libstrongswan/plugins/qkd-kem/qkd_kem_plugin.h b/src/libstrongswan/plugins/qkd-kem/qkd_kem_plugin.h new file mode 100644 index 0000000000..3ccf75986b --- /dev/null +++ b/src/libstrongswan/plugins/qkd-kem/qkd_kem_plugin.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2018-2020 Andreas Steffen + * + * Copyright (C) secunet Security Networks AG + * + * Copyright (C) 2024 Javier Blanco-Romero @fj-blanco (UC3M, QURSA project) + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/* + * qkd_kem_plugin.h + */ + +/** + * @defgroup qkd_kem_p qkd_kem + * @ingroup plugins + * + * @defgroup qkd_kem_plugin qkd_kem_plugin + * @{ @ingroup qkd_kem_p + */ + +#ifndef QKD_KEM_PLUGIN_H_ +#define QKD_KEM_PLUGIN_H_ + +#include + +typedef struct qkd_kem_plugin_t qkd_kem_plugin_t; + +/** + * Plugin implementing hybrid quantum key distribution (QKD) + Post-Quantum Key Exchange (KEM) methods. + */ +struct qkd_kem_plugin_t { + + /** + * implements plugin interface + */ + plugin_t plugin; +}; + +#endif /** QKD_KEM_PLUGIN_H_ @}*/ \ No newline at end of file diff --git a/src/libstrongswan/plugins/qkd/qkd_plugin.c b/src/libstrongswan/plugins/qkd/qkd_plugin.c index b9597622b0..ada908ff2f 100644 --- a/src/libstrongswan/plugins/qkd/qkd_plugin.c +++ b/src/libstrongswan/plugins/qkd/qkd_plugin.c @@ -1,10 +1,28 @@ +/* + * Copyright (C) 2018-2020 Andreas Steffen + * + * Copyright (C) secunet Security Networks AG + * + * Copyright (C) 2024 Javier Blanco-Romero @fj-blanco (UC3M, QURSA project) + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. +*/ + /* * qkd_plugin.c */ +#include #include "qkd_plugin.h" #include "qkd_kex.h" -#include typedef struct private_qkd_plugin_t private_qkd_plugin_t;