summaryrefslogtreecommitdiff
path: root/src/lib
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/config.h.in4
-rw-r--r--src/lib/crypt.c647
-rw-r--r--src/lib/crypt/openssl.c1190
-rw-r--r--src/lib/crypt/openssl.h99
-rw-r--r--src/lib/dev.c101
-rw-r--r--src/lib/pb/irm.proto9
-rw-r--r--src/lib/protobuf.c3
-rw-r--r--src/lib/serdes-irm.c24
-rw-r--r--src/lib/tests/CMakeLists.txt14
-rw-r--r--src/lib/tests/auth_test.c157
-rw-r--r--src/lib/tests/auth_test_pqc.c356
-rw-r--r--src/lib/tests/crypt_test.c262
-rw-r--r--src/lib/tests/hash_test.c2
-rw-r--r--src/lib/tests/kex_test.c844
-rw-r--r--src/lib/tests/kex_test_pqc.c549
-rw-r--r--src/lib/tests/sockets_test.c2
-rw-r--r--src/lib/tests/time_test.c2
-rw-r--r--src/lib/tests/tpm_test.c2
-rw-r--r--src/lib/utils.c19
19 files changed, 3786 insertions, 500 deletions
diff --git a/src/lib/config.h.in b/src/lib/config.h.in
index 4533b00e..b34e6a7b 100644
--- a/src/lib/config.h.in
+++ b/src/lib/config.h.in
@@ -23,9 +23,11 @@
#cmakedefine HAVE_SYS_RANDOM
#cmakedefine HAVE_LIBGCRYPT
#cmakedefine HAVE_OPENSSL
-
#ifdef HAVE_OPENSSL
+#cmakedefine HAVE_OPENSSL_PQC
#define HAVE_ENCRYPTION
+#define PROC_SECMEM_MAX @PROC_SECMEM_MAX@
+#define SECMEM_GUARD @SECMEM_GUARD@
#endif
#define SYS_MAX_FLOWS @SYS_MAX_FLOWS@
diff --git a/src/lib/crypt.c b/src/lib/crypt.c
index 8b18140e..a050fe38 100644
--- a/src/lib/crypt.c
+++ b/src/lib/crypt.c
@@ -22,32 +22,244 @@
#include <config.h>
-#include <ouroboros/crypt.h>
#include <ouroboros/errno.h>
+#include <ouroboros/random.h>
+#include <ouroboros/crypt.h>
+
#ifdef HAVE_OPENSSL
- #include "crypt/openssl.h"
-#endif /* HAVE_OPENSSL */
+#include <openssl/evp.h>
+#include "crypt/openssl.h"
+#endif
#include <assert.h>
+#include <stdio.h>
#include <string.h>
+#include <sys/stat.h>
+
+struct nid_map {
+ uint16_t nid;
+ const char * name;
+};
+
+static const struct nid_map cipher_nid_map[] = {
+ {NID_aes_128_gcm, "aes-128-gcm"},
+ {NID_aes_192_gcm, "aes-192-gcm"},
+ {NID_aes_256_gcm, "aes-256-gcm"},
+ {NID_chacha20_poly1305, "chacha20-poly1305"},
+ {NID_aes_128_ctr, "aes-128-ctr"},
+ {NID_aes_192_ctr, "aes-192-ctr"},
+ {NID_aes_256_ctr, "aes-256-ctr"},
+ {NID_undef, NULL}
+};
+
+const uint16_t crypt_supported_nids[] = {
+#ifdef HAVE_OPENSSL
+ NID_aes_128_gcm,
+ NID_aes_192_gcm,
+ NID_aes_256_gcm,
+ NID_chacha20_poly1305,
+ NID_aes_128_ctr,
+ NID_aes_192_ctr,
+ NID_aes_256_ctr,
+#endif
+ NID_undef
+};
+
+static const struct nid_map kex_nid_map[] = {
+ {NID_X9_62_prime256v1, "prime256v1"},
+ {NID_secp384r1, "secp384r1"},
+ {NID_secp521r1, "secp521r1"},
+ {NID_X25519, "X25519"},
+ {NID_X448, "X448"},
+ {NID_ffdhe2048, "ffdhe2048"},
+ {NID_ffdhe3072, "ffdhe3072"},
+ {NID_ffdhe4096, "ffdhe4096"},
+ {NID_MLKEM512, "ML-KEM-512"},
+ {NID_MLKEM768, "ML-KEM-768"},
+ {NID_MLKEM1024, "ML-KEM-1024"},
+ {NID_X25519MLKEM768, "X25519MLKEM768"},
+ {NID_X448MLKEM1024, "X448MLKEM1024"},
+ {NID_undef, NULL}
+};
+
+const uint16_t kex_supported_nids[] = {
+#ifdef HAVE_OPENSSL
+ NID_X9_62_prime256v1,
+ NID_secp384r1,
+ NID_secp521r1,
+ NID_X25519,
+ NID_X448,
+ NID_ffdhe2048,
+ NID_ffdhe3072,
+ NID_ffdhe4096,
+#ifdef HAVE_OPENSSL_PQC
+ NID_MLKEM512,
+ NID_MLKEM768,
+ NID_MLKEM1024,
+ NID_X25519MLKEM768,
+ NID_X448MLKEM1024,
+#endif
+#endif
+ NID_undef
+};
+
+static const struct nid_map md_nid_map[] = {
+ {NID_sha256, "sha256"},
+ {NID_sha384, "sha384"},
+ {NID_sha512, "sha512"},
+ {NID_sha3_256, "sha3-256"},
+ {NID_sha3_384, "sha3-384"},
+ {NID_sha3_512, "sha3-512"},
+ {NID_blake2b512, "blake2b512"},
+ {NID_blake2s256, "blake2s256"},
+ {NID_undef, NULL}
+};
+
+const uint16_t md_supported_nids[] = {
+#ifdef HAVE_OPENSSL
+ NID_sha256,
+ NID_sha384,
+ NID_sha512,
+ NID_sha3_256,
+ NID_sha3_384,
+ NID_sha3_512,
+ NID_blake2b512,
+ NID_blake2s256,
+#endif
+ NID_undef
+};
struct crypt_ctx {
- void * ctx;
- uint8_t key[SYMMKEYSZ];
+ void * ctx; /* Encryption context */
};
struct auth_ctx {
void * store;
};
-int crypt_dh_pkp_create(void ** pkp,
- uint8_t * pk)
+static int parse_kex_value(const char * value,
+ struct sec_config * cfg)
+{
+ SET_KEX_ALGO(cfg, value);
+ if (cfg->x.nid == NID_undef)
+ return -ENOTSUP;
+
+ return 0;
+}
+
+/* not in header, but non-static for unit testing */
+int parse_sec_config(struct sec_config * cfg,
+ FILE * fp)
+{
+ char line[256];
+ char * equals;
+ char * key;
+ char * value;
+
+ assert(cfg != NULL);
+ assert(fp != NULL);
+
+ /* Set defaults */
+ SET_KEX_ALGO_NID(cfg, NID_X9_62_prime256v1);
+ cfg->x.mode = KEM_MODE_SERVER_ENCAP;
+ SET_KEX_KDF_NID(cfg, NID_sha256);
+ SET_KEX_CIPHER_NID(cfg, NID_aes_256_gcm);
+ SET_KEX_DIGEST_NID(cfg, NID_sha256);
+
+ while (fgets(line, sizeof(line), fp) != NULL) {
+ char * trimmed;
+
+ /* Skip comments and empty lines */
+ if (line[0] == '#' || line[0] == '\n')
+ continue;
+
+ /* Check for 'none' keyword */
+ trimmed = trim_whitespace(line);
+ if (strcmp(trimmed, "none") == 0) {
+ memset(cfg, 0, sizeof(*cfg));
+ return 0;
+ }
+
+ /* Find the = separator */
+ equals = strchr(line, '=');
+ if (equals == NULL)
+ continue;
+
+ /* Split into key and value */
+ *equals = '\0';
+ key = trim_whitespace(line);
+ value = trim_whitespace(equals + 1);
+
+ /* Parse key exchange field */
+ if (strcmp(key, "kex") == 0) {
+ if (parse_kex_value(value, cfg) < 0)
+ return -EINVAL;
+ } else if (strcmp(key, "cipher") == 0) {
+ SET_KEX_CIPHER(cfg, value);
+ if (cfg->c.nid == NID_undef)
+ return -EINVAL;
+ } else if (strcmp(key, "kdf") == 0) {
+ SET_KEX_KDF(cfg, value);
+ if (cfg->k.nid == NID_undef)
+ return -EINVAL;
+ } else if (strcmp(key, "digest") == 0) {
+ SET_KEX_DIGEST(cfg, value);
+ if (cfg->d.nid == NID_undef)
+ return -EINVAL;
+ } else if (strcmp(key, "kem_mode") == 0) {
+ if (strcmp(value, "server") == 0) {
+ cfg->x.mode = KEM_MODE_SERVER_ENCAP;
+ } else if (strcmp(value, "client") == 0) {
+ cfg->x.mode = KEM_MODE_CLIENT_ENCAP;
+ } else {
+ return -EINVAL;
+ }
+ }
+ }
+
+ return 0;
+}
+
+/* Parse key exchange config from file */
+int load_sec_config_file(struct sec_config * cfg,
+ const char * path)
+{
+ FILE * fp;
+ int ret;
+
+ assert(cfg != NULL);
+ assert(path != NULL);
+
+ fp = fopen(path, "r");
+ if (fp == NULL) {
+ /* File doesn't exist - disable encryption */
+ CLEAR_KEX_ALGO(cfg);
+ return 0;
+ }
+
+ ret = parse_sec_config(cfg, fp);
+
+ fclose(fp);
+
+ return ret;
+}
+
+int kex_pkp_create(struct sec_config * cfg,
+ void ** pkp,
+ uint8_t * pk)
{
#ifdef HAVE_OPENSSL
+ assert(cfg != NULL);
assert(pkp != NULL);
+
*pkp = NULL;
- return openssl_ecdh_pkp_create(pkp, pk);
+
+ if (cfg->x.str == NULL || kex_validate_nid(cfg->x.nid) < 0)
+ return -ENOTSUP;
+
+ return openssl_pkp_create(cfg->x.str, (EVP_PKEY **) pkp, pk);
#else
+ (void) cfg;
(void) pkp;
(void) pk;
@@ -57,12 +269,12 @@ int crypt_dh_pkp_create(void ** pkp,
#endif
}
-void crypt_dh_pkp_destroy(void * pkp)
+void kex_pkp_destroy(void * pkp)
{
if (pkp == NULL)
return;
#ifdef HAVE_OPENSSL
- openssl_ecdh_pkp_destroy(pkp);
+ openssl_pkp_destroy((EVP_PKEY *) pkp);
#else
(void) pkp;
@@ -70,12 +282,18 @@ void crypt_dh_pkp_destroy(void * pkp)
#endif
}
-int crypt_dh_derive(void * pkp,
- buffer_t pk,
- uint8_t * s)
+int kex_dhe_derive(struct sec_config * cfg,
+ void * pkp,
+ buffer_t pk,
+ uint8_t * s)
{
+ assert(cfg != NULL);
+
+ if (kex_validate_nid(cfg->x.nid) < 0)
+ return -ENOTSUP;
+
#ifdef HAVE_OPENSSL
- return openssl_ecdh_derive(pkp, pk, s);
+ return openssl_dhe_derive((EVP_PKEY *) pkp, pk, cfg->k.nid, s);
#else
(void) pkp;
(void) pk;
@@ -86,6 +304,244 @@ int crypt_dh_derive(void * pkp,
#endif
}
+ssize_t kex_kem_encap(buffer_t pk,
+ uint8_t * ct,
+ int kdf,
+ uint8_t * s)
+{
+#ifdef HAVE_OPENSSL
+ return openssl_kem_encap(pk, ct, kdf, s);
+#else
+ (void) pk;
+ (void) ct;
+ (void) kdf;
+
+ memset(s, 0, SYMMKEYSZ);
+
+ return -ECRYPT;
+#endif
+}
+
+ssize_t kex_kem_encap_raw(buffer_t pk,
+ uint8_t * ct,
+ int kdf,
+ uint8_t * s)
+{
+#ifdef HAVE_OPENSSL
+ return openssl_kem_encap_raw(pk, ct, kdf, s);
+#else
+ (void) pk;
+ (void) ct;
+ (void) kdf;
+
+ memset(s, 0, SYMMKEYSZ);
+
+ return -ECRYPT;
+#endif
+}
+
+int kex_kem_decap(void * pkp,
+ buffer_t ct,
+ int kdf,
+ uint8_t * s)
+{
+#ifdef HAVE_OPENSSL
+ return openssl_kem_decap((EVP_PKEY *) pkp, ct, kdf, s);
+#else
+ (void) pkp;
+ (void) ct;
+ (void) kdf;
+
+ memset(s, 0, SYMMKEYSZ);
+
+ return -ECRYPT;
+#endif
+}
+
+int kex_get_algo_from_pk_der(buffer_t pk,
+ char * algo)
+{
+#ifdef HAVE_OPENSSL
+ return openssl_get_algo_from_pk_der(pk, algo);
+#else
+ (void) pk;
+ algo[0] = '\0';
+
+ return -ECRYPT;
+#endif
+}
+
+int kex_get_algo_from_pk_raw(buffer_t pk,
+ char * algo)
+{
+#ifdef HAVE_OPENSSL
+ return openssl_get_algo_from_pk_raw(pk, algo);
+#else
+ (void) pk;
+ algo[0] = '\0';
+
+ return -ECRYPT;
+#endif
+}
+
+int kex_validate_algo(const char * algo)
+{
+ if (algo == NULL)
+ return -EINVAL;
+
+ /* Use NID validation instead of string array */
+ return kex_validate_nid(kex_str_to_nid(algo));
+}
+
+int crypt_validate_nid(int nid)
+{
+ const struct nid_map * p;
+
+ if (nid == NID_undef)
+ return -EINVAL;
+
+ for (p = cipher_nid_map; p->name != NULL; p++) {
+ if (p->nid == nid)
+ return 0;
+ }
+
+ return -ENOTSUP;
+}
+
+
+const char * crypt_nid_to_str(uint16_t nid)
+{
+ const struct nid_map * p;
+
+ for (p = cipher_nid_map; p->name != NULL; p++) {
+ if (p->nid == nid)
+ return p->name;
+ }
+
+ return NULL;
+}
+
+uint16_t crypt_str_to_nid(const char * cipher)
+{
+ const struct nid_map * p;
+
+ if (cipher == NULL)
+ return NID_undef;
+
+ /* fast, check if cipher pointer is in the map */
+ for (p = cipher_nid_map; p->name != NULL; p++) {
+ if (cipher == p->name)
+ return p->nid;
+ }
+
+ for (p = cipher_nid_map; p->name != NULL; p++) {
+ if (strcmp(p->name, cipher) == 0)
+ return p->nid;
+ }
+
+ return NID_undef;
+}
+
+const char * kex_nid_to_str(uint16_t nid)
+{
+ const struct nid_map * p;
+
+ for (p = kex_nid_map; p->name != NULL; p++) {
+ if (p->nid == nid)
+ return p->name;
+ }
+
+ return NULL;
+}
+
+uint16_t kex_str_to_nid(const char * algo)
+{
+ const struct nid_map * p;
+
+ if (algo == NULL)
+ return NID_undef;
+
+ /* Fast path: check if algo pointer is in the map */
+ for (p = kex_nid_map; p->name != NULL; p++) {
+ if (algo == p->name)
+ return p->nid;
+ }
+
+ /* Slow path: string comparison */
+ for (p = kex_nid_map; p->name != NULL; p++) {
+ if (strcmp(p->name, algo) == 0)
+ return p->nid;
+ }
+
+ return NID_undef;
+}
+
+int kex_validate_nid(int nid)
+{
+ const struct nid_map * p;
+
+ if (nid == NID_undef)
+ return -EINVAL;
+
+ for (p = kex_nid_map; p->name != NULL; p++) {
+ if (p->nid == nid)
+ return 0;
+ }
+
+ return -ENOTSUP;
+}
+
+const char * md_nid_to_str(uint16_t nid)
+{
+ const struct nid_map * p;
+
+ for (p = md_nid_map; p->name != NULL; p++) {
+ if (p->nid == nid)
+ return p->name;
+ }
+
+ return NULL;
+}
+
+uint16_t md_str_to_nid(const char * kdf)
+{
+ const struct nid_map * p;
+
+ if (kdf == NULL)
+ return NID_undef;
+
+ /* Fast path: check if kdf pointer is in the map */
+ for (p = md_nid_map; p->name != NULL; p++) {
+ if (kdf == p->name)
+ return p->nid;
+ }
+
+ /* Slow path: string comparison */
+ for (p = md_nid_map; p->name != NULL; p++) {
+ if (strcmp(p->name, kdf) == 0)
+ return p->nid;
+ }
+
+ return NID_undef;
+}
+
+int md_validate_nid(int nid)
+{
+ const struct nid_map * p;
+
+ if (nid == NID_undef)
+ return -EINVAL;
+
+ for (p = md_nid_map; p->name != NULL; p++) {
+ if (p->nid == nid)
+ return 0;
+ }
+
+ return -ENOTSUP;
+}
+
+/* Hash length now returned by md_digest() */
+
int crypt_encrypt(struct crypt_ctx * ctx,
buffer_t in,
buffer_t * out)
@@ -94,7 +550,7 @@ int crypt_encrypt(struct crypt_ctx * ctx,
assert(ctx->ctx != NULL);
#ifdef HAVE_OPENSSL
- return openssl_encrypt(ctx->ctx, ctx->key, in, out);
+ return openssl_encrypt(ctx->ctx, in, out);
#else
(void) ctx;
(void) in;
@@ -112,7 +568,7 @@ int crypt_decrypt(struct crypt_ctx * ctx,
assert(ctx->ctx != NULL);
#ifdef HAVE_OPENSSL
- return openssl_decrypt(ctx->ctx, ctx->key, in, out);
+ return openssl_decrypt(ctx->ctx, in, out);
#else
(void) ctx;
(void) in;
@@ -122,20 +578,21 @@ int crypt_decrypt(struct crypt_ctx * ctx,
#endif
}
-struct crypt_ctx * crypt_create_ctx(const uint8_t * key)
+struct crypt_ctx * crypt_create_ctx(struct crypt_sk * sk)
{
struct crypt_ctx * crypt;
+ if (crypt_validate_nid(sk->nid) != 0)
+ return NULL;
+
crypt = malloc(sizeof(*crypt));
if (crypt == NULL)
goto fail_crypt;
memset(crypt, 0, sizeof(*crypt));
- if (key != NULL)
- memcpy(crypt->key, key, SYMMKEYSZ);
#ifdef HAVE_OPENSSL
- crypt->ctx=openssl_crypt_create_ctx();
+ crypt->ctx = openssl_crypt_create_ctx(sk);
if (crypt->ctx == NULL)
goto fail_ctx;
#endif
@@ -204,11 +661,72 @@ int crypt_load_pubkey_str(const char * str,
#endif
}
+int crypt_load_pubkey_file(const char * path,
+ void ** key)
+{
+ *key = NULL;
+
+#ifdef HAVE_OPENSSL
+ return openssl_load_pubkey_file(path, key);
+#else
+ (void) path;
+
+ return 0;
+#endif
+}
+
+int crypt_load_pubkey_file_to_der(const char * path,
+ buffer_t * buf)
+{
+ assert(buf != NULL);
+
+#ifdef HAVE_OPENSSL
+ return openssl_load_pubkey_file_to_der(path, buf);
+#else
+ (void) path;
+
+ buf->data = NULL;
+ buf->len = 0;
+ return 0;
+#endif
+}
+
+int crypt_load_pubkey_raw_file(const char * path,
+ buffer_t * buf)
+{
+ assert(buf != NULL);
+
+#ifdef HAVE_OPENSSL
+ return openssl_load_pubkey_raw_file(path, buf);
+#else
+ (void) path;
+
+ buf->data = NULL;
+ buf->len = 0;
+ return 0;
+#endif
+}
+
+int crypt_load_privkey_raw_file(const char * path,
+ void ** key)
+{
+ *key = NULL;
+
+#ifdef HAVE_OPENSSL
+ return openssl_load_privkey_raw_file(path, key);
+#else
+ (void) path;
+
+ return 0;
+#endif
+}
+
int crypt_cmp_key(const void * key1,
const void * key2)
{
#ifdef HAVE_OPENSSL
- return openssl_cmp_key(key1, key2);
+ return openssl_cmp_key((const EVP_PKEY *) key1,
+ (const EVP_PKEY *) key2);
#else
(void) key1;
(void) key2;
@@ -223,7 +741,7 @@ void crypt_free_key(void * key)
return;
#ifdef HAVE_OPENSSL
- openssl_free_key(key);
+ openssl_free_key((EVP_PKEY *) key);
#endif
}
@@ -343,6 +861,19 @@ int crypt_check_crt_name(void * crt,
#endif
}
+int crypt_get_crt_name(void * crt,
+ char * name)
+{
+#ifdef HAVE_OPENSSL
+ return openssl_get_crt_name(crt, name);
+#else
+ (void) crt;
+ (void) name;
+
+ return 0;
+#endif
+}
+
struct auth_ctx * auth_create_ctx(void)
{
struct auth_ctx * ctx;
@@ -406,13 +937,15 @@ int auth_verify_crt(struct auth_ctx * ctx,
}
int auth_sign(void * pkp,
+ int md_nid,
buffer_t msg,
buffer_t * sig)
{
#ifdef HAVE_OPENSSL
- return openssl_sign(pkp, msg, sig);
+ return openssl_sign((EVP_PKEY *) pkp, md_nid, msg, sig);
#else
(void) pkp;
+ (void) md_nid;
(void) msg;
(void) sig;
@@ -423,16 +956,82 @@ int auth_sign(void * pkp,
}
int auth_verify_sig(void * pk,
+ int md_nid,
buffer_t msg,
buffer_t sig)
{
#ifdef HAVE_OPENSSL
- return openssl_verify_sig(pk, msg, sig);
+ return openssl_verify_sig((EVP_PKEY *) pk, md_nid, msg, sig);
#else
(void) pk;
+ (void) md_nid;
(void) msg;
(void) sig;
return 0;
#endif
}
+
+ssize_t md_digest(int md_nid,
+ buffer_t in,
+ uint8_t * out)
+{
+#ifdef HAVE_OPENSSL
+ return openssl_md_digest(md_nid, in, out);
+#else
+ (void) md_nid;
+ (void) in;
+ (void) out;
+
+ return -1;
+#endif
+}
+
+ssize_t md_len(int md_nid)
+{
+#ifdef HAVE_OPENSSL
+ return openssl_md_len(md_nid);
+#else
+ (void) md_nid;
+ return -1;
+#endif
+}
+
+int crypt_secure_malloc_init(size_t max)
+{
+#ifdef HAVE_OPENSSL
+ return openssl_secure_malloc_init(max, SECMEM_GUARD);
+#else
+ return 0;
+#endif
+}
+
+void crypt_secure_malloc_fini(void)
+{
+#ifdef HAVE_OPENSSL
+ openssl_secure_malloc_fini();
+#endif
+}
+
+void * crypt_secure_malloc(size_t size)
+{
+#ifdef HAVE_OPENSSL
+ return openssl_secure_malloc(size);
+#else
+ return malloc(size);
+#endif
+}
+
+void crypt_secure_free(void * ptr,
+ size_t size)
+{
+ if (ptr == NULL)
+ return;
+
+#ifdef HAVE_OPENSSL
+ openssl_secure_free(ptr, size);
+#else
+ memset(ptr, 0, size);
+ free(ptr);
+#endif
+}
diff --git a/src/lib/crypt/openssl.c b/src/lib/crypt/openssl.c
index 291a3418..71a69c1c 100644
--- a/src/lib/crypt/openssl.c
+++ b/src/lib/crypt/openssl.c
@@ -23,6 +23,10 @@
* Foundation, Inc., http://www.fsf.org/about/contact/.
*/
+#define _POSIX_C_SOURCE 200809L
+
+#include <config.h>
+
#include <ouroboros/errno.h>
#include <ouroboros/crypt.h>
#include <ouroboros/hash.h>
@@ -32,31 +36,239 @@
#include <openssl/evp.h>
#include <openssl/bio.h>
#include <openssl/ec.h>
+#include <openssl/err.h>
+#include <openssl/kdf.h>
#include <openssl/pem.h>
#include <openssl/sha.h>
+#include <openssl/provider.h>
#include <openssl/x509v3.h>
#include <openssl/x509_vfy.h>
#include <assert.h>
+#include <stdio.h>
+
+#define IS_EC_GROUP(str) (strcmp(str, "EC") == 0)
+#define IS_DH_GROUP(str) (strcmp(str, "DH") == 0)
+
+#define HKDF_INFO_DHE "o7s-ossl-dhe"
+#define HKDF_INFO_ENCAP "o7s-ossl-encap"
+#define HKDF_SALT_LEN 32 /* SHA-256 output size */
+
+struct ossl_crypt_ctx {
+ EVP_CIPHER_CTX * evp_ctx;
+ const EVP_CIPHER * cipher;
+ uint8_t * key;
+ int ivsz;
+ int tagsz;
+};
+
+struct kdf_info {
+ buffer_t secret;
+ int nid;
+ buffer_t salt;
+ buffer_t info;
+ buffer_t key;
+};
+
+/* Convert hash NID to OpenSSL digest name string for HKDF */
+static const char * hash_nid_to_digest_name(int nid)
+{
+ const EVP_MD * md;
+ const char * name;
+
+ md = EVP_get_digestbynid(nid);
+ if (md == NULL)
+ return "SHA256"; /* fallback to SHA-256 */
+
+ name = EVP_MD_get0_name(md);
+ if (name == NULL)
+ return "SHA256"; /* fallback to SHA-256 */
+
+ return name;
+}
+
+/* Extract public key bytes from a key pair for salt derivation */
+static int get_pk_bytes_from_key(EVP_PKEY * key,
+ buffer_t * pk)
+{
+ const char * name;
+ int ret;
+
+ assert(key != NULL);
+ assert(pk != NULL);
+
+ name = EVP_PKEY_get0_type_name(key);
+ if (name == NULL)
+ goto fail_name;
+
+ if (IS_HYBRID_KEM(name)) {
+ pk->len = EVP_PKEY_get1_encoded_public_key(key, &pk->data);
+ if (pk->len == 0)
+ goto fail_name;
+ } else {
+ /* Pure ML-KEM: use DER encoding to match encap */
+ pk->data = NULL;
+ ret = i2d_PUBKEY(key, &pk->data);
+ if (ret <= 0)
+ goto fail_name;
+ pk->len = (size_t) ret;
+ }
+
+ return 0;
+ fail_name:
+ return -ECRYPT;
+}
+
+/* Derive salt from public key bytes by hashing them */
+static int derive_salt_from_pk_bytes(buffer_t pk,
+ uint8_t * salt,
+ size_t salt_len)
+{
+ uint8_t hash[EVP_MAX_MD_SIZE];
+ unsigned hash_len;
+
+ assert(pk.data != NULL);
+ assert(salt != NULL);
+
+ if (EVP_Digest(pk.data, pk.len, hash, &hash_len,
+ EVP_sha256(), NULL) != 1)
+ goto fail_digest;
+
+ memcpy(salt, hash, salt_len < hash_len ? salt_len : hash_len);
+
+ return 0;
+ fail_digest:
+ return -ECRYPT;
+}
+
+/* Derive salt from two public key byte buffers (DHE) in canonical order */
+static int derive_salt_from_pk_bytes_dhe(buffer_t local,
+ buffer_t remote,
+ uint8_t * salt,
+ size_t salt_len)
+{
+ uint8_t * concat;
+ size_t concat_len;
+ uint8_t hash[EVP_MAX_MD_SIZE];
+ unsigned hash_len;
+ size_t min_len;
+ int cmp;
+
+ assert(local.data != NULL);
+ assert(remote.data != NULL);
+ assert(salt != NULL);
+
+ concat_len = local.len + remote.len;
+ concat = OPENSSL_malloc(concat_len);
+ if (concat == NULL)
+ goto fail_malloc;
+
+ /* Canonical order: compare and concatenate smaller first */
+ min_len = local.len < remote.len ? local.len : remote.len;
+ cmp = memcmp(local.data, remote.data, min_len);
+ if (cmp < 0 || (cmp == 0 && local.len < remote.len)) {
+ memcpy(concat, local.data, local.len);
+ memcpy(concat + local.len, remote.data, remote.len);
+ } else {
+ memcpy(concat, remote.data, remote.len);
+ memcpy(concat + remote.len, local.data, local.len);
+ }
+
+ if (EVP_Digest(concat, concat_len, hash, &hash_len,
+ EVP_sha256(), NULL) != 1)
+ goto fail_digest;
+
+ OPENSSL_free(concat);
+
+ memcpy(salt, hash, salt_len < hash_len ? salt_len : hash_len);
+
+ return 0;
+ fail_digest:
+ OPENSSL_free(concat);
+ fail_malloc:
+ return -ECRYPT;
+}
+
+/* Derive key using HKDF */
+#define OPc_u_str OSSL_PARAM_construct_utf8_string
+#define OPc_o_str OSSL_PARAM_construct_octet_string
+static int derive_key_hkdf(struct kdf_info * ki)
+{
+ EVP_KDF * kdf;
+ EVP_KDF_CTX * kctx;
+ OSSL_PARAM params[5];
+ const char * digest;
+ int idx;
+
+ digest = hash_nid_to_digest_name(ki->nid);
+
+ kdf = EVP_KDF_fetch(NULL, "HKDF", NULL);
+ if (kdf == NULL)
+ goto fail_fetch;
+
+ kctx = EVP_KDF_CTX_new(kdf);
+ if (kctx == NULL)
+ goto fail_ctx;
+
+ idx = 0;
+ params[idx++] = OPc_u_str("digest", (char *) digest, 0);
+ params[idx++] = OPc_o_str("key", ki->secret.data, ki->secret.len);
+ params[idx++] = OPc_o_str("salt", ki->salt.data, ki->salt.len);
+ params[idx++] = OPc_o_str("info", ki->info.data, ki->info.len);
+
+ params[idx] = OSSL_PARAM_construct_end();
+
+ if (EVP_KDF_derive(kctx, ki->key.data, ki->key.len, params) != 1)
+ goto fail_derive;
+
+ EVP_KDF_CTX_free(kctx);
+ EVP_KDF_free(kdf);
+
+ return 0;
+
+ fail_derive:
+ EVP_KDF_CTX_free(kctx);
+ fail_ctx:
+ EVP_KDF_free(kdf);
+ fail_fetch:
+ return -ECRYPT;
+}
/*
* Derive the common secret from
- * - your public key pair (kp)
- * - the remote public key (pub).
+ * - your public key pair (pkp)
+ * - the remote public key bytes (remote_pk).
* Store it in a preallocated buffer (s).
*/
-static int __openssl_ecdh_derive_secret(EVP_PKEY * kp,
- EVP_PKEY * pub,
- uint8_t * s)
+static int __openssl_dhe_derive(EVP_PKEY * pkp,
+ EVP_PKEY * pub,
+ buffer_t remote_pk,
+ int kdf,
+ uint8_t * s)
{
- EVP_PKEY_CTX * ctx;
- int ret;
- uint8_t * secret;
- size_t secret_len;
-
- ctx = EVP_PKEY_CTX_new(kp, NULL);
+ EVP_PKEY_CTX * ctx;
+ struct kdf_info ki;
+ buffer_t local_pk;
+ int ret;
+ uint8_t * secret;
+ size_t secret_len;
+ uint8_t salt_buf[HKDF_SALT_LEN];
+
+ /* Extract local public key bytes */
+ local_pk.data = NULL;
+ ret = i2d_PUBKEY(pkp, &local_pk.data);
+ if (ret <= 0)
+ goto fail_local;
+ local_pk.len = (size_t) ret;
+
+ /* Derive salt from both public keys */
+ if (derive_salt_from_pk_bytes_dhe(local_pk, remote_pk, salt_buf,
+ HKDF_SALT_LEN) < 0)
+ goto fail_salt;
+
+ ctx = EVP_PKEY_CTX_new(pkp, NULL);
if (ctx == NULL)
- goto fail_new;
+ goto fail_salt;
ret = EVP_PKEY_derive_init(ctx);
if (ret != 1)
@@ -81,37 +293,78 @@ static int __openssl_ecdh_derive_secret(EVP_PKEY * kp,
if (ret != 1)
goto fail_derive;
- /* Hash the secret for use as AES key. */
- mem_hash(HASH_SHA3_256, s, secret, secret_len);
+ ki.nid = kdf;
+ ki.secret.len = secret_len;
+ ki.secret.data = secret;
+ ki.info.len = strlen(HKDF_INFO_DHE);
+ ki.info.data = (uint8_t *) HKDF_INFO_DHE;
+ ki.key.len = SYMMKEYSZ;
+ ki.key.data = s;
+ ki.salt.len = HKDF_SALT_LEN;
+ ki.salt.data = salt_buf;
+
+ /* Derive symmetric key from shared secret using HKDF */
+ ret = derive_key_hkdf(&ki);
OPENSSL_free(secret);
EVP_PKEY_CTX_free(ctx);
+ OPENSSL_free(local_pk.data);
+
+ if (ret != 0)
+ return ret;
return 0;
fail_derive:
OPENSSL_free(secret);
fail_ctx:
EVP_PKEY_CTX_free(ctx);
- fail_new:
+ fail_salt:
+ OPENSSL_free(local_pk.data);
+ fail_local:
return -ECRYPT;
}
-static int __openssl_ecdh_gen_key(void ** kp)
+static int __openssl_dhe_gen_key(const char * algo,
+ EVP_PKEY ** kp)
{
EVP_PKEY_CTX * ctx = NULL;
EVP_PKEY_CTX * kctx = NULL;
EVP_PKEY * params = NULL;
+ int nid;
+ int type;
int ret;
- ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL);
+ assert(algo != NULL);
+ assert(kp != NULL);
+
+ nid = OBJ_txt2nid(algo);
+ if (nid == NID_undef)
+ return -ECRYPT;
+
+ /* X25519 and X448: direct keygen context */
+ if (nid == EVP_PKEY_X25519 || nid == EVP_PKEY_X448) {
+ kctx = EVP_PKEY_CTX_new_id(nid, NULL);
+ if (kctx == NULL)
+ goto fail_kctx;
+
+ goto keygen;
+ }
+ /* EC and FFDHE: parameter generation first */
+ type = (strncmp(algo, "ffdhe", 5) == 0) ? EVP_PKEY_DH : EVP_PKEY_EC;
+
+ ctx = EVP_PKEY_CTX_new_id(type, NULL);
if (ctx == NULL)
- goto fail_new_id;
+ goto fail_ctx;
ret = EVP_PKEY_paramgen_init(ctx);
if (ret != 1)
goto fail_paramgen;
- ret = EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx, NID_X9_62_prime256v1);
+ if (type == EVP_PKEY_EC)
+ ret = EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx, nid);
+ else /* EVP_PKEY_DH */
+ ret = EVP_PKEY_CTX_set_dh_nid(ctx, nid);
+
if (ret != 1)
goto fail_paramgen;
@@ -121,187 +374,556 @@ static int __openssl_ecdh_gen_key(void ** kp)
kctx = EVP_PKEY_CTX_new(params, NULL);
if (kctx == NULL)
- goto fail_keygen_init;
+ goto fail_kctx;
+ EVP_PKEY_free(params);
+ EVP_PKEY_CTX_free(ctx);
+ keygen:
ret = EVP_PKEY_keygen_init(kctx);
if (ret != 1)
goto fail_keygen;
- ret = EVP_PKEY_keygen(kctx, (EVP_PKEY **) kp);
+ ret = EVP_PKEY_keygen(kctx, kp);
if (ret != 1)
goto fail_keygen;
- EVP_PKEY_free(params);
EVP_PKEY_CTX_free(kctx);
- EVP_PKEY_CTX_free(ctx);
return 0;
+
fail_keygen:
EVP_PKEY_CTX_free(kctx);
- fail_keygen_init:
- EVP_PKEY_free(params);
+ return -ECRYPT;
+ fail_kctx:
+ if (params != NULL)
+ EVP_PKEY_free(params);
fail_paramgen:
- EVP_PKEY_CTX_free(ctx);
- fail_new_id:
+ if (ctx != NULL)
+ EVP_PKEY_CTX_free(ctx);
+ fail_ctx:
+ return -ECRYPT;
+}
+
+static int __openssl_kem_gen_key(const char * algo,
+ EVP_PKEY ** kp)
+{
+ EVP_PKEY_CTX * kctx;
+ int ret;
+
+ assert(algo != NULL);
+ assert(kp != NULL);
+
+ /* PQC KEM (ML-KEM-512, ML-KEM-768, ML-KEM-1024) or hybrid */
+ kctx = EVP_PKEY_CTX_new_from_name(NULL, algo, NULL);
+ if (kctx == NULL)
+ goto fail_kctx;
+
+ ret = EVP_PKEY_keygen_init(kctx);
+ if (ret != 1)
+ goto fail_keygen;
+
+ ret = EVP_PKEY_keygen(kctx, kp);
+ if (ret != 1)
+ goto fail_keygen;
+
+ EVP_PKEY_CTX_free(kctx);
+
+ return 0;
+
+ fail_keygen:
+ EVP_PKEY_CTX_free(kctx);
+ fail_kctx:
return -ECRYPT;
}
-ssize_t openssl_ecdh_pkp_create(void ** pkp,
- uint8_t * pk)
+/* Determine hybrid KEM algorithm from raw key/ciphertext length */
+static const char * __openssl_hybrid_algo_from_len(size_t len)
+{
+ switch(len) {
+ case X25519MLKEM768_PKSZ:
+ return "X25519MLKEM768";
+ case X25519MLKEM768_CTSZ:
+ return "X25519MLKEM768";
+ case X448MLKEM1024_PKSZ:
+ return "X448MLKEM1024";
+ default:
+ break;
+ }
+
+ return NULL;
+}
+
+static int __openssl_kex_gen_key(const char * algo,
+ EVP_PKEY ** kp)
{
- uint8_t * pos;
- ssize_t len;
+ assert(algo != NULL);
+ assert(kp != NULL);
+
+ /* Dispatch based on algorithm name prefix */
+ if (IS_KEM_ALGORITHM(algo))
+ return __openssl_kem_gen_key(algo, kp);
+
+ return __openssl_dhe_gen_key(algo, kp);
+}
+ssize_t openssl_pkp_create(const char * algo,
+ EVP_PKEY ** pkp,
+ uint8_t * pk)
+{
+ uint8_t * pos;
+ buffer_t raw;
+ ssize_t len;
+
+ assert(algo != NULL);
assert(pkp != NULL);
assert(*pkp == NULL);
assert(pk != NULL);
- if (__openssl_ecdh_gen_key(pkp) < 0)
+ if (__openssl_kex_gen_key(algo, pkp) < 0)
goto fail_key;
- pos = pk; /* i2d_PUBKEY increments the pointer, don't use pk! */
- len = i2d_PUBKEY(*pkp, &pos);
- if (len < 0)
- goto fail_pubkey;
+ if (IS_HYBRID_KEM(algo)) { /* Raw encode hybrid KEM */
+ raw.len = EVP_PKEY_get1_encoded_public_key(*pkp, &raw.data);
+ if (raw.len == 0)
+ goto fail_pubkey;
+
+ memcpy(pk, raw.data, raw.len);
+ OPENSSL_free(raw.data);
+
+ return (ssize_t) raw.len;
+ } else { /* DER encode standard algorithms */
+ pos = pk; /* i2d_PUBKEY increments the pointer, don't use pk! */
+ len = i2d_PUBKEY(*pkp, &pos);
+ if (len < 0)
+ goto fail_pubkey;
- return len;
+ return len;
+ }
fail_pubkey:
EVP_PKEY_free(*pkp);
fail_key:
return -ECRYPT;
}
-void openssl_ecdh_pkp_destroy(void * pkp)
+/* Common KEM encapsulation - pub key and salt already prepared */
+static ssize_t __openssl_kem_encap(EVP_PKEY * pub,
+ uint8_t * salt,
+ uint8_t * ct,
+ int kdf,
+ uint8_t * s)
+{
+ EVP_PKEY_CTX * ctx;
+ struct kdf_info ki;
+ uint8_t * secret;
+ size_t secret_len;
+ size_t ct_len;
+ int ret;
+
+ ctx = EVP_PKEY_CTX_new(pub, NULL);
+ if (ctx == NULL)
+ goto fail_ctx;
+
+ ret = EVP_PKEY_encapsulate_init(ctx, NULL);
+ if (ret != 1)
+ goto fail_encap;
+
+ /* Get required lengths */
+ ret = EVP_PKEY_encapsulate(ctx, NULL, &ct_len, NULL, &secret_len);
+ if (ret != 1 || ct_len > MSGBUFSZ)
+ goto fail_encap;
+
+ /* Allocate buffer for secret */
+ secret = OPENSSL_malloc(secret_len);
+ if (secret == NULL)
+ goto fail_encap;
+
+ /* Perform encapsulation */
+ ret = EVP_PKEY_encapsulate(ctx, ct, &ct_len, secret, &secret_len);
+ if (ret != 1)
+ goto fail_secret;
+
+ ki.secret.len = secret_len;
+ ki.secret.data = secret;
+ ki.nid = kdf;
+ ki.info.len = strlen(HKDF_INFO_ENCAP);
+ ki.info.data = (uint8_t *) HKDF_INFO_ENCAP;
+ ki.key.len = SYMMKEYSZ;
+ ki.key.data = s;
+ ki.salt.len = HKDF_SALT_LEN;
+ ki.salt.data = salt;
+
+ /* Derive symmetric key from shared secret using HKDF */
+ ret = derive_key_hkdf(&ki);
+
+ OPENSSL_free(secret);
+ EVP_PKEY_CTX_free(ctx);
+
+ if (ret != 0)
+ return -ECRYPT;
+
+ return (ssize_t) ct_len;
+
+ fail_secret:
+ OPENSSL_free(secret);
+ fail_encap:
+ EVP_PKEY_CTX_free(ctx);
+ fail_ctx:
+ return -ECRYPT;
+}
+
+/* ML-KEM encapsulation - DER-encoded public key */
+ssize_t openssl_kem_encap(buffer_t pk,
+ uint8_t * ct,
+ int kdf,
+ uint8_t * s)
+{
+ EVP_PKEY * pub;
+ uint8_t * pos;
+ uint8_t salt[HKDF_SALT_LEN];
+ ssize_t ret;
+
+ assert(pk.data != NULL);
+ assert(ct != NULL);
+ assert(s != NULL);
+
+ if (derive_salt_from_pk_bytes(pk, salt, HKDF_SALT_LEN) < 0)
+ goto fail_salt;
+
+ pos = pk.data;
+ pub = d2i_PUBKEY(NULL, (const uint8_t **) &pos, (long) pk.len);
+ if (pub == NULL)
+ goto fail_salt;
+
+ ret = __openssl_kem_encap(pub, salt, ct, kdf, s);
+
+ EVP_PKEY_free(pub);
+
+ return ret;
+ fail_salt:
+ return -ECRYPT;
+}
+
+/* Hybrid KEM encapsulation: raw-encoded public key */
+ssize_t openssl_kem_encap_raw(buffer_t pk,
+ uint8_t * ct,
+ int kdf,
+ uint8_t * s)
+{
+ EVP_PKEY * pub;
+ const char * algo;
+ uint8_t salt[HKDF_SALT_LEN];
+ ssize_t ret;
+
+ assert(pk.data != NULL);
+ assert(ct != NULL);
+ assert(s != NULL);
+
+ if (derive_salt_from_pk_bytes(pk, salt, HKDF_SALT_LEN) < 0)
+ goto fail_salt;
+
+ algo = __openssl_hybrid_algo_from_len(pk.len);
+ if (algo == NULL)
+ goto fail_salt;
+
+ pub = EVP_PKEY_new_raw_public_key_ex(NULL, algo, NULL,
+ pk.data, pk.len);
+ if (pub == NULL)
+ goto fail_salt;
+
+ ret = __openssl_kem_encap(pub, salt, ct, kdf, s);
+
+ EVP_PKEY_free(pub);
+
+ return ret;
+ fail_salt:
+ return -ECRYPT;
+}
+
+/* KEM decapsulation - used by party that generated the keypair */
+int openssl_kem_decap(EVP_PKEY * priv,
+ buffer_t ct,
+ int kdf,
+ uint8_t * s)
+{
+ EVP_PKEY_CTX * ctx;
+ struct kdf_info ki;
+ buffer_t pk;
+ uint8_t * secret;
+ size_t secret_len;
+ int ret;
+ uint8_t salt[HKDF_SALT_LEN];
+
+ /* Extract public key bytes from private key */
+ if (get_pk_bytes_from_key(priv, &pk) < 0)
+ goto fail_pk;
+
+ if (derive_salt_from_pk_bytes(pk, salt, HKDF_SALT_LEN) < 0)
+ goto fail_salt;
+
+ ctx = EVP_PKEY_CTX_new(priv, NULL);
+ if (ctx == NULL)
+ goto fail_salt;
+
+ ret = EVP_PKEY_decapsulate_init(ctx, NULL);
+ if (ret != 1)
+ goto fail_ctx;
+
+ /* Get required secret length */
+ ret = EVP_PKEY_decapsulate(ctx, NULL, &secret_len, ct.data, ct.len);
+ if (ret != 1)
+ goto fail_ctx;
+
+ /* Allocate buffer for secret */
+ secret = OPENSSL_malloc(secret_len);
+ if (secret == NULL)
+ goto fail_ctx;
+
+ /* Perform decapsulation */
+ ret = EVP_PKEY_decapsulate(ctx, secret, &secret_len, ct.data, ct.len);
+ if (ret != 1)
+ goto fail_secret;
+
+ ki.secret.len = secret_len;
+ ki.secret.data = secret;
+ ki.nid = kdf;
+ ki.info.len = strlen(HKDF_INFO_ENCAP);
+ ki.info.data = (uint8_t *) HKDF_INFO_ENCAP;
+ ki.key.len = SYMMKEYSZ;
+ ki.key.data = s;
+ ki.salt.len = HKDF_SALT_LEN;
+ ki.salt.data = salt;
+
+ /* Derive symmetric key from shared secret using HKDF */
+ ret = derive_key_hkdf(&ki);
+
+ OPENSSL_free(secret);
+ EVP_PKEY_CTX_free(ctx);
+ OPENSSL_free(pk.data);
+
+ if (ret != 0)
+ return ret;
+
+ return 0;
+
+ fail_secret:
+ OPENSSL_free(secret);
+ fail_ctx:
+ EVP_PKEY_CTX_free(ctx);
+ fail_salt:
+ OPENSSL_free(pk.data);
+ fail_pk:
+ return -ECRYPT;
+}
+
+void openssl_pkp_destroy(EVP_PKEY * pkp)
+{
+ EVP_PKEY_free(pkp);
+}
+
+int __openssl_get_curve(EVP_PKEY * pub,
+ char * algo)
+{
+ int ret;
+ size_t len = KEX_ALGO_BUFSZ;
+
+ ret = EVP_PKEY_get_utf8_string_param(pub, "group", algo, len, &len);
+ return ret == 1 ? 0 : -ECRYPT;
+}
+
+int openssl_get_algo_from_pk_der(buffer_t pk,
+ char * algo)
+{
+ uint8_t * pos;
+ EVP_PKEY * pub;
+ char * type_str;
+
+ assert(pk.data != NULL);
+ assert(algo != NULL);
+
+ pos = pk.data;
+ pub = d2i_PUBKEY(NULL, (const uint8_t **) &pos, (long) pk.len);
+ if (pub == NULL)
+ goto fail_decode;
+
+ type_str = (char *) EVP_PKEY_get0_type_name(pub);
+ if (type_str == NULL)
+ goto fail_pub;
+
+ strcpy(algo, type_str);
+
+ if ((IS_EC_GROUP(algo) || IS_DH_GROUP(algo)) &&
+ __openssl_get_curve(pub, algo) < 0)
+ goto fail_pub;
+
+ EVP_PKEY_free(pub);
+ return 0;
+
+ fail_pub:
+ EVP_PKEY_free(pub);
+ fail_decode:
+ return -ECRYPT;
+}
+
+int openssl_get_algo_from_pk_raw(buffer_t pk,
+ char * algo)
{
- EVP_PKEY_free((EVP_PKEY *) pkp);
+ const char * hybrid_algo;
+
+ assert(pk.data != NULL);
+ assert(algo != NULL);
+
+ hybrid_algo = __openssl_hybrid_algo_from_len(pk.len);
+ if (hybrid_algo == NULL)
+ return -ECRYPT;
+
+ strcpy(algo, hybrid_algo);
+
+ return 0;
}
-int openssl_ecdh_derive(void * pkp,
- buffer_t pk,
- uint8_t * s)
+int openssl_dhe_derive(EVP_PKEY * pkp,
+ buffer_t pk,
+ int kdf,
+ uint8_t * s)
{
uint8_t * pos;
EVP_PKEY * pub;
+ assert(pkp != NULL);
+ assert(pk.data != NULL);
+ assert(s != NULL);
+
+ /* X.509 DER decoding for DHE */
pos = pk.data; /* d2i_PUBKEY increments pos, don't use key ptr! */
pub = d2i_PUBKEY(NULL, (const uint8_t **) &pos, (long) pk.len);
if (pub == NULL)
- goto fail_pubkey;
+ goto fail_decode;
- if (__openssl_ecdh_derive_secret(pkp, pub, s) < 0)
- goto fail_key;
+ if (__openssl_dhe_derive(pkp, pub, pk, kdf, s) < 0)
+ goto fail_derive;
EVP_PKEY_free(pub);
return 0;
- fail_pubkey:
+ fail_derive:
EVP_PKEY_free(pub);
- fail_key:
+ fail_decode:
return -ECRYPT;
}
-/*
- * AES encryption calls. If FRCT is disabled, we should generate a
- * 128-bit random IV and append it to the packet. If the flow is
- * reliable, we could initialize the context once, and consider the
- * stream a single encrypted message to avoid initializing the
- * encryption context for each packet.
- */
-
-int openssl_encrypt(void * ctx,
- uint8_t * key,
- buffer_t in,
- buffer_t * out)
+int openssl_encrypt(struct ossl_crypt_ctx * ctx,
+ buffer_t in,
+ buffer_t * out)
{
- uint8_t * ptr;
- uint8_t * iv;
- int in_sz;
- int out_sz;
- int tmp_sz;
- int ret;
+ uint8_t * ptr;
+ uint8_t * iv;
+ int in_sz;
+ int out_sz;
+ int tmp_sz;
+ int ret;
+
+ assert(ctx != NULL);
in_sz = (int) in.len;
- out->data = malloc(in.len + EVP_MAX_BLOCK_LENGTH + IVSZ);
+ out->data = malloc(in.len + EVP_MAX_BLOCK_LENGTH + \
+ ctx->ivsz + ctx->tagsz);
if (out->data == NULL)
goto fail_malloc;
iv = out->data;
- ptr = out->data + IVSZ;
+ ptr = out->data + ctx->ivsz;
- if (random_buffer(iv, IVSZ) < 0)
- goto fail_iv;
+ if (random_buffer(iv, ctx->ivsz) < 0)
+ goto fail_encrypt;
- EVP_CIPHER_CTX_reset(ctx);
+ EVP_CIPHER_CTX_reset(ctx->evp_ctx);
- ret = EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, iv);
+ ret = EVP_EncryptInit_ex(ctx->evp_ctx, ctx->cipher, NULL, ctx->key, iv);
if (ret != 1)
- goto fail_iv;
+ goto fail_encrypt;
- ret = EVP_EncryptUpdate(ctx, ptr, &tmp_sz, in.data, in_sz);
+ ret = EVP_EncryptUpdate(ctx->evp_ctx, ptr, &tmp_sz, in.data, in_sz);
if (ret != 1)
goto fail_encrypt;
out_sz = tmp_sz;
- ret = EVP_EncryptFinal_ex(ctx, ptr + tmp_sz, &tmp_sz);
+ ret = EVP_EncryptFinal_ex(ctx->evp_ctx, ptr + tmp_sz, &tmp_sz);
if (ret != 1)
goto fail_encrypt;
out_sz += tmp_sz;
- EVP_CIPHER_CTX_cleanup(ctx);
+ /* For AEAD ciphers, get and append the authentication tag */
+ if (ctx->tagsz > 0) {
+ ret = EVP_CIPHER_CTX_ctrl(ctx->evp_ctx, EVP_CTRL_AEAD_GET_TAG,
+ ctx->tagsz, ptr + out_sz);
+ if (ret != 1)
+ goto fail_encrypt;
+ out_sz += ctx->tagsz;
+ }
assert(out_sz >= in_sz);
- out->len = (size_t) out_sz + IVSZ;
+ out->len = (size_t) out_sz + ctx->ivsz;
return 0;
fail_encrypt:
- EVP_CIPHER_CTX_cleanup(ctx);
- fail_iv:
free(out->data);
fail_malloc:
clrbuf(*out);
return -ECRYPT;
}
-int openssl_decrypt(void * ctx,
- uint8_t * key,
- buffer_t in,
- buffer_t * out)
+int openssl_decrypt(struct ossl_crypt_ctx * ctx,
+ buffer_t in,
+ buffer_t * out)
{
- uint8_t * ptr;
- uint8_t * iv;
- uint8_t * input;
- int ret;
- int out_sz;
- int in_sz;
- int tmp_sz;
-
- in_sz = (int) in.len - IVSZ;
- if (in_sz < 0)
+ uint8_t * ptr;
+ uint8_t * iv;
+ uint8_t * input;
+ int ret;
+ int out_sz;
+ int in_sz;
+ int tmp_sz;
+
+ assert(ctx != NULL);
+
+ in_sz = (int) in.len - ctx->ivsz;
+ if (in_sz < ctx->tagsz)
return -ECRYPT;
- out->data = malloc(in_sz);
+ in_sz -= ctx->tagsz;
+
+ out->data = malloc(in_sz + EVP_MAX_BLOCK_LENGTH);
if (out->data == NULL)
goto fail_malloc;
iv = in.data;
ptr = out->data;
- input = in.data + IVSZ;
+ input = in.data + ctx->ivsz;
- EVP_CIPHER_CTX_reset(ctx);
+ EVP_CIPHER_CTX_reset(ctx->evp_ctx);
- ret = EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, iv);
+ ret = EVP_DecryptInit_ex(ctx->evp_ctx, ctx->cipher, NULL, ctx->key, iv);
if (ret != 1)
- goto fail_decrypt_init;
+ goto fail_decrypt;
+
+ /* For AEAD ciphers, set the expected authentication tag */
+ if (ctx->tagsz > 0) {
+ uint8_t * tag = input + in_sz;
+ ret = EVP_CIPHER_CTX_ctrl(ctx->evp_ctx, EVP_CTRL_AEAD_SET_TAG,
+ ctx->tagsz, tag);
+ if (ret != 1)
+ goto fail_decrypt;
+ }
- ret = EVP_DecryptUpdate(ctx, ptr, &tmp_sz, input, in_sz);
+ ret = EVP_DecryptUpdate(ctx->evp_ctx, ptr, &tmp_sz, input, in_sz);
if (ret != 1)
goto fail_decrypt;
out_sz = tmp_sz;
- ret = EVP_DecryptFinal_ex(ctx, ptr + tmp_sz, &tmp_sz);
+ ret = EVP_DecryptFinal_ex(ctx->evp_ctx, ptr + tmp_sz, &tmp_sz);
if (ret != 1)
goto fail_decrypt;
@@ -313,22 +935,65 @@ int openssl_decrypt(void * ctx,
return 0;
fail_decrypt:
- EVP_CIPHER_CTX_cleanup(ctx);
- fail_decrypt_init:
free(out->data);
fail_malloc:
clrbuf(*out);
return -ECRYPT;
}
-void * openssl_crypt_create_ctx(void)
+struct ossl_crypt_ctx * openssl_crypt_create_ctx(struct crypt_sk * sk)
{
- return (void *) EVP_CIPHER_CTX_new();
+ struct ossl_crypt_ctx * ctx;
+
+ assert(sk != NULL);
+ assert(sk->key != NULL);
+
+ ctx = malloc(sizeof(*ctx));
+ if (ctx == NULL)
+ goto fail_malloc;
+
+ memset(ctx, 0, sizeof(*ctx));
+
+ ctx->key = OPENSSL_secure_malloc(SYMMKEYSZ);
+ if (ctx->key == NULL)
+ goto fail_key;
+
+ memcpy(ctx->key, sk->key, SYMMKEYSZ);
+
+ ctx->cipher = EVP_get_cipherbynid(sk->nid);
+ if (ctx->cipher == NULL)
+ goto fail_cipher;
+
+ ctx->ivsz = EVP_CIPHER_iv_length(ctx->cipher);
+
+ /* Set tag size for AEAD ciphers (GCM, CCM, OCB, ChaCha20-Poly1305) */
+ if (EVP_CIPHER_flags(ctx->cipher) & EVP_CIPH_FLAG_AEAD_CIPHER)
+ ctx->tagsz = 16; /* Standard AEAD tag length (128 bits) */
+
+ ctx->evp_ctx = EVP_CIPHER_CTX_new();
+ if (ctx->evp_ctx == NULL)
+ goto fail_cipher;
+
+ return ctx;
+
+ fail_cipher:
+ OPENSSL_secure_clear_free(ctx->key, SYMMKEYSZ);
+ fail_key:
+ free(ctx);
+ fail_malloc:
+ return NULL;
}
-void openssl_crypt_destroy_ctx(void * ctx)
+void openssl_crypt_destroy_ctx(struct ossl_crypt_ctx * ctx)
{
- EVP_CIPHER_CTX_free((EVP_CIPHER_CTX *) ctx);
+ if (ctx == NULL)
+ return;
+
+ if (ctx->key != NULL)
+ OPENSSL_secure_clear_free(ctx->key, SYMMKEYSZ);
+
+ EVP_CIPHER_CTX_free(ctx->evp_ctx);
+ free(ctx);
}
/* AUTHENTICATION */
@@ -442,14 +1107,24 @@ int openssl_load_privkey_file(const char * path,
{
FILE * fp;
EVP_PKEY * pkey;
+ unsigned long err;
+ char errbuf[256];
fp = fopen(path, "r");
- if (fp == NULL)
+ if (fp == NULL) {
+ fprintf(stderr, "Failed to open %s\n", path);
goto fail_file;
+ }
pkey = PEM_read_PrivateKey(fp, NULL, NULL, "");
- if (pkey == NULL)
+ if (pkey == NULL) {
+ err = ERR_get_error();
+ ERR_error_string_n(err, errbuf, sizeof(errbuf));
+ fprintf(stderr,
+ "OpenSSL error loading privkey from %s: %s\n",
+ path, errbuf);
goto fail_key;
+ }
fclose(fp);
@@ -518,6 +1193,48 @@ int openssl_load_pubkey_file(const char * path,
return -1;
}
+int openssl_load_pubkey_file_to_der(const char * path,
+ buffer_t * buf)
+{
+ FILE * fp;
+ EVP_PKEY * pkey;
+ int ret;
+
+ assert(path != NULL);
+ assert(buf != NULL);
+
+ memset(buf, 0, sizeof(*buf));
+
+ fp = fopen(path, "r");
+ if (fp == NULL)
+ goto fail_file;
+
+ pkey = PEM_read_PUBKEY(fp, NULL, NULL, NULL);
+ if (pkey == NULL)
+ goto fail_key;
+
+ fclose(fp);
+
+ /* Extract public key bytes in DER format */
+ ret = get_pk_bytes_from_key(pkey, buf);
+
+ EVP_PKEY_free(pkey);
+
+ if (ret < 0)
+ goto fail_extract;
+
+ return 0;
+
+ fail_extract:
+ clrbuf(*buf);
+ return -1;
+ fail_key:
+ fclose(fp);
+ fail_file:
+ clrbuf(*buf);
+ return -1;
+}
+
int openssl_load_pubkey_str(const char * str,
void ** key)
{
@@ -547,28 +1264,123 @@ int openssl_load_pubkey_str(const char * str,
return -1;
}
-int openssl_cmp_key(const void * key1,
- const void * key2)
+int openssl_load_pubkey_raw_file(const char * path,
+ buffer_t * buf)
+{
+ FILE * fp;
+ uint8_t tmp_buf[MSGBUFSZ];
+ size_t bytes_read;
+ const char * algo;
+
+ assert(path != NULL);
+ assert(buf != NULL);
+
+ fp = fopen(path, "rb");
+ if (fp == NULL)
+ goto fail_file;
+
+ bytes_read = fread(tmp_buf, 1, MSGBUFSZ, fp);
+ if (bytes_read == 0)
+ goto fail_read;
+
+ /* Validate that this is a known hybrid KEM format */
+ algo = __openssl_hybrid_algo_from_len(bytes_read);
+ if (algo == NULL)
+ goto fail_read;
+
+ buf->data = malloc(bytes_read);
+ if (buf->data == NULL)
+ goto fail_malloc;
+
+ memcpy(buf->data, tmp_buf, bytes_read);
+ buf->len = bytes_read;
+
+ return 0;
+
+ fail_malloc:
+ fail_read:
+ fclose(fp);
+ fail_file:
+ clrbuf(*buf);
+ return -1;
+}
+
+/* Determine hybrid KEM algorithm from raw private key length */
+static const char * __openssl_hybrid_algo_from_sk_len(size_t len)
+{
+ switch(len) {
+ case X25519MLKEM768_SKSZ:
+ return "X25519MLKEM768";
+ case X448MLKEM1024_SKSZ:
+ return "X448MLKEM1024";
+ default:
+ break;
+ }
+
+ return NULL;
+}
+
+int openssl_load_privkey_raw_file(const char * path,
+ void ** key)
{
- EVP_PKEY * pkey1;
- EVP_PKEY * pkey2;
+ FILE * fp;
+ uint8_t tmp_buf[4096];
+ size_t bytes_read;
+ const char * algo;
+ EVP_PKEY * pkey;
+
+ assert(path != NULL);
+ assert(key != NULL);
+
+ fp = fopen(path, "rb");
+ if (fp == NULL)
+ goto fail_file;
+
+ bytes_read = fread(tmp_buf, 1, sizeof(tmp_buf), fp);
+ fclose(fp);
+
+ if (bytes_read == 0)
+ goto fail_read;
+
+ /* Determine algorithm from key size */
+ algo = __openssl_hybrid_algo_from_sk_len(bytes_read);
+ if (algo == NULL)
+ goto fail_read;
+
+ pkey = EVP_PKEY_new_raw_private_key_ex(NULL, algo, NULL,
+ tmp_buf, bytes_read);
+ /* Clear sensitive data from stack */
+ OPENSSL_cleanse(tmp_buf, bytes_read);
+ if (pkey == NULL)
+ goto fail_read;
+
+ *key = (void *) pkey;
+
+ return 0;
+
+ fail_read:
+ fail_file:
+ *key = NULL;
+ return -1;
+}
+
+int openssl_cmp_key(const EVP_PKEY * key1,
+ const EVP_PKEY * key2)
+{
assert(key1 != NULL);
assert(key2 != NULL);
- pkey1 = (EVP_PKEY *) key1;
- pkey2 = (EVP_PKEY *) key2;
-
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
- return EVP_PKEY_eq(pkey1, pkey2) == 1 ? 0 : -1;
+ return EVP_PKEY_eq(key1, key2) == 1 ? 0 : -1;
#else
- return EVP_PKEY_cmp(pkey1, pkey2) == 1 ? 0 : -1;
+ return EVP_PKEY_cmp(key1, key2) == 1 ? 0 : -1;
#endif
}
-void openssl_free_key(void * key)
+void openssl_free_key(EVP_PKEY * key)
{
- EVP_PKEY_free((EVP_PKEY *) key);
+ EVP_PKEY_free(key);
}
int openssl_check_crt_name(void * crt,
@@ -600,6 +1412,41 @@ int openssl_check_crt_name(void * crt,
return -1;
}
+int openssl_get_crt_name(void * crt,
+ char * name)
+{
+ char * subj;
+ char * cn;
+ char * end;
+ X509 * xcrt;
+
+ xcrt = (X509 *) crt;
+
+ subj = X509_NAME_oneline(X509_get_subject_name(xcrt), NULL, 0);
+ if (subj == NULL)
+ goto fail_subj;
+
+ cn = strstr(subj, "CN=");
+ if (cn == NULL)
+ goto fail_cn;
+
+ cn += 3; /* Skip "CN=" */
+
+ /* Find end of CN (comma or slash for next field) */
+ end = strpbrk(cn, ",/");
+ if (end != NULL)
+ *end = '\0';
+
+ strcpy(name, cn);
+ free(subj);
+
+ return 0;
+ fail_cn:
+ free(subj);
+ fail_subj:
+ return -1;
+}
+
int openssl_crt_str(const void * crt,
char * str)
{
@@ -704,37 +1551,48 @@ int openssl_verify_crt(void * store,
return -1;
}
-int openssl_sign(void * pkp,
+static const EVP_MD * select_md(EVP_PKEY * pkey,
+ int nid)
+{
+ if (EVP_PKEY_get_id(pkey) < 0)
+ return NULL; /* Provider-based (PQC) */
+
+ if (nid == NID_undef)
+ return NULL; /* Classical requires explicit nid */
+
+ return EVP_get_digestbynid(nid);
+}
+
+int openssl_sign(EVP_PKEY * pkp,
+ int nid,
buffer_t msg,
buffer_t * sig)
{
- EVP_PKEY * pkey;
- EVP_MD_CTX * mdctx;
- size_t required;
+ EVP_MD_CTX * mdctx;
+ const EVP_MD * md;
+ size_t required;
assert(pkp != NULL);
assert(sig != NULL);
- pkey = (EVP_PKEY *) pkp;
-
mdctx = EVP_MD_CTX_new();
if (!mdctx)
goto fail_ctx;
- if (EVP_DigestSignInit(mdctx, NULL, EVP_sha256(), NULL, pkey) != 1)
- goto fail_digest;
+ md = select_md(pkp, nid);
- if (EVP_DigestSignUpdate(mdctx, msg.data, msg.len) != 1)
+ if (EVP_DigestSignInit(mdctx, NULL, md, NULL, pkp) != 1)
goto fail_digest;
- if (EVP_DigestSignFinal(mdctx, NULL, &required) != 1)
+ /* Get required signature buffer size */
+ if (EVP_DigestSign(mdctx, NULL, &required, msg.data, msg.len) != 1)
goto fail_digest;
sig->data = malloc(required);
if (sig->data == NULL)
goto fail_digest;
- if (EVP_DigestSignFinal(mdctx, sig->data, &required) != 1)
+ if (EVP_DigestSign(mdctx, sig->data, &required, msg.data, msg.len) != 1)
goto fail_sign;
sig->len = required;
@@ -751,29 +1609,27 @@ int openssl_sign(void * pkp,
return -1;
}
-int openssl_verify_sig(void * pk,
- buffer_t msg,
- buffer_t sig)
+int openssl_verify_sig(EVP_PKEY * pk,
+ int nid,
+ buffer_t msg,
+ buffer_t sig)
{
- EVP_PKEY * pkey;
- EVP_MD_CTX * mdctx;
- int ret;
+ EVP_MD_CTX * mdctx;
+ const EVP_MD * md;
+ int ret;
assert(pk != NULL);
- pkey = (EVP_PKEY *) pk;
-
mdctx = EVP_MD_CTX_new();
if (!mdctx)
goto fail_ctx;
- if (EVP_DigestVerifyInit(mdctx, NULL, EVP_sha256(), NULL, pkey) != 1)
- goto fail_digest;
+ md = select_md(pk, nid);
- if (EVP_DigestVerifyUpdate(mdctx, msg.data, msg.len) != 1)
+ if (EVP_DigestVerifyInit(mdctx, NULL, md, NULL, pk) != 1)
goto fail_digest;
- ret = EVP_DigestVerifyFinal(mdctx, sig.data, sig.len);
+ ret = EVP_DigestVerify(mdctx, sig.data, sig.len, msg.data, msg.len);
if (ret != 1)
goto fail_digest;
@@ -786,3 +1642,55 @@ int openssl_verify_sig(void * pk,
clrbuf(sig);
return -1;
}
+
+ssize_t openssl_md_digest(int nid,
+ buffer_t in,
+ uint8_t * out)
+{
+ const EVP_MD * md;
+ unsigned int len;
+
+ assert(in.data != NULL);
+ assert(out != NULL);
+
+ md = EVP_get_digestbynid(nid);
+ if (md == NULL)
+ return -1;
+
+ if (EVP_Digest(in.data, in.len, out, &len, md, NULL) != 1)
+ return -1;
+
+ return (ssize_t) len;
+}
+
+ssize_t openssl_md_len(int nid)
+{
+ const EVP_MD * md;
+
+ md = EVP_get_digestbynid(nid);
+ if (md == NULL)
+ return -1;
+
+ return (ssize_t) EVP_MD_get_size(md);
+}
+
+int openssl_secure_malloc_init(size_t max,
+ size_t guard)
+{
+ return CRYPTO_secure_malloc_init(max, guard) == 1 ? 0 : -1;
+}
+
+void openssl_secure_malloc_fini(void)
+{
+ CRYPTO_secure_malloc_done();
+}
+
+void * openssl_secure_malloc(size_t size)
+{
+ return OPENSSL_secure_malloc(size);
+}
+
+void openssl_secure_free(void * ptr)
+{
+ OPENSSL_secure_free(ptr);
+}
diff --git a/src/lib/crypt/openssl.h b/src/lib/crypt/openssl.h
index d4ee73b9..c28d0b4d 100644
--- a/src/lib/crypt/openssl.h
+++ b/src/lib/crypt/openssl.h
@@ -26,28 +26,52 @@
#ifndef OUROBOROS_LIB_CRYPT_OPENSSL_H
#define OUROBOROS_LIB_CRYPT_OPENSSL_H
-ssize_t openssl_ecdh_pkp_create(void ** pkp,
- uint8_t * pk);
+struct ossl_crypt_ctx;
-void openssl_ecdh_pkp_destroy(void * pkp);
+ssize_t openssl_pkp_create(const char * algo,
+ EVP_PKEY ** pkp,
+ uint8_t * pk);
-int openssl_ecdh_derive(void * pkp,
- buffer_t pk,
- uint8_t * s);
+void openssl_pkp_destroy(EVP_PKEY * pkp);
-int openssl_encrypt(void * ctx,
- uint8_t * key,
- buffer_t in,
- buffer_t * out);
+int openssl_dhe_derive(EVP_PKEY * pkp,
+ buffer_t pk,
+ int kdf_nid,
+ uint8_t * s);
-int openssl_decrypt(void * ctx,
- uint8_t * key,
- buffer_t in,
- buffer_t * out);
+ssize_t openssl_kem_encap(buffer_t pk,
+ uint8_t * ct,
+ int kdf_nid,
+ uint8_t * s);
-void * openssl_crypt_create_ctx(void);
+/* no X509 DER support yet for DHKEM public keys */
+ssize_t openssl_kem_encap_raw(buffer_t pk,
+ uint8_t * ct,
+ int kdf_nid,
+ uint8_t * s);
-void openssl_crypt_destroy_ctx(void * ctx);
+int openssl_kem_decap(EVP_PKEY * priv,
+ buffer_t ct,
+ int kdf_nid,
+ uint8_t * s);
+
+int openssl_get_algo_from_pk_der(buffer_t pk,
+ char * algo);
+
+int openssl_get_algo_from_pk_raw(buffer_t pk,
+ char * algo);
+
+int openssl_encrypt(struct ossl_crypt_ctx * ctx,
+ buffer_t in,
+ buffer_t * out);
+
+int openssl_decrypt(struct ossl_crypt_ctx * ctx,
+ buffer_t in,
+ buffer_t * out);
+
+struct ossl_crypt_ctx * openssl_crypt_create_ctx(struct crypt_sk * sk);
+
+void openssl_crypt_destroy_ctx(struct ossl_crypt_ctx * ctx);
/* AUTHENTICATION */
@@ -76,15 +100,25 @@ int openssl_load_pubkey_file(const char * path,
int openssl_load_pubkey_str(const char * str,
void ** key);
+int openssl_load_pubkey_file_to_der(const char * path,
+ buffer_t * buf);
+int openssl_load_pubkey_raw_file(const char * path,
+ buffer_t * buf);
+
+int openssl_load_privkey_raw_file(const char * path,
+ void ** key);
-int openssl_cmp_key(const void * key1,
- const void * key2);
+int openssl_cmp_key(const EVP_PKEY * key1,
+ const EVP_PKEY * key2);
-void openssl_free_key(void * key);
+void openssl_free_key(EVP_PKEY * key);
int openssl_check_crt_name(void * crt,
const char * name);
+int openssl_get_crt_name(void * crt,
+ char * name);
+
int openssl_crt_str(const void * crt,
char * str);
@@ -101,12 +135,31 @@ int openssl_auth_add_crt_to_store(void * store,
int openssl_verify_crt(void * store,
void * crt);
-int openssl_sign(void * pkp,
+int openssl_sign(EVP_PKEY * pkp,
+ int md_nid,
buffer_t msg,
buffer_t * sig);
-int openssl_verify_sig(void * pk,
- buffer_t msg,
- buffer_t sig);
+int openssl_verify_sig(EVP_PKEY * pk,
+ int md_nid,
+ buffer_t msg,
+ buffer_t sig);
+
+ssize_t openssl_md_digest(int md_nid,
+ buffer_t in,
+ uint8_t * out);
+
+ssize_t openssl_md_len(int md_nid);
+
+/* Secure memory allocation */
+int openssl_secure_malloc_init(size_t max,
+ size_t guard);
+
+void openssl_secure_malloc_fini(void);
+
+void * openssl_secure_malloc(size_t size);
+
+void openssl_secure_free(void * ptr,
+ size_t size);
#endif /* OUROBOROS_LIB_CRYPT_OPENSSL_H */
diff --git a/src/lib/dev.c b/src/lib/dev.c
index cb483aca..2c0dbf28 100644
--- a/src/lib/dev.c
+++ b/src/lib/dev.c
@@ -502,8 +502,10 @@ static void flow_fini(int fd)
pthread_rwlock_unlock(&ai.lock);
}
+#define IS_ENCRYPTED(crypt) ((crypt)->nid != NID_undef)
+#define IS_ORDERED(flow) (flow.qs.in_order != 0)
static int flow_init(struct flow_info * info,
- buffer_t * sk)
+ struct crypt_sk * sk)
{
struct timespec now;
struct flow * flow;
@@ -542,16 +544,15 @@ static int flow_init(struct flow_info * info,
flow->rcv_act = now;
flow->crypt = NULL;
- if (sk!= NULL && sk->data != NULL) {
- assert(sk->len == SYMMKEYSZ);
- flow->crypt = crypt_create_ctx(sk->data);
+ if (IS_ENCRYPTED(sk)) {
+ flow->crypt = crypt_create_ctx(sk);
if (flow->crypt == NULL)
goto fail_crypt;
}
assert(flow->frcti == NULL);
- if (info->qs.in_order != 0) {
+ if (IS_ORDERED(flow->info)) {
flow->frcti = frcti_create(fd, DELT_A, DELT_R, info->mpl);
if (flow->frcti == NULL)
goto fail_frcti;
@@ -708,6 +709,11 @@ static void init(int argc,
goto fail_timerwheel;
}
+ if (crypt_secure_malloc_init(PROC_SECMEM_MAX) < 0) {
+ fprintf(stderr, "FATAL: Could not init secure malloc.\n");
+ goto fail_timerwheel;
+ }
+
#if defined PROC_FLOW_STATS
if (strstr(argv[0], "ipcpd") == NULL) {
sprintf(procstr, "proc.%d", getpid());
@@ -823,12 +829,13 @@ __attribute__((section(FINI_SECTION))) __typeof__(fini) * __fini = fini;
int flow_accept(qosspec_t * qs,
const struct timespec * timeo)
{
- struct flow_info flow;
- uint8_t buf[SOCK_BUF_SIZE];
- buffer_t msg = {SOCK_BUF_SIZE, buf};
- buffer_t sk;
- int fd;
- int err;
+ struct flow_info flow;
+ struct crypt_sk crypt;
+ uint8_t buf[SOCK_BUF_SIZE];
+ buffer_t msg = {SOCK_BUF_SIZE, buf};
+ uint8_t key[SYMMKEYSZ];
+ int fd;
+ int err;
#ifdef QOS_DISABLE_CRC
if (qs != NULL)
@@ -846,13 +853,15 @@ int flow_accept(qosspec_t * qs,
if (err < 0)
return err;
- err = flow__irm_result_des(&msg, &flow, &sk);
+ crypt.key = key;
+
+ err = flow__irm_result_des(&msg, &flow, &crypt);
if (err < 0)
return err;
- fd = flow_init(&flow, &sk);
+ fd = flow_init(&flow, &crypt);
- freebuf(sk);
+ explicit_bzero(key, SYMMKEYSZ);
if (qs != NULL)
*qs = flow.qs;
@@ -864,12 +873,13 @@ int flow_alloc(const char * dst,
qosspec_t * qs,
const struct timespec * timeo)
{
- struct flow_info flow;
- uint8_t buf[SOCK_BUF_SIZE];
- buffer_t msg = {SOCK_BUF_SIZE, buf};
- buffer_t sk; /* symmetric key */
- int fd;
- int err;
+ struct flow_info flow;
+ struct crypt_sk crypt;
+ uint8_t buf[SOCK_BUF_SIZE];
+ buffer_t msg = {SOCK_BUF_SIZE, buf};
+ uint8_t key[SYMMKEYSZ];
+ int fd;
+ int err;
#ifdef QOS_DISABLE_CRC
if (qs != NULL)
@@ -890,13 +900,15 @@ int flow_alloc(const char * dst,
return err;
}
- err = flow__irm_result_des(&msg, &flow, &sk);
+ crypt.key = key;
+
+ err = flow__irm_result_des(&msg, &flow, &crypt);
if (err < 0)
return err;
- fd = flow_init(&flow, &sk);
+ fd = flow_init(&flow, &crypt);
- freebuf(sk);
+ explicit_bzero(key, SYMMKEYSZ);
if (qs != NULL)
*qs = flow.qs;
@@ -907,11 +919,13 @@ int flow_alloc(const char * dst,
int flow_join(const char * dst,
const struct timespec * timeo)
{
- struct flow_info flow;
- uint8_t buf[SOCK_BUF_SIZE];
- buffer_t msg = {SOCK_BUF_SIZE, buf};
- int fd;
- int err;
+ struct flow_info flow;
+ struct crypt_sk crypt;
+ uint8_t buf[SOCK_BUF_SIZE];
+ buffer_t msg = {SOCK_BUF_SIZE, buf};
+ uint8_t key[SYMMKEYSZ];
+ int fd;
+ int err;
memset(&flow, 0, sizeof(flow));
@@ -925,11 +939,15 @@ int flow_join(const char * dst,
if (err < 0)
return err;
- err = flow__irm_result_des(&msg, &flow, NULL);
+ crypt.key = key;
+
+ err = flow__irm_result_des(&msg, &flow, &crypt);
if (err < 0)
return err;
- fd = flow_init(&flow, NULL);
+ fd = flow_init(&flow, &crypt);
+
+ explicit_bzero(key, SYMMKEYSZ);
return fd;
}
@@ -1785,7 +1803,8 @@ ssize_t fevent(struct flow_set * set,
int np1_flow_alloc(pid_t n_pid,
int flow_id)
{
- struct flow_info flow;
+ struct flow_info flow;
+ struct crypt_sk crypt = { .nid = NID_undef, .key = NULL };
memset(&flow, 0, sizeof(flow));
@@ -1795,7 +1814,7 @@ int np1_flow_alloc(pid_t n_pid,
flow.mpl = 0;
flow.n_1_pid = n_pid; /* This "flow" is upside-down! */
- return flow_init(&flow, NULL);
+ return flow_init(&flow, &crypt);
}
int np1_flow_dealloc(int flow_id,
@@ -1859,9 +1878,11 @@ int ipcp_flow_req_arr(const buffer_t * dst,
const buffer_t * data)
{
struct flow_info flow;
- uint8_t buf[SOCK_BUF_SIZE];
- buffer_t msg = {SOCK_BUF_SIZE, buf};
- int err;
+ uint8_t buf[SOCK_BUF_SIZE];
+ buffer_t msg = {SOCK_BUF_SIZE, buf};
+ struct crypt_sk crypt;
+ uint8_t key[SYMMKEYSZ];
+ int err;
memset(&flow, 0, sizeof(flow));
@@ -1878,17 +1899,23 @@ int ipcp_flow_req_arr(const buffer_t * dst,
if (err < 0)
return err;
- err = flow__irm_result_des(&msg, &flow, NULL);
+ crypt.key = key;
+
+ err = flow__irm_result_des(&msg, &flow, &crypt);
if (err < 0)
return err;
+ assert(crypt.nid == NID_undef); /* np1 flows are not encrypted */
+
/* inverted for np1_flow */
flow.n_1_pid = flow.n_pid;
flow.n_pid = getpid();
flow.mpl = 0;
flow.qs = qos_np1;
- return flow_init(&flow, NULL);
+ crypt.nid = NID_undef;
+
+ return flow_init(&flow, &crypt);
}
int ipcp_flow_alloc_reply(int fd,
diff --git a/src/lib/pb/irm.proto b/src/lib/pb/irm.proto
index 75f5f350..5d0ee611 100644
--- a/src/lib/pb/irm.proto
+++ b/src/lib/pb/irm.proto
@@ -91,8 +91,9 @@ message irm_msg {
optional sint32 mpl = 20;
optional string comp = 21;
optional bytes pk = 22; /* piggyback */
- optional bytes symmkey = 23;
- optional uint32 timeo_sec = 24;
- optional uint32 timeo_nsec = 25;
- optional sint32 result = 26;
+ optional uint32 timeo_sec = 23;
+ optional uint32 timeo_nsec = 24;
+ optional sint32 result = 25;
+ optional bytes sym_key = 26; /* symmetric encryption key */
+ optional sint32 cipher_nid = 27; /* cipher NID */
}
diff --git a/src/lib/protobuf.c b/src/lib/protobuf.c
index 6df4e810..bd6c179e 100644
--- a/src/lib/protobuf.c
+++ b/src/lib/protobuf.c
@@ -23,6 +23,7 @@
#define _DEFAULT_SOURCE
#include <ouroboros/protobuf.h>
+#include <ouroboros/crypt.h>
#include <stdlib.h>
#include <string.h>
@@ -96,6 +97,8 @@ struct flow_info flow_info_msg_to_s(const flow_info_msg_t * msg)
assert(msg != NULL);
+ memset(&s, 0, sizeof(s));
+
s.id = msg->id;
s.n_pid = msg->n_pid;
s.n_1_pid = msg->n_1_pid;
diff --git a/src/lib/serdes-irm.c b/src/lib/serdes-irm.c
index 3aea0617..a0fdbec2 100644
--- a/src/lib/serdes-irm.c
+++ b/src/lib/serdes-irm.c
@@ -24,6 +24,7 @@
#include "config.h"
+#include <ouroboros/crypt.h>
#include <ouroboros/errno.h>
#include <ouroboros/serdes-irm.h>
#include <ouroboros/protobuf.h>
@@ -133,16 +134,13 @@ int flow_join__irm_req_ser(buffer_t * buf,
IRM_MSG_CODE__IRM_FLOW_JOIN);
}
-int flow__irm_result_des(buffer_t * buf,
- struct flow_info * flow,
- buffer_t * sk)
+int flow__irm_result_des(buffer_t * buf,
+ struct flow_info * flow,
+ struct crypt_sk * sk)
{
irm_msg_t * msg;
int err;
- if (sk != NULL)
- sk->data = NULL;
-
msg = irm_msg__unpack(NULL, buf->len, buf->data);
if (msg == NULL) {
err = -EIRMD;
@@ -166,13 +164,15 @@ int flow__irm_result_des(buffer_t * buf,
*flow = flow_info_msg_to_s(msg->flow_info);
- if (sk != NULL) {
- sk->len = msg->symmkey.len;
- sk->data = msg->symmkey.data;
+ if (msg->has_cipher_nid)
+ sk->nid = msg->cipher_nid;
+ else
+ sk->nid = NID_undef;
- msg->symmkey.data = NULL;
- msg->symmkey.len = 0;
- }
+ if (msg->sym_key.len == SYMMKEYSZ)
+ memcpy(sk->key, msg->sym_key.data, SYMMKEYSZ);
+ else
+ memset(sk->key, 0, SYMMKEYSZ);
irm_msg__free_unpacked(msg, NULL);
diff --git a/src/lib/tests/CMakeLists.txt b/src/lib/tests/CMakeLists.txt
index 69fdf18b..6ab69bd1 100644
--- a/src/lib/tests/CMakeLists.txt
+++ b/src/lib/tests/CMakeLists.txt
@@ -1,14 +1,19 @@
get_filename_component(PARENT_PATH ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY)
get_filename_component(PARENT_DIR ${PARENT_PATH} NAME)
+compute_test_prefix()
+
create_test_sourcelist(${PARENT_DIR}_tests test_suite.c
# Add new tests here
auth_test.c
+ auth_test_pqc.c
bitmap_test.c
btree_test.c
crc32_test.c
crypt_test.c
hash_test.c
+ kex_test.c
+ kex_test_pqc.c
md5_test.c
sha3_test.c
shm_rbuff_test.c
@@ -33,8 +38,11 @@ endif()
foreach (test ${tests_to_run})
get_filename_component(test_name ${test} NAME_WE)
- add_test(${test_name} ${CMAKE_CURRENT_BINARY_DIR}/${PARENT_DIR}_test ${test_name})
+ add_test(${TEST_PREFIX}/${test_name} ${CMAKE_CURRENT_BINARY_DIR}/${PARENT_DIR}_test ${test_name})
endforeach (test)
-set_property(TEST auth_test PROPERTY SKIP_RETURN_CODE 1)
-set_property(TEST crypt_test PROPERTY SKIP_RETURN_CODE 1)
+set_property(TEST ${TEST_PREFIX}/auth_test PROPERTY SKIP_RETURN_CODE 1)
+set_property(TEST ${TEST_PREFIX}/auth_test_pqc PROPERTY SKIP_RETURN_CODE 1)
+set_property(TEST ${TEST_PREFIX}/crypt_test PROPERTY SKIP_RETURN_CODE 1)
+set_property(TEST ${TEST_PREFIX}/kex_test PROPERTY SKIP_RETURN_CODE 1)
+set_property(TEST ${TEST_PREFIX}/kex_test_pqc PROPERTY SKIP_RETURN_CODE 1)
diff --git a/src/lib/tests/auth_test.c b/src/lib/tests/auth_test.c
index 896c42b0..16c88f31 100644
--- a/src/lib/tests/auth_test.c
+++ b/src/lib/tests/auth_test.c
@@ -22,111 +22,14 @@
#include "config.h"
-#include <ouroboros/test.h>
+#include <test/test.h>
#include <ouroboros/crypt.h>
#include <ouroboros/random.h>
#include <ouroboros/utils.h>
-#define TEST_MSG_SIZE 1500
+#include <test/certs.h>
-/*
-* Certificates created following the guide
-* Building an openssl certificate authority
-* on
-* https://community.f5.com/kb/technicalarticles/
-*/
-
-/* Root certificate for CA ca.unittest.o7s */
-static const char * root_ca_crt = \
-"-----BEGIN CERTIFICATE-----\n"
-"MIICXTCCAgOgAwIBAgIURlENlCOy1OsA/AXFscPUQ2li8OYwCgYIKoZIzj0EAwIw\n"
-"fDELMAkGA1UEBhMCQkUxDDAKBgNVBAgMA09WTDEOMAwGA1UEBwwFR2hlbnQxDDAK\n"
-"BgNVBAoMA283czEVMBMGA1UECwwMdW5pdHRlc3QubzdzMRgwFgYDVQQDDA9jYS51\n"
-"bml0dGVzdC5vN3MxEDAOBgkqhkiG9w0BCQEWASAwHhcNMjUwODAzMTg1MzE1WhcN\n"
-"NDUwNzI5MTg1MzE1WjB8MQswCQYDVQQGEwJCRTEMMAoGA1UECAwDT1ZMMQ4wDAYD\n"
-"VQQHDAVHaGVudDEMMAoGA1UECgwDbzdzMRUwEwYDVQQLDAx1bml0dGVzdC5vN3Mx\n"
-"GDAWBgNVBAMMD2NhLnVuaXR0ZXN0Lm83czEQMA4GCSqGSIb3DQEJARYBIDBZMBMG\n"
-"ByqGSM49AgEGCCqGSM49AwEHA0IABEPMseCScbd/d5TlHmyYVszn/YGVeNdUCnFR\n"
-"naOr95WlTNo3MyKKBuoiEFwHhjPASgXr/VDVjJLSyM3JUPebAcGjYzBhMB0GA1Ud\n"
-"DgQWBBQkxjMILHH6lZ+rnCMnD/63GO3y1zAfBgNVHSMEGDAWgBQkxjMILHH6lZ+r\n"
-"nCMnD/63GO3y1zAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAKBggq\n"
-"hkjOPQQDAgNIADBFAiEA1jVJWW4idkCgAYv0m2LT9C33Dq42aLyRkJ+9YdzDqLwC\n"
-"IHT6MS4I0k52YP/hxoqWVBbpOW79PKYMRLyXTk1r7+Fa\n"
-"-----END CERTIFICATE-----\n";
-
-
-/* Certificate for intermediary im.unittest.o7s used for signing */
-static const char * intermediate_ca_crt = \
-"-----BEGIN CERTIFICATE-----\n"
-"MIICbTCCAhOgAwIBAgICEAMwCgYIKoZIzj0EAwIwfDELMAkGA1UEBhMCQkUxDDAK\n"
-"BgNVBAgMA09WTDEOMAwGA1UEBwwFR2hlbnQxDDAKBgNVBAoMA283czEVMBMGA1UE\n"
-"CwwMdW5pdHRlc3QubzdzMRgwFgYDVQQDDA9jYS51bml0dGVzdC5vN3MxEDAOBgkq\n"
-"hkiG9w0BCQEWASAwHhcNMjUwODAzMTkwMjU3WhcNNDUwNzI5MTkwMjU3WjBaMQsw\n"
-"CQYDVQQGEwJCRTEMMAoGA1UECAwDT1ZMMQwwCgYDVQQKDANvN3MxFTATBgNVBAsM\n"
-"DHVuaXR0ZXN0Lm83czEYMBYGA1UEAwwPaW0udW5pdHRlc3QubzdzMFkwEwYHKoZI\n"
-"zj0CAQYIKoZIzj0DAQcDQgAEdlra08XItIPtVl5veaq4UF6LIcBXj2mZFqKNEXFh\n"
-"l9uAz6UAbIc+FUPNfom6dwKbg/AjQ82a100eh6K/jCY7eKOBpjCBozAdBgNVHQ4E\n"
-"FgQUy8Go8BIO6i0lJ+mgBr9lvh2L0eswHwYDVR0jBBgwFoAUJMYzCCxx+pWfq5wj\n"
-"Jw/+txjt8tcwEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwEQYD\n"
-"VR0fBAowCDAGoASgAoYAMCoGCCsGAQUFBwEBBB4wHDAMBggrBgEFBQcwAoYAMAwG\n"
-"CCsGAQUFBzABhgAwCgYIKoZIzj0EAwIDSAAwRQIhAN3ZYhqu6mVLGidmONsbANk5\n"
-"rzT6aHJcmvj19OxMusaXAiBKy0gBFCri/GLizi4wZo09wf31yZMqfr8IrApvPaLw\n"
-"qA==\n"
-"-----END CERTIFICATE-----\n";
-
-/* Server test-1.unittest.o7s private-public key pair */
-static const char * server_ec_pkp = \
-"-----BEGIN EC PRIVATE KEY-----\n"
-"MHcCAQEEIA4/bcmquVvGrY4+TtfnFSy1SpXs896r5xJjGuD6NmGRoAoGCCqGSM49\n"
-"AwEHoUQDQgAE4BSOhv36q4bCMLSkJaCvzwZ3pPy2M0YzRKFKeV48tG5eD+MBaTrT\n"
-"eoHUcRfpz0EO/inq3FVDzEoAQ2NWpnz0kA==\n"
-"-----END EC PRIVATE KEY-----\n";
-
-/* Public key for the Private key */
-static const char * server_ec_pk = \
-"-----BEGIN PUBLIC KEY-----\n"
-"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE4BSOhv36q4bCMLSkJaCvzwZ3pPy2\n"
-"M0YzRKFKeV48tG5eD+MBaTrTeoHUcRfpz0EO/inq3FVDzEoAQ2NWpnz0kA==\n"
-"-----END PUBLIC KEY-----\n";
-
-/* Valid signed server certificate for test-1.unittest.o7s */
-#define SSC_TEXT_SIZE 2295 /* size of cleartext certificate */
-static const char * signed_server_crt = \
-"-----BEGIN CERTIFICATE-----\n"
-"MIIDiTCCAy+gAwIBAgICEAUwCgYIKoZIzj0EAwIwWjELMAkGA1UEBhMCQkUxDDAK\n"
-"BgNVBAgMA09WTDEMMAoGA1UECgwDbzdzMRUwEwYDVQQLDAx1bml0dGVzdC5vN3Mx\n"
-"GDAWBgNVBAMMD2ltLnVuaXR0ZXN0Lm83czAeFw0yNTA4MDgxODQ4NTNaFw00NTA4\n"
-"MDMxODQ4NTNaMG4xCzAJBgNVBAYTAkJFMQwwCgYDVQQIDANPVkwxDjAMBgNVBAcM\n"
-"BUdoZW50MQwwCgYDVQQKDANvN3MxFTATBgNVBAsMDHVuaXR0ZXN0Lm83czEcMBoG\n"
-"A1UEAwwTdGVzdC0xLnVuaXR0ZXN0Lm83czBZMBMGByqGSM49AgEGCCqGSM49AwEH\n"
-"A0IABOAUjob9+quGwjC0pCWgr88Gd6T8tjNGM0ShSnlePLRuXg/jAWk603qB1HEX\n"
-"6c9BDv4p6txVQ8xKAENjVqZ89JCjggHPMIIByzAJBgNVHRMEAjAAMBEGCWCGSAGG\n"
-"+EIBAQQEAwIGQDA4BglghkgBhvhCAQ0EKxYpbzdzIHVuaXR0ZXN0IEdlbmVyYXRl\n"
-"ZCBTZXJ2ZXIgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFI+htsK0xxy6e1CqCyxn7mqi\n"
-"wRrpMIGoBgNVHSMEgaAwgZ2AFMvBqPASDuotJSfpoAa/Zb4di9HroYGApH4wfDEL\n"
-"MAkGA1UEBhMCQkUxDDAKBgNVBAgMA09WTDEOMAwGA1UEBwwFR2hlbnQxDDAKBgNV\n"
-"BAoMA283czEVMBMGA1UECwwMdW5pdHRlc3QubzdzMRgwFgYDVQQDDA9jYS51bml0\n"
-"dGVzdC5vN3MxEDAOBgkqhkiG9w0BCQEWASCCAhADMA4GA1UdDwEB/wQEAwIFoDAT\n"
-"BgNVHSUEDDAKBggrBgEFBQcDATAoBgNVHR8EITAfMB2gG6AZhhdodHRwczovL291\n"
-"cm9ib3Jvcy5yb2NrczBYBggrBgEFBQcBAQRMMEowIwYIKwYBBQUHMAKGF2h0dHBz\n"
-"Oi8vb3Vyb2Jvcm9zLnJvY2tzMCMGCCsGAQUFBzABhhdodHRwczovL291cm9ib3Jv\n"
-"cy5yb2NrczAKBggqhkjOPQQDAgNIADBFAiBZuw/Yb2pq925H7pEiOXr4fMo0wknz\n"
-"ktkxoHAFbjQEPQIhAMInHI7lvRmS0IMw1wBF/WlUZWKvhyU/TeMIZfk/JGCS\n"
-"-----END CERTIFICATE-----\n";
-
-/* Self-signed by server test-1.unittest.o7s using its key */
-static const char * server_crt = \
-"-----BEGIN CERTIFICATE-----\n"
-"MIIBfjCCASWgAwIBAgIUB5VYxp7i+sgYjvLiwfpf0W5NfqQwCgYIKoZIzj0EAwIw\n"
-"HjEcMBoGA1UEAwwTdGVzdC0xLnVuaXR0ZXN0Lm83czAeFw0yNTA4MDMxOTI4MzVa\n"
-"Fw00NTA3MjkxOTI4MzVaMB4xHDAaBgNVBAMME3Rlc3QtMS51bml0dGVzdC5vN3Mw\n"
-"WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATgFI6G/fqrhsIwtKQloK/PBnek/LYz\n"
-"RjNEoUp5Xjy0bl4P4wFpOtN6gdRxF+nPQQ7+KercVUPMSgBDY1amfPSQo0EwPzAe\n"
-"BgNVHREEFzAVghN0ZXN0LTEudW5pdHRlc3QubzdzMB0GA1UdDgQWBBSPobbCtMcc\n"
-"untQqgssZ+5qosEa6TAKBggqhkjOPQQDAgNHADBEAiAoFC/rqgrRXmMUx4y5cPbv\n"
-"jOKpoL3FpehRgGkPatmL/QIgMRHc2TSGo6q1SG22Xt1dHAIBsaN2AlSfhjKULMH5\n"
-"gRo=\n"
-"-----END CERTIFICATE-----\n";
+#define TEST_MSG_SIZE 1500
static int test_auth_create_destroy_ctx(void)
{
@@ -156,7 +59,7 @@ static int test_load_free_crt(void)
TEST_START();
- if (crypt_load_crt_str(root_ca_crt, &crt) < 0) {
+ if (crypt_load_crt_str(root_ca_crt_ec, &crt) < 0) {
printf("Failed to load certificate string.\n");
goto fail_load;
}
@@ -178,7 +81,7 @@ static int test_crypt_get_pubkey_crt(void)
TEST_START();
- if (crypt_load_crt_str(signed_server_crt, &crt) < 0) {
+ if (crypt_load_crt_str(signed_server_crt_ec, &crt) < 0) {
printf("Failed to load server certificate from string.\n");
goto fail_load;
}
@@ -208,7 +111,7 @@ static int test_check_crt_name(void)
TEST_START();
- if (crypt_load_crt_str(signed_server_crt, &crt) < 0) {
+ if (crypt_load_crt_str(signed_server_crt_ec, &crt) < 0) {
printf("Failed to load certificate from string.\n");
goto fail_load;
}
@@ -241,7 +144,7 @@ static int test_load_free_privkey(void)
TEST_START();
- if (crypt_load_privkey_str(server_ec_pkp, &key) < 0) {
+ if (crypt_load_privkey_str(server_pkp_ec, &key) < 0) {
printf("Failed to load server key pair from string.\n");
goto fail_load;
}
@@ -262,7 +165,7 @@ static int test_load_free_pubkey(void)
TEST_START();
- if (crypt_load_pubkey_str(server_ec_pk, &key) < 0) {
+ if (crypt_load_pubkey_str(server_pk_ec, &key) < 0) {
printf("Failed to load server public key from string.\n");
goto fail_load;
}
@@ -285,12 +188,12 @@ static int test_crypt_check_pubkey_crt(void)
TEST_START();
- if (crypt_load_crt_str(signed_server_crt, &crt) < 0) {
+ if (crypt_load_crt_str(signed_server_crt_ec, &crt) < 0) {
printf("Failed to load public certificate from string.\n");
goto fail_crt;
}
- if (crypt_load_pubkey_str(server_ec_pk, &pk) < 0) {
+ if (crypt_load_pubkey_str(server_pk_ec, &pk) < 0) {
printf("Failed to load public key from string.\n");
goto fail_pubkey;
}
@@ -337,7 +240,7 @@ static int test_store_add(void)
goto fail_create;
}
- if (crypt_load_crt_str(root_ca_crt, &_root_ca_crt) < 0) {
+ if (crypt_load_crt_str(root_ca_crt_ec, &_root_ca_crt) < 0) {
printf("Failed to load root crt from string.\n");
goto fail_load;
}
@@ -369,7 +272,7 @@ static int test_verify_crt(void)
void * _server_crt;
void * _signed_server_crt;
void * _root_ca_crt;
- void * _intermediate_ca_crt;
+ void * _im_ca_crt;
TEST_START();
@@ -379,24 +282,24 @@ static int test_verify_crt(void)
goto fail_create_ctx;
}
- if (crypt_load_crt_str(server_crt, &_server_crt) < 0) {
+ if (crypt_load_crt_str(server_crt_ec, &_server_crt) < 0) {
printf("Failed to load self-signed crt from string.\n");
goto fail_load_server_crt;
}
- if (crypt_load_crt_str(signed_server_crt, &_signed_server_crt) < 0) {
+ if (crypt_load_crt_str(signed_server_crt_ec, &_signed_server_crt) < 0) {
printf("Failed to load signed crt from string.\n");
goto fail_load_signed_server_crt;
}
- if (crypt_load_crt_str(root_ca_crt, &_root_ca_crt) < 0) {
+ if (crypt_load_crt_str(root_ca_crt_ec, &_root_ca_crt) < 0) {
printf("Failed to load root crt from string.\n");
goto fail_load_root_ca_crt;
}
- if (crypt_load_crt_str(intermediate_ca_crt, &_intermediate_ca_crt) < 0) {
+ if (crypt_load_crt_str(im_ca_crt_ec, &_im_ca_crt) < 0) {
printf("Failed to load intermediate crt from string.\n");
- goto fail_load_intermediate_ca_crt;
+ goto fail_load_im_ca_crt;
}
if (auth_add_crt_to_store(auth, _root_ca_crt) < 0) {
@@ -404,7 +307,7 @@ static int test_verify_crt(void)
goto fail_verify;
}
- if (auth_add_crt_to_store(auth, _intermediate_ca_crt) < 0) {
+ if (auth_add_crt_to_store(auth, _im_ca_crt) < 0) {
printf("Failed to add intermediate ca crt to auth store.\n");
goto fail_verify;
}
@@ -419,7 +322,7 @@ static int test_verify_crt(void)
goto fail_verify;
}
- crypt_free_crt(_intermediate_ca_crt);
+ crypt_free_crt(_im_ca_crt);
crypt_free_crt(_root_ca_crt);
crypt_free_crt(_signed_server_crt);
crypt_free_crt(_server_crt);
@@ -430,8 +333,8 @@ static int test_verify_crt(void)
return TEST_RC_SUCCESS;
fail_verify:
- crypt_free_crt(_intermediate_ca_crt);
- fail_load_intermediate_ca_crt:
+ crypt_free_crt(_im_ca_crt);
+ fail_load_im_ca_crt:
crypt_free_crt(_root_ca_crt);
fail_load_root_ca_crt:
crypt_free_crt(_signed_server_crt);
@@ -462,22 +365,22 @@ int test_auth_sign(void)
goto fail_init;
}
- if (crypt_load_privkey_str(server_ec_pkp, &pkp) < 0) {
+ if (crypt_load_privkey_str(server_pkp_ec, &pkp) < 0) {
printf("Failed to load server key pair from string.\n");
goto fail_init;
}
- if (crypt_load_pubkey_str(server_ec_pk, &pk) < 0) {
+ if (crypt_load_pubkey_str(server_pk_ec, &pk) < 0) {
printf("Failed to load public key.\n");
goto fail_pubkey;
}
- if (auth_sign(pkp, msg, &sig) < 0) {
+ if (auth_sign(pkp, 0, msg, &sig) < 0) {
printf("Failed to sign message.\n");
goto fail_sign;
}
- if (auth_verify_sig(pk, msg, sig) < 0) {
+ if (auth_verify_sig(pk, 0, msg, sig) < 0) {
printf("Failed to verify signature.\n");
goto fail_verify;
}
@@ -519,17 +422,17 @@ int test_auth_bad_signature(void)
goto fail_init;
}
- if (crypt_load_privkey_str(server_ec_pkp, &pkp) < 0) {
+ if (crypt_load_privkey_str(server_pkp_ec, &pkp) < 0) {
printf("Failed to load server key pair from string.\n");
goto fail_init;
}
- if (crypt_load_pubkey_str(server_ec_pk, &pk) < 0) {
+ if (crypt_load_pubkey_str(server_pk_ec, &pk) < 0) {
printf("Failed to load public key.\n");
goto fail_pubkey;
}
- if (auth_sign(pkp, msg, &sig) < 0) {
+ if (auth_sign(pkp, 0, msg, &sig) < 0) {
printf("Failed to sign message.\n");
goto fail_sign;
}
@@ -546,7 +449,7 @@ int test_auth_bad_signature(void)
goto fail_malloc;
}
- if (auth_verify_sig(pk, msg, fake_sig) == 0) {
+ if (auth_verify_sig(pk, 0, msg, fake_sig) == 0) {
printf("Failed to detect bad signature.\n");
goto fail_verify;
}
@@ -579,7 +482,7 @@ int test_crt_str(void)
TEST_START();
- if (crypt_load_crt_str(signed_server_crt, &crt) < 0) {
+ if (crypt_load_crt_str(signed_server_crt_ec, &crt) < 0) {
printf("Failed to load certificate from string.\n");
goto fail_load;
}
diff --git a/src/lib/tests/auth_test_pqc.c b/src/lib/tests/auth_test_pqc.c
new file mode 100644
index 00000000..349636d2
--- /dev/null
+++ b/src/lib/tests/auth_test_pqc.c
@@ -0,0 +1,356 @@
+/*
+ * Ouroboros - Copyright (C) 2016 - 2026
+ *
+ * Test of the PQC authentication functions (ML-DSA-65)
+ *
+ * Dimitri Staessens <dimitri@ouroboros.rocks>
+ * Sander Vrijders <sander@ouroboros.rocks>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., http://www.fsf.org/about/contact/.
+ */
+
+#include "config.h"
+
+#include <test/test.h>
+#include <ouroboros/crypt.h>
+#include <ouroboros/random.h>
+#include <ouroboros/utils.h>
+
+#include <test/certs_pqc.h>
+
+#define TEST_MSG_SIZE 1500
+
+static int test_auth_create_destroy_ctx(void)
+{
+ struct auth_ctx * ctx;
+
+ TEST_START();
+
+ ctx = auth_create_ctx();
+ if (ctx == NULL) {
+ printf("Failed to create auth context.\n");
+ goto fail_create;
+ }
+
+ auth_destroy_ctx(ctx);
+
+ TEST_SUCCESS();
+
+ return TEST_RC_SUCCESS;
+ fail_create:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_load_free_crt(void)
+{
+ void * crt;
+
+ TEST_START();
+
+ if (crypt_load_crt_str(root_ca_crt_ml, &crt) < 0) {
+ printf("Failed to load root crt from string.\n");
+ goto fail_load;
+ }
+
+ crypt_free_crt(crt);
+
+ TEST_SUCCESS();
+
+ return TEST_RC_SUCCESS;
+ fail_load:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_load_free_privkey(void)
+{
+ void * key;
+
+ TEST_START();
+
+ if (crypt_load_privkey_str(server_pkp_ml, &key) < 0) {
+ printf("Failed to load server key pair from string.\n");
+ goto fail_load;
+ }
+
+ crypt_free_key(key);
+
+ TEST_SUCCESS();
+
+ return TEST_RC_SUCCESS;
+ fail_load:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_load_free_pubkey(void)
+{
+ void * key;
+
+ TEST_START();
+
+ if (crypt_load_pubkey_str(server_pk_ml, &key) < 0) {
+ printf("Failed to load server public key from string.\n");
+ goto fail_load;
+ }
+
+ crypt_free_key(key);
+
+ TEST_SUCCESS();
+
+ return TEST_RC_SUCCESS;
+ fail_load:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_verify_crt(void)
+{
+ struct auth_ctx * auth;
+ void * _server_crt;
+ void * _signed_server_crt;
+ void * _root_ca_crt;
+ void * _im_ca_crt;
+
+ TEST_START();
+
+ auth = auth_create_ctx();
+ if (auth == NULL) {
+ printf("Failed to create auth context.\n");
+ goto fail_create_ctx;
+ }
+
+ if (crypt_load_crt_str(server_crt_ml, &_server_crt) < 0) {
+ printf("Failed to load self-signed crt from string.\n");
+ goto fail_load_server_crt;
+ }
+
+ if (crypt_load_crt_str(signed_server_crt_ml, &_signed_server_crt) < 0) {
+ printf("Failed to load signed crt from string.\n");
+ goto fail_load_signed_server_crt;
+ }
+
+ if (crypt_load_crt_str(root_ca_crt_ml, &_root_ca_crt) < 0) {
+ printf("Failed to load root crt from string.\n");
+ goto fail_load_root_ca_crt;
+ }
+
+ if (crypt_load_crt_str(im_ca_crt_ml, &_im_ca_crt) < 0) {
+ printf("Failed to load intermediate crt from string.\n");
+ goto fail_load_im_ca_crt;
+ }
+
+ if (auth_add_crt_to_store(auth, _root_ca_crt) < 0) {
+ printf("Failed to add root ca crt to auth store.\n");
+ goto fail_verify;
+ }
+
+ if (auth_add_crt_to_store(auth, _im_ca_crt) < 0) {
+ printf("Failed to add intermediate ca crt to auth store.\n");
+ goto fail_verify;
+ }
+
+ if (auth_verify_crt(auth, _signed_server_crt) < 0) {
+ printf("Failed to verify signed crt with ca crt.\n");
+ goto fail_verify;
+ }
+
+ if (auth_verify_crt(auth, _server_crt) == 0) {
+ printf("Failed to detect untrusted crt.\n");
+ goto fail_verify;
+ }
+
+ crypt_free_crt(_im_ca_crt);
+ crypt_free_crt(_root_ca_crt);
+ crypt_free_crt(_signed_server_crt);
+ crypt_free_crt(_server_crt);
+
+ auth_destroy_ctx(auth);
+
+ TEST_SUCCESS();
+
+ return TEST_RC_SUCCESS;
+ fail_verify:
+ crypt_free_crt(_im_ca_crt);
+ fail_load_im_ca_crt:
+ crypt_free_crt(_root_ca_crt);
+ fail_load_root_ca_crt:
+ crypt_free_crt(_signed_server_crt);
+ fail_load_signed_server_crt:
+ crypt_free_crt(_server_crt);
+ fail_load_server_crt:
+ auth_destroy_ctx(auth);
+ fail_create_ctx:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_auth_sign(void)
+{
+ uint8_t buf[TEST_MSG_SIZE];
+ void * pkp;
+ void * pk;
+ buffer_t msg;
+ buffer_t sig;
+
+ TEST_START();
+
+ msg.data = buf;
+ msg.len = sizeof(buf);
+
+ if (random_buffer(msg.data, msg.len) < 0) {
+ printf("Failed to generate random message.\n");
+ goto fail_init;
+ }
+
+ if (crypt_load_privkey_str(server_pkp_ml, &pkp) < 0) {
+ printf("Failed to load server key pair from string.\n");
+ goto fail_init;
+ }
+
+ if (crypt_load_pubkey_str(server_pk_ml, &pk) < 0) {
+ printf("Failed to load public key from string.\n");
+ goto fail_pubkey;
+ }
+
+ if (auth_sign(pkp, 0, msg, &sig) < 0) {
+ printf("Failed to sign message.\n");
+ goto fail_sign;
+ }
+
+ if (auth_verify_sig(pk, 0, msg, sig) < 0) {
+ printf("Failed to verify signature.\n");
+ goto fail_verify;
+ }
+
+ freebuf(sig);
+
+ crypt_free_key(pk);
+ crypt_free_key(pkp);
+
+ TEST_SUCCESS();
+
+ return TEST_RC_SUCCESS;
+ fail_verify:
+ freebuf(sig);
+ fail_sign:
+ crypt_free_key(pk);
+ fail_pubkey:
+ crypt_free_key(pkp);
+ fail_init:
+ return TEST_RC_FAIL;
+}
+
+static int test_auth_bad_signature(void)
+{
+ uint8_t buf[TEST_MSG_SIZE];
+ void * pkp;
+ void * pk;
+ buffer_t msg;
+ buffer_t sig;
+ buffer_t fake_sig;
+
+ TEST_START();
+
+ msg.data = buf;
+ msg.len = sizeof(buf);
+
+ if (random_buffer(msg.data, msg.len) < 0) {
+ printf("Failed to generate random message.\n");
+ goto fail_init;
+ }
+
+ if (crypt_load_privkey_str(server_pkp_ml, &pkp) < 0) {
+ printf("Failed to load server key pair from string.\n");
+ goto fail_init;
+ }
+
+ if (crypt_load_pubkey_str(server_pk_ml, &pk) < 0) {
+ printf("Failed to load public key from string.\n");
+ goto fail_pubkey;
+ }
+
+ if (auth_sign(pkp, 0, msg, &sig) < 0) {
+ printf("Failed to sign message.\n");
+ goto fail_sign;
+ }
+
+ fake_sig.data = malloc(sig.len);
+ if (fake_sig.data == NULL) {
+ printf("Failed to allocate memory for fake signature.\n");
+ goto fail_malloc;
+ }
+
+ fake_sig.len = sig.len;
+ if (random_buffer(fake_sig.data, fake_sig.len) < 0) {
+ printf("Failed to generate random fake signature.\n");
+ goto fail_malloc;
+ }
+
+ if (auth_verify_sig(pk, 0, msg, fake_sig) == 0) {
+ printf("Failed to detect bad ML-DSA-65 signature.\n");
+ goto fail_verify;
+ }
+
+ freebuf(fake_sig);
+ freebuf(sig);
+
+ crypt_free_key(pk);
+ crypt_free_key(pkp);
+
+ TEST_SUCCESS();
+
+ return TEST_RC_SUCCESS;
+ fail_verify:
+ freebuf(fake_sig);
+ fail_malloc:
+ freebuf(sig);
+ fail_sign:
+ crypt_free_key(pk);
+ fail_pubkey:
+ crypt_free_key(pkp);
+ fail_init:
+ return TEST_RC_FAIL;
+}
+
+int auth_test_pqc(int argc,
+ char ** argv)
+{
+ int ret = 0;
+
+ (void) argc;
+ (void) argv;
+
+#ifdef HAVE_OPENSSL_PQC
+ ret |= test_auth_create_destroy_ctx();
+ ret |= test_load_free_crt();
+ ret |= test_load_free_privkey();
+ ret |= test_load_free_pubkey();
+ ret |= test_verify_crt();
+ ret |= test_auth_sign();
+ ret |= test_auth_bad_signature();
+#else
+ (void) test_auth_create_destroy_ctx;
+ (void) test_load_free_crt;
+ (void) test_load_free_privkey;
+ (void) test_load_free_pubkey;
+ (void) test_verify_crt;
+ (void) test_auth_sign;
+ (void) test_auth_bad_signature;
+
+ ret = TEST_RC_SKIP;
+#endif
+ return ret;
+}
diff --git a/src/lib/tests/crypt_test.c b/src/lib/tests/crypt_test.c
index e7a09e8f..906059be 100644
--- a/src/lib/tests/crypt_test.c
+++ b/src/lib/tests/crypt_test.c
@@ -22,45 +22,32 @@
#include "config.h"
-#include <ouroboros/test.h>
-#include <ouroboros/crypt.h>
+#include <test/test.h>
#include <ouroboros/random.h>
+#include <ouroboros/crypt.h>
#include <ouroboros/utils.h>
-#define TEST_PACKET_SIZE 1500
-
-static int test_crypt_create_destroy(void)
-{
- struct crypt_ctx * ctx;
-
- TEST_START();
-
- ctx = crypt_create_ctx(NULL);
- if (ctx == NULL) {
- printf("Failed to initialize cryptography.\n");
- goto fail;
- }
-
- crypt_destroy_ctx(ctx);
+#include <stdio.h>
- TEST_SUCCESS();
+#define TEST_PACKET_SIZE 1500
- return TEST_RC_SUCCESS;
- fail:
- TEST_FAIL();
- return TEST_RC_FAIL;
-}
+extern const uint16_t crypt_supported_nids[];
+extern const uint16_t md_supported_nids[];
-static int test_crypt_create_destroy_with_key(void)
+static int test_crypt_create_destroy(void)
{
struct crypt_ctx * ctx;
uint8_t key[SYMMKEYSZ];
+ struct crypt_sk sk = {
+ .nid = NID_aes_256_gcm,
+ .key = key
+ };
TEST_START();
memset(key, 0, sizeof(key));
- ctx = crypt_create_ctx(key);
+ ctx = crypt_create_ctx(&sk);
if (ctx == NULL) {
printf("Failed to initialize cryptography.\n");
goto fail;
@@ -76,100 +63,22 @@ static int test_crypt_create_destroy_with_key(void)
return TEST_RC_FAIL;
}
-static int test_crypt_dh_pkp_create_destroy(void)
-{
- void * pkp;
- uint8_t buf[MSGBUFSZ];
-
- TEST_START();
-
- if (crypt_dh_pkp_create(&pkp, buf) < 0) {
- printf("Failed to create DH PKP.");
- goto fail;
- }
-
- crypt_dh_pkp_destroy(pkp);
-
- TEST_SUCCESS();
-
- return TEST_RC_SUCCESS;
- fail:
- TEST_FAIL();
- return TEST_RC_FAIL;
-}
-
-static int test_crypt_dh_derive(void)
-{
- void * pkp1;
- void * pkp2;
- buffer_t pk1;
- buffer_t pk2;
- ssize_t len;
- uint8_t buf1[MSGBUFSZ];
- uint8_t buf2[MSGBUFSZ];
- uint8_t s1[SYMMKEYSZ];
- uint8_t s2[SYMMKEYSZ];
-
- TEST_START();
-
- len = crypt_dh_pkp_create(&pkp1, buf1);
- if (len < 0) {
- printf("Failed to create first key pair.");
- goto fail_pkp1;
- }
-
- pk1.len = (size_t) len;
- pk1.data = buf1;
-
- len = crypt_dh_pkp_create(&pkp2, buf2);
- if (len < 0) {
- printf("Failed to create second key pair.");
- goto fail_pkp2;
- }
-
- pk2.len = (size_t) len;
- pk2.data = buf2;
-
- if (crypt_dh_derive(pkp1, pk2, s1) < 0) {
- printf("Failed to derive first key.");
- goto fail;
- }
-
- if (crypt_dh_derive(pkp2, pk1, s2) < 0) {
- printf("Failed to derive second key.");
- goto fail;
- }
-
- if (memcmp(s1, s2, SYMMKEYSZ) != 0) {
- printf("Derived keys do not match.");
- goto fail;
- }
-
- crypt_dh_pkp_destroy(pkp2);
- crypt_dh_pkp_destroy(pkp1);
-
- TEST_SUCCESS();
-
- return TEST_RC_SUCCESS;
- fail:
- crypt_dh_pkp_destroy(pkp2);
- fail_pkp2:
- crypt_dh_pkp_destroy(pkp1);
- fail_pkp1:
- TEST_FAIL();
- return TEST_RC_FAIL;
-}
-
-int test_crypt_encrypt_decrypt(void)
+static int test_crypt_encrypt_decrypt(int nid)
{
uint8_t pkt[TEST_PACKET_SIZE];
- uint8_t key[SYMMKEYSZ];
struct crypt_ctx * ctx;
+ uint8_t key[SYMMKEYSZ];
+ struct crypt_sk sk = {
+ .nid = NID_aes_256_gcm,
+ .key = key
+ };
buffer_t in;
buffer_t out;
buffer_t out2;
+ const char * cipher;
- TEST_START();
+ cipher = crypt_nid_to_str(nid);
+ TEST_START("(%s)", cipher);
if (random_buffer(key, sizeof(key)) < 0) {
printf("Failed to generate random key.\n");
@@ -181,7 +90,7 @@ int test_crypt_encrypt_decrypt(void)
goto fail_init;
}
- ctx = crypt_create_ctx(key);
+ ctx = crypt_create_ctx(&sk);
if (ctx == NULL) {
printf("Failed to initialize cryptography.\n");
goto fail_init;
@@ -219,7 +128,7 @@ int test_crypt_encrypt_decrypt(void)
freebuf(out2);
freebuf(out);
- TEST_SUCCESS();
+ TEST_SUCCESS("(%s)", cipher);
return TEST_RC_SUCCESS;
fail_chk:
@@ -229,10 +138,122 @@ int test_crypt_encrypt_decrypt(void)
fail_encrypt:
crypt_destroy_ctx(ctx);
fail_init:
+ TEST_FAIL("(%s)", cipher);
+ return TEST_RC_FAIL;
+}
+
+static int test_encrypt_decrypt_all(void)
+{
+ int ret = 0;
+ int i;
+
+ for (i = 0; crypt_supported_nids[i] != NID_undef; i++)
+ ret |= test_crypt_encrypt_decrypt(crypt_supported_nids[i]);
+
+ return ret;
+}
+
+#ifdef HAVE_OPENSSL
+#include <openssl/evp.h>
+#include <openssl/obj_mac.h>
+
+static int test_cipher_nid_values(void)
+{
+ int i;
+
+ TEST_START();
+
+ /* Loop over all supported ciphers and verify NIDs match OpenSSL's */
+ for (i = 0; crypt_supported_nids[i] != NID_undef; i++) {
+ uint16_t our_nid = crypt_supported_nids[i];
+ const char * str = crypt_nid_to_str(our_nid);
+ const EVP_CIPHER * cipher;
+ int openssl_nid;
+
+ if (str == NULL) {
+ printf("crypt_nid_to_str failed for NID %u\n", our_nid);
+ goto fail;
+ }
+
+ cipher = EVP_get_cipherbyname(str);
+ if (cipher == NULL) {
+ printf("OpenSSL doesn't recognize cipher '%s'\n", str);
+ goto fail;
+ }
+
+ openssl_nid = EVP_CIPHER_nid(cipher);
+
+ if (our_nid != openssl_nid) {
+ printf("NID mismatch for '%s': ours=%u, OpenSSL=%d\n",
+ str, our_nid, openssl_nid);
+ goto fail;
+ }
+
+ /* Test reverse conversion */
+ if (crypt_str_to_nid(str) != our_nid) {
+ printf("crypt_str_to_nid failed for '%s'\n", str);
+ goto fail;
+ }
+ }
+
+ /* Test error cases */
+ if (crypt_str_to_nid("invalid") != NID_undef) {
+ printf("crypt_str_to_nid: no NID_undef for invalid.\n");
+ goto fail;
+ }
+
+ if (crypt_nid_to_str(9999) != NULL) {
+ printf("crypt_nid_to_str should return NULL for invalid NID\n");
+ goto fail;
+ }
+
+ if (crypt_str_to_nid(NULL) != NID_undef) {
+ printf("crypt_str_to_nid should return NID_undef for NULL\n");
+ goto fail;
+ }
+
+ TEST_SUCCESS();
+
+ return TEST_RC_SUCCESS;
+ fail:
TEST_FAIL();
return TEST_RC_FAIL;
}
+static int test_md_nid_values(void)
+{
+ int i;
+
+ TEST_START();
+
+ for (i = 0; md_supported_nids[i] != NID_undef; i++) {
+ uint16_t our_nid = md_supported_nids[i];
+ const EVP_MD * md;
+ int openssl_nid;
+
+ md = EVP_get_digestbynid(our_nid);
+ if (md == NULL) {
+ printf("OpenSSL doesn't recognize NID %u\n", our_nid);
+ goto fail;
+ }
+
+ openssl_nid = EVP_MD_nid(md);
+ if (our_nid != openssl_nid) {
+ printf("NID mismatch: ours=%u, OpenSSL=%d\n",
+ our_nid, openssl_nid);
+ goto fail;
+ }
+ }
+
+ TEST_SUCCESS();
+
+ return TEST_RC_SUCCESS;
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+#endif
+
int crypt_test(int argc,
char ** argv)
{
@@ -242,17 +263,10 @@ int crypt_test(int argc,
(void) argv;
ret |= test_crypt_create_destroy();
- ret |= test_crypt_create_destroy_with_key();
+ ret |= test_encrypt_decrypt_all();
#ifdef HAVE_OPENSSL
- ret |= test_crypt_dh_pkp_create_destroy();
- ret |= test_crypt_dh_derive();
- ret |= test_crypt_encrypt_decrypt();
-#else
- (void) test_crypt_dh_pkp_create_destroy;
- (void) test_crypt_dh_derive;
- (void) test_crypt_encrypt_decrypt;
-
- ret = TEST_RC_SKIP;
+ ret |= test_cipher_nid_values();
+ ret |= test_md_nid_values();
#endif
return ret;
}
diff --git a/src/lib/tests/hash_test.c b/src/lib/tests/hash_test.c
index 970d9185..fb428b47 100644
--- a/src/lib/tests/hash_test.c
+++ b/src/lib/tests/hash_test.c
@@ -21,7 +21,7 @@
*/
#include <ouroboros/hash.h>
-#include <ouroboros/test.h>
+#include <test/test.h>
#include <stdlib.h>
#include <stdint.h>
diff --git a/src/lib/tests/kex_test.c b/src/lib/tests/kex_test.c
new file mode 100644
index 00000000..58cf8b43
--- /dev/null
+++ b/src/lib/tests/kex_test.c
@@ -0,0 +1,844 @@
+/*
+ * Ouroboros - Copyright (C) 2016 - 2024
+ *
+ * Test of the key exchange functions
+ *
+ * Dimitri Staessens <dimitri@ouroboros.rocks>
+ * Sander Vrijders <sander@ouroboros.rocks>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., http://www.fsf.org/about/contact/.
+ */
+
+#define _POSIX_C_SOURCE 200809L
+
+#include "config.h"
+
+#include <test/test.h>
+#include <ouroboros/utils.h>
+#include <ouroboros/crypt.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#ifdef HAVE_OPENSSL
+#include <openssl/evp.h>
+#include <openssl/x509.h>
+#endif
+
+/* Test configuration strings */
+#define KEX_CONFIG_CUSTOM \
+ "kex=X25519\n"
+
+#define KEX_CONFIG_NONE \
+ "none\n"
+
+#define KEX_CONFIG_WHITESPACE \
+ "# Comment line\n" \
+ "kex = X448" \
+ "\n" \
+ "# Another comment\n"
+
+#define KEX_CONFIG_CIPHER \
+ "kex=X25519\n" \
+ "cipher=chacha20-poly1305\n"
+
+#define KEX_CONFIG_DIGEST \
+ "kex=X25519\n" \
+ "digest=sha384\n"
+
+/* Test key material for key loading tests */
+#define X25519_PRIVKEY_PEM \
+ "-----BEGIN PRIVATE KEY-----\n" \
+ "MC4CAQAwBQYDK2VuBCIEIJDd3+/0k2IZlaH5sZ9Z2e5J8dV2U0nsXaSUm70ZaMhL\n" \
+ "-----END PRIVATE KEY-----\n"
+
+#define X25519_PUBKEY_PEM \
+ "-----BEGIN PUBLIC KEY-----\n" \
+ "MCowBQYDK2VuAyEAKYLIycSZtLFlwAX07YWWgBAYhEnRxHfgK1TVw9+mtBs=\n" \
+ "-----END PUBLIC KEY-----\n"
+
+/* Helper macro to open string constant as FILE stream */
+#define FMEMOPEN_STR(str) fmemopen((void *) (str), strlen(str), "r")
+
+extern const uint16_t kex_supported_nids[];
+
+int parse_sec_config(struct sec_config * cfg,
+ FILE * fp);
+
+static int test_kex_create_destroy(void)
+{
+ struct sec_config cfg;
+
+ TEST_START();
+
+ memset(&cfg, 0, sizeof(cfg));
+ cfg.x.nid = NID_X9_62_prime256v1;
+ cfg.x.str = kex_nid_to_str(cfg.x.nid);
+ cfg.c.nid = NID_aes_256_gcm;
+ cfg.c.str = crypt_nid_to_str(cfg.c.nid);
+
+ if (cfg.x.nid == NID_undef || cfg.c.nid == NID_undef) {
+ printf("Failed to initialize kex config.\n");
+ goto fail;
+ }
+
+ TEST_SUCCESS();
+
+ return TEST_RC_SUCCESS;
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_kex_dh_pkp_create_destroy(void)
+{
+ struct sec_config kex;
+ void * pkp;
+ uint8_t buf[MSGBUFSZ];
+
+ TEST_START();
+
+ memset(&kex, 0, sizeof(kex));
+ SET_KEX_ALGO(&kex, "prime256v1");
+
+ if (kex_pkp_create(&kex, &pkp, buf) < 0) {
+ printf("Failed to create DH PKP.\n");
+ goto fail;
+ }
+
+ kex_pkp_destroy(pkp);
+
+ TEST_SUCCESS();
+
+ return TEST_RC_SUCCESS;
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_kex_get_algo_from_pk(const char * algo)
+{
+ struct sec_config kex;
+ void * pkp;
+ buffer_t pk;
+ ssize_t len;
+ uint8_t buf[MSGBUFSZ];
+ char extracted_algo[256];
+
+ TEST_START("(%s)", algo);
+
+ memset(&kex, 0, sizeof(kex));
+ SET_KEX_ALGO(&kex, algo);
+
+ len = kex_pkp_create(&kex, &pkp, buf);
+ if (len < 0) {
+ printf("Failed to create key pair.\n");
+ goto fail;
+ }
+
+ pk.len = (size_t) len;
+ pk.data = buf;
+
+ /* Use raw decode for hybrid KEMs, DER for others */
+ if (IS_HYBRID_KEM(algo)) {
+ if (kex_get_algo_from_pk_raw(pk, extracted_algo) < 0) {
+ printf("Failed to extract algo from pk.\n");
+ goto fail_pkp;
+ }
+ } else {
+ if (kex_get_algo_from_pk_der(pk, extracted_algo) < 0) {
+ printf("Failed to extract algo from pk.\n");
+ goto fail_pkp;
+ }
+ }
+
+ /* All algorithms should now return the specific group name */
+ if (strcmp(extracted_algo, algo) != 0) {
+ printf("Algo mismatch: expected %s, got %s.\n",
+ algo, extracted_algo);
+ goto fail_pkp;
+ }
+
+ kex_pkp_destroy(pkp);
+
+ TEST_SUCCESS("(%s)", algo);
+
+ return TEST_RC_SUCCESS;
+ fail_pkp:
+ kex_pkp_destroy(pkp);
+ fail:
+ TEST_FAIL("(%s)", algo);
+ return TEST_RC_FAIL;
+}
+
+static int test_kex_get_algo_from_pk_all(void)
+{
+ int ret = 0;
+ int i;
+
+ for (i = 0; kex_supported_nids[i] != NID_undef; i++) {
+ const char * algo = kex_nid_to_str(kex_supported_nids[i]);
+ ret |= test_kex_get_algo_from_pk(algo);
+ }
+
+ return ret;
+}
+
+static int test_kex_dhe_derive(const char * algo)
+{
+ struct sec_config kex;
+ void * pkp1;
+ void * pkp2;
+ buffer_t pk1;
+ buffer_t pk2;
+ ssize_t len;
+ uint8_t buf1[MSGBUFSZ];
+ uint8_t buf2[MSGBUFSZ];
+ uint8_t s1[SYMMKEYSZ];
+ uint8_t s2[SYMMKEYSZ];
+
+ TEST_START("(%s)", algo);
+
+ memset(&kex, 0, sizeof(kex));
+ SET_KEX_ALGO(&kex, algo);
+
+ len = kex_pkp_create(&kex, &pkp1, buf1);
+ if (len < 0) {
+ printf("Failed to create first key pair for %s.\n", algo);
+ goto fail;
+ }
+
+ pk1.len = (size_t) len;
+ pk1.data = buf1;
+
+ len = kex_pkp_create(&kex, &pkp2, buf2);
+ if (len < 0) {
+ printf("Failed to create second key pair for %s.\n", algo);
+ goto fail_pkp1;
+ }
+
+ pk2.len = (size_t) len;
+ pk2.data = buf2;
+
+ if (kex_dhe_derive(&kex, pkp1, pk2, s1) < 0) {
+ printf("Failed to derive first key for %s.\n", algo);
+ goto fail_pkp2;
+ }
+
+ if (kex_dhe_derive(&kex, pkp2, pk1, s2) < 0) {
+ printf("Failed to derive second key for %s.\n", algo);
+ goto fail_pkp2;
+ }
+
+ if (memcmp(s1, s2, SYMMKEYSZ) != 0) {
+ printf("Derived keys do not match for %s.\n", algo);
+ goto fail_pkp2;
+ }
+
+ kex_pkp_destroy(pkp2);
+ kex_pkp_destroy(pkp1);
+
+ TEST_SUCCESS("(%s)", algo);
+
+ return TEST_RC_SUCCESS;
+ fail_pkp2:
+ kex_pkp_destroy(pkp2);
+ fail_pkp1:
+ kex_pkp_destroy(pkp1);
+ fail:
+ TEST_FAIL("(%s)", algo);
+ return TEST_RC_FAIL;
+}
+
+static int test_kex_validate_algo(void)
+{
+ TEST_START();
+
+ if (kex_validate_algo("prime256v1") != 0) {
+ printf("prime256v1 should be valid.\n");
+ goto fail;
+ }
+
+ if (kex_validate_algo("X25519") != 0) {
+ printf("X25519 should be valid.\n");
+ goto fail;
+ }
+
+#ifdef HAVE_OPENSSL_PQC
+ if (kex_validate_algo("ML-KEM-768") != 0) {
+ printf("ML-KEM-768 should be valid.\n");
+ goto fail;
+ }
+#endif
+
+ if (kex_validate_algo("ffdhe2048") != 0) {
+ printf("ffdhe2048 should be valid.\n");
+ goto fail;
+ }
+
+ if (kex_validate_algo("invalid_algo") == 0) {
+ printf("invalid_algo should be rejected.\n");
+ goto fail;
+ }
+
+ if (kex_validate_algo("rsa2048") == 0) {
+ printf("rsa2048 should be rejected.\n");
+ goto fail;
+ }
+
+ if (kex_validate_algo(NULL) == 0) {
+ printf("NULL should be rejected.\n");
+ goto fail;
+ }
+
+ TEST_SUCCESS();
+
+ return TEST_RC_SUCCESS;
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_kex_dhe_corrupted_pubkey(const char * algo)
+{
+ struct sec_config kex;
+ void * pkp;
+ buffer_t pk;
+ ssize_t len;
+ uint8_t buf[MSGBUFSZ];
+ uint8_t s[SYMMKEYSZ];
+
+ TEST_START("(%s)", algo);
+
+ memset(&kex, 0, sizeof(kex));
+ SET_KEX_ALGO(&kex, algo);
+
+ len = kex_pkp_create(&kex, &pkp, buf);
+ if (len < 0) {
+ printf("Failed to create key pair.\n");
+ goto fail;
+ }
+
+ pk.len = (size_t) len;
+ pk.data = buf;
+
+ /* Corrupt the public key */
+ buf[0] ^= 0xFF;
+ buf[len - 1] ^= 0xFF;
+
+ if (kex_dhe_derive(&kex, pkp, pk, s) == 0) {
+ printf("Should fail with corrupted public key.\n");
+ goto fail_pkp;
+ }
+
+ kex_pkp_destroy(pkp);
+
+ TEST_SUCCESS("(%s)", algo);
+
+ return TEST_RC_SUCCESS;
+ fail_pkp:
+ kex_pkp_destroy(pkp);
+ fail:
+ TEST_FAIL("(%s)", algo);
+ return TEST_RC_FAIL;
+}
+
+static int test_kex_dhe_wrong_algo(void)
+{
+ struct sec_config kex1;
+ struct sec_config kex2;
+ void * pkp1;
+ void * pkp2;
+ buffer_t pk2;
+ ssize_t len;
+ uint8_t buf1[MSGBUFSZ];
+ uint8_t buf2[MSGBUFSZ];
+ uint8_t s[SYMMKEYSZ];
+ const char * algo1 = "X25519";
+ const char * algo2 = "X448";
+
+ TEST_START("(%s vs %s)", algo1, algo2);
+
+ memset(&kex1, 0, sizeof(kex1));
+ memset(&kex2, 0, sizeof(kex2));
+ SET_KEX_ALGO(&kex1, algo1);
+ SET_KEX_ALGO(&kex2, algo2);
+
+ if (kex_pkp_create(&kex1, &pkp1, buf1) < 0) {
+ printf("Failed to create first key pair.\n");
+ goto fail;
+ }
+
+ len = kex_pkp_create(&kex2, &pkp2, buf2);
+ if (len < 0) {
+ printf("Failed to create second key pair.\n");
+ goto fail_pkp1;
+ }
+
+ pk2.len = (size_t) len;
+ pk2.data = buf2;
+
+ /* Try to derive with mismatched algorithms */
+ if (kex_dhe_derive(&kex1, pkp1, pk2, s) == 0) {
+ printf("Should fail with mismatched algorithms.\n");
+ goto fail_pkp2;
+ }
+
+ kex_pkp_destroy(pkp2);
+ kex_pkp_destroy(pkp1);
+
+ TEST_SUCCESS("(%s vs %s)", algo1, algo2);
+
+ return TEST_RC_SUCCESS;
+ fail_pkp2:
+ kex_pkp_destroy(pkp2);
+ fail_pkp1:
+ kex_pkp_destroy(pkp1);
+ fail:
+ TEST_FAIL("(%s vs %s)", algo1, algo2);
+ return TEST_RC_FAIL;
+}
+
+static int test_kex_load_dhe_privkey(void)
+{
+ void * key;
+
+ TEST_START();
+
+ if (crypt_load_privkey_str(X25519_PRIVKEY_PEM, &key) < 0) {
+ printf("Failed to load X25519 private key.\n");
+ goto fail;
+ }
+
+ crypt_free_key(key);
+
+ TEST_SUCCESS();
+
+ return TEST_RC_SUCCESS;
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_kex_load_dhe_pubkey(void)
+{
+ void * key;
+
+ TEST_START();
+
+ if (crypt_load_pubkey_str(X25519_PUBKEY_PEM, &key) < 0) {
+ printf("Failed to load X25519 public key.\n");
+ goto fail;
+ }
+
+ crypt_free_key(key);
+
+ TEST_SUCCESS();
+
+ return TEST_RC_SUCCESS;
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+#ifdef HAVE_OPENSSL
+#include <openssl/obj_mac.h>
+
+static int test_kex_nid_values(void)
+{
+ int i;
+
+ TEST_START();
+
+ /* Verify all KEX algorithm NIDs match OpenSSL's */
+ for (i = 0; kex_supported_nids[i] != NID_undef; i++) {
+ uint16_t our_nid = kex_supported_nids[i];
+ const char * kex_name;
+ int openssl_nid;
+
+ kex_name = kex_nid_to_str(our_nid);
+ if (kex_name == NULL) {
+ printf("kex_nid_to_str failed for NID %u\n", our_nid);
+ goto fail;
+ }
+
+ /* Test reverse conversion */
+ if (kex_str_to_nid(kex_name) != our_nid) {
+ printf("kex_str_to_nid failed for '%s'\n", kex_name);
+ goto fail;
+ }
+
+ /* Get OpenSSL's NID for this name */
+ openssl_nid = OBJ_txt2nid(kex_name);
+ if (openssl_nid != NID_undef) {
+ /* OpenSSL recognizes this algorithm */
+ if (our_nid != openssl_nid) {
+ printf("NID mismatch for '%s': "
+ "ours=%d, OpenSSL=%d\n",
+ kex_name, our_nid, openssl_nid);
+ goto fail;
+ }
+ } else {
+ /* Verify no NID collision with different algorithm */
+ const char * ossl_name = OBJ_nid2sn(our_nid);
+ if (ossl_name != NULL &&
+ strcmp(ossl_name, kex_name) != 0) {
+ printf("NID collision for '%d': "
+ "ours=%s, OpenSSL=%s\n",
+ our_nid, kex_name, ossl_name);
+ goto fail;
+ }
+ }
+ }
+
+ /* Test error cases */
+ if (kex_str_to_nid("invalid") != NID_undef) {
+ printf("kex_str_to_nid should return NID_undef for invalid\n");
+ goto fail;
+ }
+
+ if (kex_nid_to_str(9999) != NULL) {
+ printf("kex_nid_to_str should return NULL for invalid NID\n");
+ goto fail;
+ }
+
+ if (kex_str_to_nid(NULL) != NID_undef) {
+ printf("kex_str_to_nid should return NID_undef for NULL\n");
+ goto fail;
+ }
+
+ TEST_SUCCESS();
+
+ return TEST_RC_SUCCESS;
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+#endif
+
+static int test_kex_all(void)
+{
+ int ret = 0;
+ int i;
+
+ for (i = 0; kex_supported_nids[i] != NID_undef; i++) {
+ const char * algo = kex_nid_to_str(kex_supported_nids[i]);
+
+ /* KEM tests are in kex_test_pqc.c */
+ if (IS_KEM_ALGORITHM(algo))
+ continue;
+
+ ret |= test_kex_dhe_derive(algo);
+ }
+
+ return ret;
+}
+
+static int test_kex_dhe_corrupted_pubkey_all(void)
+{
+ int ret = 0;
+ int i;
+
+ /* Test corruption for all DHE algorithms */
+ /* KEM error injection tests are in kex_test_pqc.c */
+ for (i = 0; kex_supported_nids[i] != NID_undef; i++) {
+ const char * algo = kex_nid_to_str(kex_supported_nids[i]);
+
+ if (IS_KEM_ALGORITHM(algo))
+ continue;
+
+ ret |= test_kex_dhe_corrupted_pubkey(algo);
+ }
+
+ return ret;
+}
+
+static int test_kex_parse_config_empty(void)
+{
+ struct sec_config kex;
+ FILE * fp;
+
+ TEST_START();
+
+ memset(&kex, 0, sizeof(kex));
+
+ fp = FMEMOPEN_STR("");
+ if (fp == NULL) {
+ printf("Failed to open memory stream.\n");
+ goto fail;
+ }
+
+ if (parse_sec_config(&kex, fp) < 0) {
+ printf("Failed to parse empty config.\n");
+ fclose(fp);
+ goto fail;
+ }
+
+ if (strcmp(kex.x.str, "prime256v1") != 0) {
+ printf("Empty config should use prime256v1.\n");
+ fclose(fp);
+ goto fail;
+ }
+
+ fclose(fp);
+
+ TEST_SUCCESS();
+
+ return TEST_RC_SUCCESS;
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_kex_parse_config_custom(void)
+{
+ struct sec_config kex;
+ FILE * fp;
+
+ TEST_START();
+
+ memset(&kex, 0, sizeof(kex));
+
+ fp = FMEMOPEN_STR(KEX_CONFIG_CUSTOM);
+ if (fp == NULL) {
+ printf("Failed to open memory stream.\n");
+ goto fail;
+ }
+
+ if (parse_sec_config(&kex, fp) < 0) {
+ printf("Failed to parse custom config.\n");
+ fclose(fp);
+ goto fail;
+ }
+
+ if (strcmp(kex.x.str, "X25519") != 0) {
+ printf("Algorithm not set correctly.\n");
+ fclose(fp);
+ goto fail;
+ }
+
+ fclose(fp);
+
+ TEST_SUCCESS();
+
+ return TEST_RC_SUCCESS;
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_kex_parse_config_none(void)
+{
+ struct sec_config kex;
+ FILE * fp;
+
+ TEST_START();
+
+ memset(&kex, 0, sizeof(kex));
+
+ fp = FMEMOPEN_STR(KEX_CONFIG_NONE);
+ if (fp == NULL) {
+ printf("Failed to open memory stream.\n");
+ goto fail;
+ }
+
+ if (parse_sec_config(&kex, fp) < 0) {
+ printf("Failed to parse 'none' config.\n");
+ fclose(fp);
+ goto fail;
+ }
+
+ if (kex.x.nid != NID_undef) {
+ printf("'none' keyword should disable encryption.\n");
+ fclose(fp);
+ goto fail;
+ }
+
+ fclose(fp);
+
+ TEST_SUCCESS();
+
+ return TEST_RC_SUCCESS;
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_kex_parse_config_whitespace(void)
+{
+ struct sec_config kex;
+ FILE * fp;
+
+ TEST_START();
+
+ memset(&kex, 0, sizeof(kex));
+
+ fp = FMEMOPEN_STR(KEX_CONFIG_WHITESPACE);
+ if (fp == NULL) {
+ printf("Failed to open memory stream.\n");
+ goto fail;
+ }
+
+ if (parse_sec_config(&kex, fp) < 0) {
+ printf("Failed to parse config with comments.\n");
+ fclose(fp);
+ goto fail;
+ }
+
+ if (strcmp(kex.x.str, "X448") != 0) {
+ printf("Algorithm with whitespace not parsed correctly.\n");
+ fclose(fp);
+ goto fail;
+ }
+
+ fclose(fp);
+
+ TEST_SUCCESS();
+
+ return TEST_RC_SUCCESS;
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_kex_parse_config_cipher(void)
+{
+ struct sec_config kex;
+ FILE * fp;
+
+ TEST_START();
+
+ memset(&kex, 0, sizeof(kex));
+
+ fp = FMEMOPEN_STR(KEX_CONFIG_CIPHER);
+ if (fp == NULL) {
+ printf("Failed to open memory stream.\n");
+ goto fail;
+ }
+
+ if (parse_sec_config(&kex, fp) < 0) {
+ printf("Failed to parse cipher config.\n");
+ fclose(fp);
+ goto fail;
+ }
+
+ if (strcmp(kex.x.str, "X25519") != 0) {
+ printf("Algorithm not set correctly.\n");
+ fclose(fp);
+ goto fail;
+ }
+
+ if (kex.c.nid != NID_chacha20_poly1305) {
+ printf("Cipher not set correctly.\n");
+ fclose(fp);
+ goto fail;
+ }
+
+ fclose(fp);
+
+ TEST_SUCCESS();
+
+ return TEST_RC_SUCCESS;
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_kex_parse_config_digest(void)
+{
+ struct sec_config kex;
+ FILE * fp;
+
+ TEST_START();
+
+ memset(&kex, 0, sizeof(kex));
+
+ fp = FMEMOPEN_STR(KEX_CONFIG_DIGEST);
+ if (fp == NULL) {
+ printf("Failed to open memory stream.\n");
+ goto fail;
+ }
+
+ if (parse_sec_config(&kex, fp) < 0) {
+ printf("Failed to parse digest config.\n");
+ fclose(fp);
+ goto fail;
+ }
+
+ if (strcmp(kex.x.str, "X25519") != 0) {
+ printf("Algorithm not set correctly.\n");
+ fclose(fp);
+ goto fail;
+ }
+
+ if (kex.d.nid != NID_sha384) {
+ printf("Digest not set correctly.\n");
+ fclose(fp);
+ goto fail;
+ }
+
+ fclose(fp);
+
+ TEST_SUCCESS();
+
+ return TEST_RC_SUCCESS;
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+int kex_test(int argc,
+ char ** argv)
+{
+ int ret = 0;
+
+ (void) argc;
+ (void) argv;
+
+ ret |= test_kex_create_destroy();
+ ret |= test_kex_parse_config_empty();
+ ret |= test_kex_parse_config_none();
+#ifdef HAVE_OPENSSL
+ ret |= test_kex_parse_config_custom();
+ ret |= test_kex_parse_config_whitespace();
+ ret |= test_kex_parse_config_cipher();
+ ret |= test_kex_parse_config_digest();
+ ret |= test_kex_nid_values();
+ ret |= test_kex_dh_pkp_create_destroy();
+ ret |= test_kex_all();
+ ret |= test_kex_validate_algo();
+ ret |= test_kex_get_algo_from_pk_all();
+ ret |= test_kex_dhe_wrong_algo();
+ ret |= test_kex_dhe_corrupted_pubkey_all();
+ ret |= test_kex_load_dhe_privkey();
+ ret |= test_kex_load_dhe_pubkey();
+#else
+ (void) test_kex_parse_config_custom;
+ (void) test_kex_parse_config_whitespace;
+ (void) test_kex_parse_config_cipher;
+ (void) test_kex_parse_config_digest;
+ (void) test_kex_dh_pkp_create_destroy;
+ (void) test_kex_all;
+ (void) test_kex_validate_algo;
+ (void) test_kex_get_algo_from_pk_all;
+ (void) test_kex_dhe_wrong_algo();
+ (void) test_kex_dhe_corrupted_pubkey_all;
+ (void) test_kex_load_dhe_privkey;
+ (void) test_kex_load_dhe_pubkey;
+
+ ret = TEST_RC_SKIP;
+#endif
+ return ret;
+}
diff --git a/src/lib/tests/kex_test_pqc.c b/src/lib/tests/kex_test_pqc.c
new file mode 100644
index 00000000..d4579eca
--- /dev/null
+++ b/src/lib/tests/kex_test_pqc.c
@@ -0,0 +1,549 @@
+/*
+ * Ouroboros - Copyright (C) 2016 - 2026
+ *
+ * Test of the post-quantum key exchange functions
+ *
+ * Dimitri Staessens <dimitri@ouroboros.rocks>
+ * Sander Vrijders <sander@ouroboros.rocks>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., http://www.fsf.org/about/contact/.
+ */
+
+#define _POSIX_C_SOURCE 200809L
+
+#include "config.h"
+
+#include <test/test.h>
+#include <ouroboros/utils.h>
+#include <ouroboros/crypt.h>
+#include <ouroboros/random.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#ifdef HAVE_OPENSSL
+#include <openssl/evp.h>
+#include <openssl/x509.h>
+#endif
+
+extern const uint16_t kex_supported_nids[];
+extern const uint16_t md_supported_nids[];
+
+static int get_random_kdf(void)
+{
+ static int idx = 0;
+ int count;
+
+ if (md_supported_nids[0] == NID_undef)
+ return NID_undef;
+
+ for (count = 0; md_supported_nids[count] != NID_undef; count++)
+ ;
+
+ return md_supported_nids[(idx++) % count];
+}
+
+/* ML-KEM-768 test key material */
+
+#define MLKEM768_PRIVKEY_PEM \
+ "-----BEGIN PRIVATE KEY-----\n" \
+ "MIIJvgIBADALBglghkgBZQMEBAIEggmqMIIJpgRA+QIIiQLQkS5fl5RluSmgXRjZ\n" \
+ "YU16W4TVt0dmnBP41rLTTRT3S8CRtkb+xmoFAcWTfEzbdr5pp3g2CBRx+APXTwSC\n" \
+ "CWBll6AecTd1Kqdyix3zNQcthDBP0XnwdTHDqkKuFzMP58Y+0gc9Bo+W0xBOK2ZK\n" \
+ "gcAmix3YLJuDS8Teep/Tdc7KIm5AaLNoI8BIMgKC/ASsW8kC+78BV4OIgqNWurS9\n" \
+ "BrTiCmiag7c+6DsVDJHJ4kfcccwUDBKiW0v+LAkk1HXBcx6usrwuFC0H3ICli2sC\n" \
+ "o5DfGL7g4kWHhobXjAZnxn298C8FGmLQK5kah4nZiJ+MuHqrirziCGTLKkY1a8vC\n" \
+ "GFgzfHIcvB4dtyi9dxZmWpSXqDf2AVNgqrD2C7WQEULQOKxm/I8Mw31Yp8TC6SAP\n" \
+ "RzM4cBAXF00W4Rce05O0am/ga5dStAhikMESyckCoEGlPFFXOmjy1HmOasI+AbGk\n" \
+ "2BKp6cfbImbjd0ePdCSFEgIQwAQHm7+4UoZR2JmNwSI1AC2P4FMRAIaD2A69i6LC\n" \
+ "kFniGcOog5m09nw5FqZmeEfNs6yyFGSX16D1YyjuooAFGlU0FFX7aKwsYM8t1gkS\n" \
+ "YSUfMxIW9yzhSW4vZHuGyxlxBMr1y51RZrW8gnvW5p/Ip5yDBJRahY6KMWT15C14\n" \
+ "C2rIe8U+d4Xi5IMI3D1JNpwFebYhKs3/ManxoU7Fwwa0GzQrgLYU5KhqO8/hopnl\n" \
+ "8mQH+BPh+TR5lqYawS7HZXFJE8JzOnCtOSgB6Hz2U7oG9ik8h0FRqVD3ak20EmZU\n" \
+ "c7gpGW8Odc51uaIBzDu4ej4dGgwo4awYaX4ugLOutHqGqRfCjIVb6XQ4m35p4KKi\n" \
+ "qBVQ211aIhavUIgNECJ7WUETilXyyHLB9x3EFJdidEfSRUxLYJNAC5XM2WFCyhnE\n" \
+ "pKmossSNq6ZOqBjPegE0J6zfNg65dR/OlIdGVDgrVTIpwYAUzBMW2nTnCa00EmPj\n" \
+ "F7tRscHI8qb/QlnRVEUN+S+A2CtVIH1c666zOoRFRI9G4bmVoa8k2x0ANB51tCns\n" \
+ "vAYqkMybIgMvWwbqoAxeW0G1O3qObGXtgs94BzhAEM3RbG/hy3GR1qUNSk/qyDKc\n" \
+ "t1qpiaao0aLVsnpb28eBIk6+q0I82reGdV31OYvUpnVxRbRPFXEFs5PNS3s/7I8a\n" \
+ "SlSLUGOh+mhrUzDPSJCzgEvOmrwrRxe3F52tS0nAt6Z5zKToASHphoISUi7lGX1F\n" \
+ "Owx62qhSqqlI98bKqh7yQRZYrHXqE0bscAHCcIaZ8RVya42JHDCoQWyxqBuLOWEl\n" \
+ "+Fz6vI5DqEnJkA7ke49EvBAOJ58lxAXQIV5remtzYGPKdyG2oamiFHiLVQDzGX/l\n" \
+ "aFNMGXRWcK4/Y3mnkJvx9QGtq6KstQN/J4a51ZeX5YwNBcoY9UcFS6kHRW5rR3UM\n" \
+ "tEZj5VN8BL9nyWM9h7hUSHQboaxO7M5qswfXB8f21xR16T40Ki4nawx/6zHGCQsc\n" \
+ "uKr5SaCV88tghqJYHBorU5iKB5KsLDSHqYYrNo/Vy8W6kMA2jGAO24d4G32DSshR\n" \
+ "sEF9W1nuAHK/5ste01G5KmX2KhdZBE37oGhM98HRQ6hU8qwuKrhdV7vZis5C8LXY\n" \
+ "7MbDyDt1NnFqWFc6lYeVa6eRcmYzeAbXahrxwiiaLIdHXD95aZ/0S6+tKBGgQzwm\n" \
+ "ZsbwdXhl+n+yqDNE6Sow2bwueqhDwZVWoMCv5SK+HAGPtcZ7UU9oWrqpiL085m7F\n" \
+ "5G49KJUEZadVtj4Z9zrkeQkida+4I7v3Y3MzsWsGJww7YhTDJpsxxmSm85bHwx98\n" \
+ "hZXSqckJTL4c2nBzgrBlukIT9Wl+qItMthVvABPzp4wGZhdgKrEIRl3yCnhhUgpL\n" \
+ "lUxYegwWDMEjZxKlSbIyl5p9lCS8w2lsBzsQ2FJiAy/MWLa56aA+wFs3C8smZ6Cf\n" \
+ "p5NWa8Rm+k898GWBxZivhF03CBOZ42du0YUZdCPoA5V1KC6bh4JyWFI49VFbQFMG\n" \
+ "gwAqc0ErAH3iMammKC9746WWagnUIG3o8LygZrusuGeTohXJhVUTJDw2s0rzNhbw\n" \
+ "5IyookkY5BWENKFKTIgdBxvYelOKwbGE8Z36FEW0ABlmx7SRCKWlNVjSEAIXmMiQ\n" \
+ "VLdQF33QVYD9RR5chja254VuJH4plo+5JwiKWz8LlCIBm7CVkifZMLofmMk3s3L4\n" \
+ "sXtE+Bhfm5Plk3RrgDdlHH+hK7gk61XGdynGjDY7aLtCKZ0SMsVskSLom1pbIR5M\n" \
+ "KLYsQ1Pse4mhfDOFCkWFLI5TShGMuIoo1k7XeIE6g8QoUlV5EXyWHHhIVaE4yWGP\n" \
+ "AVgEp0UswKFeeo3SoCAeADA3U88ymxpBJp73yDIqok5dM3SgkjfPWZDkgkAI8WHs\n" \
+ "CKKeqrSOs1kkE3JXtE7kcTHT6XHo162TmgGkqMVwOQ3EmR6FRpYxJhZvuVbjJsSx\n" \
+ "YjW3ScnR4Zivoi7q95ypco331pIlIZpqV0NydUpMyQaz1cnoPKYDh1xa6LhcqEKK\n" \
+ "8a68iXjQgzgqQBDABonVybNDtlJ5lnTTuKhak8PBFAmmhj1JdrPqoIvQRCmLaark\n" \
+ "J7/q9RLtk6kTOJ0qtLe2qqwCxJwyoMd2Q5F4+xTWZHu90ljRdcnYewarqcKzoL27\n" \
+ "tcpTOmVz88I1hYVUJEV7aB36QMhTS1dquTqJZCD0hBPWAMToEoD4OFvKWmbFmzaW\n" \
+ "xrMc4ECYeDAAKYs2YqoXSLfAixBmZjb6UDB61l2GA58pFJW0ZwN8S5tApA2NRi+7\n" \
+ "oC/zgMgBGHft6E0+OUVb8It89pY1t7ybq5+fkBvEixDId3f1pK3gqcaYqG/YhoMJ\n" \
+ "MJWkqYxCNGmdZ8gFo46V6K+4xZUblQWKypN6+RYO4kDh0koppWGEULjgBoCH+V8E\n" \
+ "7GcoE8SRdQY1BIMoRVWb8Ur8ZYIVU8lqgaZPlWM3oRCiWk0kRxexFF0i5WlILIK9\n" \
+ "GT8saX+bmRd9KSy3JrpPhQn59CpJBRxz8WKdJ3wwtqE/2TbxQhLooEWHYVrZEG5E\n" \
+ "SkIoOkUAJUR+CzLLFDMdUE8w3CasE4ys+hco7AA5TAms24A1FXcxMgNb6VHA0bi5\n" \
+ "c8rPCZvjubLXR4A0/A2Ualo4cy3UAr9k0rbZOJnjqk8eExkeaxbyh42cJpU75i4O\n" \
+ "NLYsRZJkg9bkCpPgZKb707sPZO72CX3h/lQdXVgGkZ7Tqd1qzM+JOhSWvrYiBLa+\n" \
+ "5IKSmFwT+5sw1InEesXwRN09000U90vAkbZG/sZqBQHFk3xM23a+aad4NggUcfgD\n" \
+ "108=\n" \
+ "-----END PRIVATE KEY-----\n"
+
+#define MLKEM768_PUBKEY_PEM \
+ "-----BEGIN PUBLIC KEY-----\n" \
+ "MIIEsjALBglghkgBZQMEBAIDggShAMPIO3U2cWpYVzqVh5Vrp5FyZjN4BtdqGvHC\n" \
+ "KJosh0dcP3lpn/RLr60oEaBDPCZmxvB1eGX6f7KoM0TpKjDZvC56qEPBlVagwK/l\n" \
+ "Ir4cAY+1xntRT2hauqmIvTzmbsXkbj0olQRlp1W2Phn3OuR5CSJ1r7gju/djczOx\n" \
+ "awYnDDtiFMMmmzHGZKbzlsfDH3yFldKpyQlMvhzacHOCsGW6QhP1aX6oi0y2FW8A\n" \
+ "E/OnjAZmF2AqsQhGXfIKeGFSCkuVTFh6DBYMwSNnEqVJsjKXmn2UJLzDaWwHOxDY\n" \
+ "UmIDL8xYtrnpoD7AWzcLyyZnoJ+nk1ZrxGb6Tz3wZYHFmK+EXTcIE5njZ27RhRl0\n" \
+ "I+gDlXUoLpuHgnJYUjj1UVtAUwaDACpzQSsAfeIxqaYoL3vjpZZqCdQgbejwvKBm\n" \
+ "u6y4Z5OiFcmFVRMkPDazSvM2FvDkjKiiSRjkFYQ0oUpMiB0HG9h6U4rBsYTxnfoU\n" \
+ "RbQAGWbHtJEIpaU1WNIQAheYyJBUt1AXfdBVgP1FHlyGNrbnhW4kfimWj7knCIpb\n" \
+ "PwuUIgGbsJWSJ9kwuh+YyTezcvixe0T4GF+bk+WTdGuAN2Ucf6EruCTrVcZ3KcaM\n" \
+ "Njtou0IpnRIyxWyRIuibWlshHkwotixDU+x7iaF8M4UKRYUsjlNKEYy4iijWTtd4\n" \
+ "gTqDxChSVXkRfJYceEhVoTjJYY8BWASnRSzAoV56jdKgIB4AMDdTzzKbGkEmnvfI\n" \
+ "MiqiTl0zdKCSN89ZkOSCQAjxYewIop6qtI6zWSQTcle0TuRxMdPpcejXrZOaAaSo\n" \
+ "xXA5DcSZHoVGljEmFm+5VuMmxLFiNbdJydHhmK+iLur3nKlyjffWkiUhmmpXQ3J1\n" \
+ "SkzJBrPVyeg8pgOHXFrouFyoQorxrryJeNCDOCpAEMAGidXJs0O2UnmWdNO4qFqT\n" \
+ "w8EUCaaGPUl2s+qgi9BEKYtpquQnv+r1Eu2TqRM4nSq0t7aqrALEnDKgx3ZDkXj7\n" \
+ "FNZke73SWNF1ydh7BqupwrOgvbu1ylM6ZXPzwjWFhVQkRXtoHfpAyFNLV2q5Oolk\n" \
+ "IPSEE9YAxOgSgPg4W8paZsWbNpbGsxzgQJh4MAApizZiqhdIt8CLEGZmNvpQMHrW\n" \
+ "XYYDnykUlbRnA3xLm0CkDY1GL7ugL/OAyAEYd+3oTT45RVvwi3z2ljW3vJurn5+Q\n" \
+ "G8SLEMh3d/WkreCpxpiob9iGgwkwlaSpjEI0aZ1nyAWjjpXor7jFlRuVBYrKk3r5\n" \
+ "Fg7iQOHSSimlYYRQuOAGgIf5XwTsZygTxJF1BjUEgyhFVZvxSvxlghVTyWqBpk+V\n" \
+ "YzehEKJaTSRHF7EUXSLlaUgsgr0ZPyxpf5uZF30pLLcmuk+FCfn0KkkFHHPxYp0n\n" \
+ "fDC2oT/ZNvFCEuigRYdhWtkQbkRKQig6RQAlRH4LMssUMx1QTzDcJqwTjKz6Fyjs\n" \
+ "ADlMCazbgDUVdzEyA1vpUcDRuLlzys8Jm+O5stdHgDT8DZRqWjhzLdQCv2TSttk4\n" \
+ "meOqTx4TGR5rFvKHjZwmlTvmLg40tixFkmSD1uQKk+BkpvvTuw9k7vYJfeH+VB1d\n" \
+ "WAaRntOp\n" \
+ "-----END PUBLIC KEY-----\n"
+
+/* Helper macro to open string constant as FILE stream */
+#define FMEMOPEN_STR(str) fmemopen((void *) (str), strlen(str), "r")
+
+static int test_kex_load_kem_privkey(void)
+{
+ void * key;
+
+ TEST_START();
+
+ if (crypt_load_privkey_str(MLKEM768_PRIVKEY_PEM, &key) < 0) {
+ printf("Failed to load ML-KEM-768 private key.\n");
+ goto fail;
+ }
+
+ crypt_free_key(key);
+
+ TEST_SUCCESS();
+
+ return TEST_RC_SUCCESS;
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_kex_load_kem_pubkey(void)
+{
+ void * key;
+
+ TEST_START();
+
+ if (crypt_load_pubkey_str(MLKEM768_PUBKEY_PEM, &key) < 0) {
+ printf("Failed to load ML-KEM-768 public key.\n");
+ goto fail;
+ }
+
+ crypt_free_key(key);
+
+ TEST_SUCCESS();
+
+ return TEST_RC_SUCCESS;
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_kex_kem(const char * algo)
+{
+ struct sec_config kex;
+ void * pkp;
+ buffer_t pk;
+ buffer_t ct;
+ ssize_t len;
+ ssize_t ct_len;
+ uint8_t buf1[MSGBUFSZ];
+ uint8_t buf2[MSGBUFSZ];
+ uint8_t s1[SYMMKEYSZ];
+ uint8_t s2[SYMMKEYSZ];
+ int kdf;
+
+ TEST_START("(%s)", algo);
+
+ kdf = get_random_kdf();
+
+ memset(&kex, 0, sizeof(kex));
+ SET_KEX_ALGO(&kex, algo);
+
+ len = kex_pkp_create(&kex, &pkp, buf1);
+ if (len < 0) {
+ printf("Failed to create key pair for %s.\n", algo);
+ goto fail;
+ }
+
+ pk.len = (size_t) len;
+ pk.data = buf1;
+
+ if (IS_HYBRID_KEM(algo))
+ ct_len = kex_kem_encap_raw(pk, buf2, kdf, s1);
+ else
+ ct_len = kex_kem_encap(pk, buf2, kdf, s1);
+
+ if (ct_len < 0) {
+ printf("Failed to encapsulate for %s.\n", algo);
+ goto fail_pkp;
+ }
+
+ ct.len = (size_t) ct_len;
+ ct.data = buf2;
+
+ if (kex_kem_decap(pkp, ct, kdf, s2) < 0) {
+ printf("Failed to decapsulate for %s.\n", algo);
+ goto fail_pkp;
+ }
+
+ if (memcmp(s1, s2, SYMMKEYSZ) != 0) {
+ printf("Shared secrets don't match for %s.\n", algo);
+ goto fail_pkp;
+ }
+
+ kex_pkp_destroy(pkp);
+
+ TEST_SUCCESS("(%s)", algo);
+
+ return TEST_RC_SUCCESS;
+ fail_pkp:
+ kex_pkp_destroy(pkp);
+ fail:
+ TEST_FAIL("(%s)", algo);
+ return TEST_RC_FAIL;
+}
+
+static int test_kex_kem_corrupted_ciphertext(const char * algo)
+{
+ struct sec_config kex;
+ void * pkp;
+ buffer_t pk;
+ buffer_t ct;
+ ssize_t len;
+ ssize_t ct_len;
+ uint8_t buf1[MSGBUFSZ];
+ uint8_t buf2[MSGBUFSZ];
+ uint8_t s1[SYMMKEYSZ];
+ uint8_t s2[SYMMKEYSZ];
+ int kdf;
+
+ TEST_START("(%s)", algo);
+
+ kdf = get_random_kdf();
+
+ memset(&kex, 0, sizeof(kex));
+ SET_KEX_ALGO(&kex, algo);
+
+ len = kex_pkp_create(&kex, &pkp, buf1);
+ if (len < 0) {
+ printf("Failed to create key pair.\n");
+ goto fail;
+ }
+
+ pk.len = (size_t) len;
+ pk.data = buf1;
+
+ if (IS_HYBRID_KEM(algo))
+ ct_len = kex_kem_encap_raw(pk, buf2, kdf, s1);
+ else
+ ct_len = kex_kem_encap(pk, buf2, kdf, s1);
+
+ if (ct_len < 0) {
+ printf("Failed to encapsulate.\n");
+ goto fail_pkp;
+ }
+
+ ct.len = (size_t) ct_len;
+ ct.data = buf2;
+
+ /* Corrupt the ciphertext */
+ buf2[0] ^= 0xFF;
+ buf2[ct_len - 1] ^= 0xFF;
+
+ /* ML-KEM uses implicit rejection */
+ if (kex_kem_decap(pkp, ct, kdf, s2) < 0) {
+ printf("Decapsulation failed unexpectedly.\n");
+ goto fail_pkp;
+ }
+
+ /* The shared secrets should NOT match with corrupted CT */
+ if (memcmp(s1, s2, SYMMKEYSZ) == 0) {
+ printf("Corrupted ciphertext produced same secret.\n");
+ goto fail_pkp;
+ }
+
+ kex_pkp_destroy(pkp);
+
+ TEST_SUCCESS("(%s)", algo);
+
+ return TEST_RC_SUCCESS;
+ fail_pkp:
+ kex_pkp_destroy(pkp);
+ fail:
+ TEST_FAIL("(%s)", algo);
+ return TEST_RC_FAIL;
+}
+
+static int test_kex_kem_wrong_keypair(const char * algo)
+{
+ struct sec_config kex;
+ void * pkp1;
+ void * pkp2;
+ buffer_t pk1;
+ buffer_t ct;
+ ssize_t len;
+ ssize_t ct_len;
+ uint8_t buf1[MSGBUFSZ];
+ uint8_t buf2[MSGBUFSZ];
+ uint8_t buf3[MSGBUFSZ];
+ uint8_t s1[SYMMKEYSZ];
+ uint8_t s2[SYMMKEYSZ];
+
+ TEST_START("(%s)", algo);
+
+ memset(&kex, 0, sizeof(kex));
+ SET_KEX_ALGO(&kex, algo);
+
+ len = kex_pkp_create(&kex, &pkp1, buf1);
+ if (len < 0) {
+ printf("Failed to create first key pair.\n");
+ goto fail;
+ }
+
+ pk1.len = (size_t) len;
+ pk1.data = buf1;
+
+ if (kex_pkp_create(&kex, &pkp2, buf2) < 0) {
+ printf("Failed to create second key pair.\n");
+ goto fail_pkp1;
+ }
+
+ if (IS_HYBRID_KEM(algo))
+ ct_len = kex_kem_encap_raw(pk1, buf3, NID_sha256, s1);
+ else
+ ct_len = kex_kem_encap(pk1, buf3, NID_sha256, s1);
+
+ if (ct_len < 0) {
+ printf("Failed to encapsulate.\n");
+ goto fail_pkp2;
+ }
+
+ ct.len = (size_t) ct_len;
+ ct.data = buf3;
+
+ if (kex_kem_decap(pkp2, ct, NID_sha256, s2) == 0) {
+ if (memcmp(s1, s2, SYMMKEYSZ) == 0) {
+ printf("Wrong keypair produced same secret.\n");
+ goto fail_pkp2;
+ }
+ }
+
+ kex_pkp_destroy(pkp2);
+ kex_pkp_destroy(pkp1);
+
+ TEST_SUCCESS("(%s)", algo);
+
+ return TEST_RC_SUCCESS;
+ fail_pkp2:
+ kex_pkp_destroy(pkp2);
+ fail_pkp1:
+ kex_pkp_destroy(pkp1);
+ fail:
+ TEST_FAIL("(%s)", algo);
+ return TEST_RC_FAIL;
+}
+
+static int test_kex_kem_truncated_ciphertext(const char * algo)
+{
+ struct sec_config kex;
+ void * pkp;
+ buffer_t pk;
+ buffer_t ct;
+ ssize_t len;
+ ssize_t ct_len;
+ uint8_t buf1[MSGBUFSZ];
+ uint8_t buf2[MSGBUFSZ];
+ uint8_t s1[SYMMKEYSZ];
+ uint8_t s2[SYMMKEYSZ];
+
+ TEST_START("(%s)", algo);
+
+ memset(&kex, 0, sizeof(kex));
+ SET_KEX_ALGO(&kex, algo);
+
+ len = kex_pkp_create(&kex, &pkp, buf1);
+ if (len < 0) {
+ printf("Failed to create key pair.\n");
+ goto fail;
+ }
+
+ pk.len = (size_t) len;
+ pk.data = buf1;
+
+ if (IS_HYBRID_KEM(algo))
+ ct_len = kex_kem_encap_raw(pk, buf2, NID_sha256, s1);
+ else
+ ct_len = kex_kem_encap(pk, buf2, NID_sha256, s1);
+
+ if (ct_len < 0) {
+ printf("Failed to encapsulate.\n");
+ goto fail_pkp;
+ }
+
+ /* Truncate the ciphertext */
+ ct.len = (size_t) ct_len / 2;
+ ct.data = buf2;
+
+ if (kex_kem_decap(pkp, ct, NID_sha256, s2) == 0) {
+ printf("Should fail with truncated ciphertext.\n");
+ goto fail_pkp;
+ }
+
+ kex_pkp_destroy(pkp);
+
+ TEST_SUCCESS("(%s)", algo);
+
+ return TEST_RC_SUCCESS;
+ fail_pkp:
+ kex_pkp_destroy(pkp);
+ fail:
+ TEST_FAIL("(%s)", algo);
+ return TEST_RC_FAIL;
+}
+
+static int test_kex_kem_all(void)
+{
+ int ret = 0;
+ int i;
+
+ for (i = 0; kex_supported_nids[i] != NID_undef; i++) {
+ const char * algo = kex_nid_to_str(kex_supported_nids[i]);
+
+ if (!IS_KEM_ALGORITHM(algo))
+ continue;
+
+ ret |= test_kex_kem(algo);
+ }
+
+ return ret;
+}
+
+static int test_kex_kem_corrupted_ciphertext_all(void)
+{
+ int ret = 0;
+ int i;
+
+ for (i = 0; kex_supported_nids[i] != NID_undef; i++) {
+ const char * algo = kex_nid_to_str(kex_supported_nids[i]);
+
+ if (!IS_KEM_ALGORITHM(algo))
+ continue;
+
+ ret |= test_kex_kem_corrupted_ciphertext(algo);
+ }
+
+ return ret;
+}
+
+static int test_kex_kem_wrong_keypair_all(void)
+{
+ int ret = 0;
+ int i;
+
+ for (i = 0; kex_supported_nids[i] != NID_undef; i++) {
+ const char * algo = kex_nid_to_str(kex_supported_nids[i]);
+
+ if (!IS_KEM_ALGORITHM(algo))
+ continue;
+
+ ret |= test_kex_kem_wrong_keypair(algo);
+ }
+
+ return ret;
+}
+
+static int test_kex_kem_truncated_ciphertext_all(void)
+{
+ int ret = 0;
+ int i;
+
+ for (i = 0; kex_supported_nids[i] != NID_undef; i++) {
+ const char * algo = kex_nid_to_str(kex_supported_nids[i]);
+
+ if (!IS_KEM_ALGORITHM(algo))
+ continue;
+
+ ret |= test_kex_kem_truncated_ciphertext(algo);
+ }
+
+ return ret;
+}
+
+int kex_test_pqc(int argc,
+ char ** argv)
+{
+ int ret = 0;
+
+ (void) argc;
+ (void) argv;
+
+#ifdef HAVE_OPENSSL_PQC
+ ret |= test_kex_load_kem_privkey();
+ ret |= test_kex_load_kem_pubkey();
+ ret |= test_kex_kem_all();
+ ret |= test_kex_kem_corrupted_ciphertext_all();
+ ret |= test_kex_kem_wrong_keypair_all();
+ ret |= test_kex_kem_truncated_ciphertext_all();
+#else
+ (void) test_kex_load_kem_privkey;
+ (void) test_kex_load_kem_pubkey;
+ (void) test_kex_kem_all;
+ (void) test_kex_kem_corrupted_ciphertext_all;
+ (void) test_kex_kem_wrong_keypair_all;
+ (void) test_kex_kem_truncated_ciphertext_all;
+
+ ret = TEST_RC_SKIP;
+#endif
+ return ret;
+}
diff --git a/src/lib/tests/sockets_test.c b/src/lib/tests/sockets_test.c
index bbf2323b..952f9529 100644
--- a/src/lib/tests/sockets_test.c
+++ b/src/lib/tests/sockets_test.c
@@ -23,7 +23,7 @@
#define _POSIX_C_SOURCE 200112L
#include <ouroboros/sockets.h>
-#include <ouroboros/test.h>
+#include <test/test.h>
#include <assert.h>
#include <stdio.h>
diff --git a/src/lib/tests/time_test.c b/src/lib/tests/time_test.c
index 2b75b873..4685310b 100644
--- a/src/lib/tests/time_test.c
+++ b/src/lib/tests/time_test.c
@@ -22,7 +22,7 @@
#define _POSIX_C_SOURCE 200809L
-#include <ouroboros/test.h>
+#include <test/test.h>
#include <ouroboros/time.h>
#include <stdio.h>
diff --git a/src/lib/tests/tpm_test.c b/src/lib/tests/tpm_test.c
index 98d4fab3..41bce964 100644
--- a/src/lib/tests/tpm_test.c
+++ b/src/lib/tests/tpm_test.c
@@ -23,7 +23,7 @@
#include "tpm.c"
-#include <ouroboros/test.h>
+#include <test/test.h>
static void * test_func(void * o)
{
diff --git a/src/lib/utils.c b/src/lib/utils.c
index fd275f63..74f8ce4f 100644
--- a/src/lib/utils.c
+++ b/src/lib/utils.c
@@ -24,6 +24,7 @@
#include <ouroboros/utils.h>
+#include <ctype.h>
#include <stdlib.h>
#include <string.h>
@@ -67,6 +68,24 @@ char * path_strip(const char * src)
return dst;
}
+char * trim_whitespace(char * str)
+{
+ char * end;
+
+ while (isspace((unsigned char) *str))
+ str++;
+
+ if (*str == '\0')
+ return str;
+
+ /* Trim trailing space */
+ end = str + strlen(str) - 1;
+ while (end > str && isspace((unsigned char)*end))
+ *end-- = '\0';
+
+ return str;
+}
+
size_t argvlen(const char ** argv)
{
size_t argc = 0;