diff options
Diffstat (limited to 'src/lib')
| -rw-r--r-- | src/lib/config.h.in | 4 | ||||
| -rw-r--r-- | src/lib/crypt.c | 647 | ||||
| -rw-r--r-- | src/lib/crypt/openssl.c | 1190 | ||||
| -rw-r--r-- | src/lib/crypt/openssl.h | 99 | ||||
| -rw-r--r-- | src/lib/dev.c | 101 | ||||
| -rw-r--r-- | src/lib/pb/irm.proto | 9 | ||||
| -rw-r--r-- | src/lib/protobuf.c | 3 | ||||
| -rw-r--r-- | src/lib/serdes-irm.c | 24 | ||||
| -rw-r--r-- | src/lib/tests/CMakeLists.txt | 14 | ||||
| -rw-r--r-- | src/lib/tests/auth_test.c | 157 | ||||
| -rw-r--r-- | src/lib/tests/auth_test_pqc.c | 356 | ||||
| -rw-r--r-- | src/lib/tests/crypt_test.c | 262 | ||||
| -rw-r--r-- | src/lib/tests/hash_test.c | 2 | ||||
| -rw-r--r-- | src/lib/tests/kex_test.c | 844 | ||||
| -rw-r--r-- | src/lib/tests/kex_test_pqc.c | 549 | ||||
| -rw-r--r-- | src/lib/tests/sockets_test.c | 2 | ||||
| -rw-r--r-- | src/lib/tests/time_test.c | 2 | ||||
| -rw-r--r-- | src/lib/tests/tpm_test.c | 2 | ||||
| -rw-r--r-- | src/lib/utils.c | 19 |
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; |
