diff options
| -rw-r--r-- | include/ouroboros/crypt.h | 35 | ||||
| -rw-r--r-- | sec.conf.in | 2 | ||||
| -rw-r--r-- | src/irmd/oap/cli.c | 35 | ||||
| -rw-r--r-- | src/irmd/oap/hdr.c | 52 | ||||
| -rw-r--r-- | src/irmd/oap/hdr.h | 68 | ||||
| -rw-r--r-- | src/irmd/oap/srv.c | 106 | ||||
| -rw-r--r-- | src/irmd/oap/tests/oap_test.c | 6 | ||||
| -rw-r--r-- | src/irmd/oap/tests/oap_test_ml_dsa.c | 52 | ||||
| -rw-r--r-- | src/lib/crypt.c | 38 | ||||
| -rw-r--r-- | src/lib/crypt/openssl.c | 113 | ||||
| -rw-r--r-- | src/lib/crypt/openssl.h | 265 | ||||
| -rw-r--r-- | src/lib/tests/kex_test.c | 19 | ||||
| -rw-r--r-- | src/lib/tests/kex_test_ml_kem.c | 8 |
13 files changed, 443 insertions, 356 deletions
diff --git a/include/ouroboros/crypt.h b/include/ouroboros/crypt.h index 9feaa610..dd0c2853 100644 --- a/include/ouroboros/crypt.h +++ b/include/ouroboros/crypt.h @@ -65,8 +65,12 @@ #define NID_MLKEM512 1454 #define NID_MLKEM768 1455 #define NID_MLKEM1024 1456 -#define NID_X25519MLKEM768 2053 /* !! not in OpenSSL */ -#define NID_X448MLKEM1024 2054 /* !! not in OpenSSL */ + +/* Hybrid KEM NIDs: project-assigned range, OpenSSL defines none */ +#define NID_X25519MLKEM768 2053 +#define NID_X448MLKEM1024 2054 +#define NID_HYBRID_KEM_MIN NID_X25519MLKEM768 +#define NID_HYBRID_KEM_MAX NID_X448MLKEM1024 /* KDF NIDs (match OpenSSL values) */ #define NID_hkdf 1036 @@ -85,15 +89,8 @@ #define IS_KEM_ALGORITHM(algo) \ (strstr(algo, "ML-KEM") != NULL || strstr(algo, "MLKEM") != NULL) -#define IS_HYBRID_KEM(algo) \ - ((strstr(algo, "X25519") != NULL || strstr(algo, "X448") != NULL) && \ - strstr(algo, "MLKEM") != NULL) - -#define X25519MLKEM768_PKSZ 1216 /* 32 + 1184 */ -#define X25519MLKEM768_CTSZ 1120 /* 32 + 1088 */ -#define X25519MLKEM768_SKSZ 2432 /* 32 + 2400 */ -#define X448MLKEM1024_PKSZ 1624 /* 56 + 1568 */ -#define X448MLKEM1024_SKSZ 3224 /* 56 + 3168 */ +#define IS_HYBRID_KEM_NID(nid) kex_nid_is_hybrid(nid) +#define IS_HYBRID_KEM(algo) kex_nid_is_hybrid(kex_str_to_nid(algo)) #define CRYPT_KEY_BUFSZ 4096 /* Safe buffer for key material */ @@ -268,10 +265,11 @@ ssize_t kex_kem_encap(buffer_t pk, int kdf_nid, uint8_t * s); -ssize_t kex_kem_encap_raw(buffer_t pk, - uint8_t * ct, - int kdf_nid, - uint8_t * s); +ssize_t kex_kem_encap_raw(const char * algo, + buffer_t pk, + uint8_t * ct, + int kdf_nid, + uint8_t * s); int kex_kem_decap(void * pkp, buffer_t ct, @@ -281,13 +279,12 @@ int kex_kem_decap(void * pkp, int kex_get_algo_from_pk_der(buffer_t pk, char * algo); -int kex_get_algo_from_pk_raw(buffer_t pk, - char * algo); - int kex_validate_algo(const char * algo); int kex_validate_nid(int nid); +bool kex_nid_is_hybrid(uint16_t nid); + const char * kex_nid_to_str(uint16_t nid); uint16_t kex_str_to_nid(const char * algo); @@ -393,9 +390,11 @@ int crypt_load_pubkey_file_to_der(const char * path, buffer_t * buf); int crypt_load_pubkey_raw_file(const char * path, + const char * algo, buffer_t * buf); int crypt_load_privkey_raw_file(const char * path, + const char * algo, void ** key); int crypt_ct_cmp(const void * a, diff --git a/sec.conf.in b/sec.conf.in index 4796b72d..4115c850 100644 --- a/sec.conf.in +++ b/sec.conf.in @@ -137,7 +137,7 @@ # # File formats: # - Pure ML-KEM: PEM format (.pem extension) -# - Hybrid KEMs: Raw bytes (.raw extension) +# - Hybrid KEMs: Raw key bytes (.raw extension), for the kex= algorithm # # Examples: # --------- diff --git a/src/irmd/oap/cli.c b/src/irmd/oap/cli.c index 2203596f..02ad2005 100644 --- a/src/irmd/oap/cli.c +++ b/src/irmd/oap/cli.c @@ -51,7 +51,7 @@ struct oap_cli_ctx { uint8_t __id[OAP_ID_SIZE]; buffer_t id; char peer[NAME_SIZE + 1]; /* expected server name */ - uint8_t kex_buf[CRYPT_KEY_BUFSZ]; + uint8_t kex_buf[OAP_KEX_NIDSZ + CRYPT_KEY_BUFSZ]; uint8_t req_hash[MAX_HASH_SIZE]; size_t req_hash_len; int req_md_nid; @@ -113,13 +113,13 @@ int load_server_kem_pk(const char * name, assert(cfg != NULL); assert(pk != NULL); - ext = IS_HYBRID_KEM(cfg->x.str) ? "raw" : "pem"; + ext = IS_HYBRID_KEM_NID(cfg->x.nid) ? "raw" : "pem"; snprintf(path, sizeof(path), OUROBOROS_CLI_CRT_DIR "/%s/kex.srv.pub.%s", name, ext); - if (IS_HYBRID_KEM(cfg->x.str)) { - if (crypt_load_pubkey_raw_file(path, pk) < 0) { + if (IS_HYBRID_KEM_NID(cfg->x.nid)) { + if (crypt_load_pubkey_raw_file(path, cfg->x.str, pk) < 0) { log_err("Failed to load %s pubkey from %s.", ext, path); return -1; } @@ -173,12 +173,14 @@ static int do_client_kex_prepare_kem_encap(const char * server_name, return -ECRYPT; } - if (IS_HYBRID_KEM(scfg->x.str)) - len = kex_kem_encap_raw(server_pk, kex->data, + if (IS_HYBRID_KEM_NID(scfg->x.nid)) { + len = kex_kem_encap_raw(scfg->x.str, server_pk, + kex->data + OAP_KEX_NIDSZ, scfg->k.nid, key_buf); - else - len = kex_kem_encap(server_pk, kex->data, - scfg->k.nid, key_buf); + len = oap_kex_tag_nid(kex->data, scfg->x.nid, len); + } else { + len = kex_kem_encap(server_pk, kex->data, scfg->k.nid, key_buf); + } freebuf(server_pk); @@ -210,7 +212,13 @@ static int do_client_kex_prepare_kem_decap(struct oap_cli_ctx * s) ssize_t len; /* Server encaps: generate keypair, send PK */ - len = kex_pkp_create(scfg, &s->pkp, kex->data); + if (IS_HYBRID_KEM_NID(scfg->x.nid)) { + len = kex_pkp_create(scfg, &s->pkp, kex->data + OAP_KEX_NIDSZ); + len = oap_kex_tag_nid(kex->data, scfg->x.nid, len); + } else { + len = kex_pkp_create(scfg, &s->pkp, kex->data); + } + if (len < 0) { log_err_id(id, "Failed to generate KEM keypair."); return -ECRYPT; @@ -421,6 +429,13 @@ static int do_client_kex_complete_kem(struct oap_cli_ctx * s, ct.data = peer_hdr->kex.data; ct.len = peer_hdr->kex.len; + if (IS_HYBRID_KEM_NID(scfg->x.nid)) { + if (oap_kex_strip_nid(&ct, scfg->x.nid) < 0) { + log_err_id(id, "KEX algo mismatch in CT."); + return -ECRYPT; + } + } + if (kex_kem_decap(s->pkp, ct, scfg->k.nid, key_buf) < 0) { log_err_id(id, "Failed to decapsulate KEM."); return -ECRYPT; diff --git a/src/irmd/oap/hdr.c b/src/irmd/oap/hdr.c index 0cff345c..d037d7b8 100644 --- a/src/irmd/oap/hdr.c +++ b/src/irmd/oap/hdr.c @@ -211,6 +211,56 @@ void oap_hdr_fini(struct oap_hdr * oap_hdr) memset(oap_hdr, 0, sizeof(*oap_hdr)); } +uint16_t oap_kex_nid(buffer_t kex) +{ + uint16_t nid; + + if (kex.len <= OAP_KEX_NIDSZ) + return NID_undef; + + memcpy(&nid, kex.data, sizeof(nid)); + + return ntoh16(nid); +} + +void oap_kex_set_nid(uint8_t * buf, + uint16_t nid) +{ + uint16_t v; + + assert(buf != NULL); + + v = hton16(nid); + + memcpy(buf, &v, sizeof(v)); +} + +int oap_kex_strip_nid(buffer_t * kex, + uint16_t nid) +{ + assert(kex != NULL); + + if (oap_kex_nid(*kex) != nid) + return -1; + + kex->data += OAP_KEX_NIDSZ; + kex->len -= OAP_KEX_NIDSZ; + + return 0; +} + +ssize_t oap_kex_tag_nid(uint8_t * buf, + uint16_t nid, + ssize_t len) +{ + if (len < 0) + return len; + + oap_kex_set_nid(buf, nid); + + return len + OAP_KEX_NIDSZ; +} + int oap_hdr_copy_data(const struct oap_hdr * hdr, buffer_t * out) { @@ -286,7 +336,7 @@ static void write_oap_fixed(uint8_t * buf, kex_len = (uint16_t) hdr->kex.len; if (hdr->kex.len > 0 && IS_KEM_ALGORITHM(scfg->x.str)) { - if (IS_HYBRID_KEM(scfg->x.str)) + if (IS_HYBRID_KEM_NID(scfg->x.nid)) kex_len |= OAP_KEX_FMT_BIT; if (scfg->x.mode == KEM_MODE_CLIENT_ENCAP) kex_len |= OAP_KEX_ROLE_BIT; diff --git a/src/irmd/oap/hdr.h b/src/irmd/oap/hdr.h index 66fbac9a..4c5f885a 100644 --- a/src/irmd/oap/hdr.h +++ b/src/irmd/oap/hdr.h @@ -32,10 +32,13 @@ #define OAP_ID_SIZE (16) #define OAP_HDR_MIN_SIZE (OAP_ID_SIZE + sizeof(uint64_t) + 6 * sizeof(uint16_t)) -#define OAP_KEX_FMT_BIT 0x8000 /* bit 15: 0=X.509 DER, 1=Raw */ +#define OAP_KEX_FMT_BIT 0x8000 /* bit 15: 0=X.509 DER, 1=Raw: NID + bytes */ #define OAP_KEX_ROLE_BIT 0x4000 /* bit 14: 0=Server encaps, 1=Client encaps */ #define OAP_KEX_LEN_MASK 0x3FFF /* bits 0-13: Length (0-16383 bytes) */ +/* Raw format kex payloads lead with the algorithm NID */ +#define OAP_KEX_NIDSZ sizeof(uint16_t) + #define OAP_KEX_ROLE(hdr) (hdr->kex_flags.role) #define OAP_KEX_FMT(hdr) (hdr->kex_flags.fmt) @@ -160,40 +163,55 @@ struct oap_hdr { }; -void oap_hdr_init(struct oap_hdr * hdr, - buffer_t id, - uint8_t * kex_buf, - buffer_t data, - uint16_t nid); +void oap_hdr_init(struct oap_hdr * hdr, + buffer_t id, + uint8_t * kex_buf, + buffer_t data, + uint16_t nid); + +void oap_hdr_fini(struct oap_hdr * oap_hdr); + +/* NID leading a raw format kex payload, NID_undef if too short */ +uint16_t oap_kex_nid(buffer_t kex); + +void oap_kex_set_nid(uint8_t * buf, + uint16_t nid); + +/* Verify the NID leading a raw kex payload, then strip it */ +int oap_kex_strip_nid(buffer_t * kex, + uint16_t nid); -void oap_hdr_fini(struct oap_hdr * oap_hdr); +/* Tag a raw payload written at buf + OAP_KEX_NIDSZ, len passthrough */ +ssize_t oap_kex_tag_nid(uint8_t * buf, + uint16_t nid, + ssize_t len); /* oap_hdr_encode option flags */ #define OAP_ENC_REKEY (1U << 0) /* signed, cert-less re-key packet */ -int oap_hdr_encode(struct oap_hdr * hdr, - void * pkp, - void * crt, - struct sec_config * scfg, - buffer_t rsp_tag, - int req_md_nid, - const uint8_t * seal_key, - int flags); +int oap_hdr_encode(struct oap_hdr * hdr, + void * pkp, + void * crt, + struct sec_config * scfg, + buffer_t rsp_tag, + int req_md_nid, + const uint8_t * seal_key, + int flags); -int oap_hdr_decode(struct oap_hdr * hdr, - buffer_t buf, - int req_md_nid, - bool rekey); +int oap_hdr_decode(struct oap_hdr * hdr, + buffer_t buf, + int req_md_nid, + bool rekey); /* Decrypt a sealed response identity block; fills data, crt and sig. */ -int oap_hdr_unseal(struct oap_hdr * hdr, - const uint8_t * key); +int oap_hdr_unseal(struct oap_hdr * hdr, + const uint8_t * key); -void debug_oap_hdr_rcv(const struct oap_hdr * hdr); +void debug_oap_hdr_rcv(const struct oap_hdr * hdr); -void debug_oap_hdr_snd(const struct oap_hdr * hdr); +void debug_oap_hdr_snd(const struct oap_hdr * hdr); -int oap_hdr_copy_data(const struct oap_hdr * hdr, - buffer_t * out); +int oap_hdr_copy_data(const struct oap_hdr * hdr, + buffer_t * out); #endif /* OUROBOROS_IRMD_OAP_HDR_H */ diff --git a/src/irmd/oap/srv.c b/src/irmd/oap/srv.c index f78e0874..0ceba7b6 100644 --- a/src/irmd/oap/srv.c +++ b/src/irmd/oap/srv.c @@ -51,9 +51,9 @@ extern int load_srv_credentials(const struct name_info * info, void ** crt); extern int load_srv_sec_config(const struct name_info * info, struct sec_config * cfg); -extern int load_server_kem_keypair(const char * name, - bool raw_fmt, - void ** pkp); +extern int load_server_kem_keypair(const char * name, + struct sec_config * cfg, + void ** pkp); #else int load_srv_credentials(const struct name_info * info, @@ -79,23 +79,26 @@ int load_srv_sec_config(const struct name_info * info, return load_sec_config(info->name, info->s.sec, cfg); } -int load_server_kem_keypair(const char * name, - bool raw_fmt, - void ** pkp) +int load_server_kem_keypair(const char * name, + struct sec_config * cfg, + void ** pkp) { char path[PATH_MAX]; const char * ext; + bool raw_fmt; assert(name != NULL); + assert(cfg != NULL); assert(pkp != NULL); + raw_fmt = IS_HYBRID_KEM_NID(cfg->x.nid); ext = raw_fmt ? "raw" : "pem"; snprintf(path, sizeof(path), OUROBOROS_SRV_CRT_DIR "/%s/kex.key.%s", name, ext); if (raw_fmt) { - if (crypt_load_privkey_raw_file(path, pkp) < 0) { + if (crypt_load_privkey_raw_file(path, cfg->x.str, pkp) < 0) { log_err("Failed to load %s keypair from %s.", ext, path); return -ECRYPT; @@ -109,6 +112,7 @@ int load_server_kem_keypair(const char * name, } log_dbg("Loaded server KEM keypair from %s.", path); + return 0; } @@ -117,18 +121,19 @@ int load_server_kem_keypair(const char * name, static int get_algo_from_peer_key(const struct oap_hdr * peer_hdr, char * algo_buf) { - uint8_t * id = peer_hdr->id.data; - int ret; + uint8_t * id = peer_hdr->id.data; + const char * name; if (OAP_KEX_IS_RAW_FMT(peer_hdr)) { - ret = kex_get_algo_from_pk_raw(peer_hdr->kex, algo_buf); - if (ret < 0) { - log_err_id(id, "Failed to get algo from raw key."); + name = kex_nid_to_str(oap_kex_nid(peer_hdr->kex)); + if (name == NULL || !IS_HYBRID_KEM(name)) { + log_err_id(id, "Bad algo NID in raw key."); return -ECRYPT; } + + strcpy(algo_buf, name); } else { - ret = kex_get_algo_from_pk_der(peer_hdr->kex, algo_buf); - if (ret < 0) { + if (kex_get_algo_from_pk_der(peer_hdr->kex, algo_buf) < 0) { log_err_id(id, "Failed to get algo from DER key."); return -ECRYPT; } @@ -216,15 +221,20 @@ static int do_server_kem_decap(const struct name_info * info, int ret; uint8_t * id = peer_hdr->id.data; - ret = load_server_kem_keypair(info->name, - peer_hdr->kex_flags.fmt, - &server_pkp); - if (ret < 0) - return ret; - ct.data = peer_hdr->kex.data; ct.len = peer_hdr->kex.len; + if (IS_HYBRID_KEM_NID(scfg->x.nid)) { + if (oap_kex_strip_nid(&ct, scfg->x.nid) < 0) { + log_err_id(id, "KEX algo mismatch in CT."); + return -ECRYPT; + } + } + + ret = load_server_kem_keypair(info->name, scfg, &server_pkp); + if (ret < 0) + return ret; + ret = kex_kem_decap(server_pkp, ct, scfg->k.nid, sk->key); crypt_free_key(server_pkp); @@ -251,12 +261,20 @@ static int do_server_kem_encap(const struct oap_hdr * peer_hdr, client_pk.data = peer_hdr->kex.data; client_pk.len = peer_hdr->kex.len; - if (IS_HYBRID_KEM(scfg->x.str)) - ct_len = kex_kem_encap_raw(client_pk, kex->data, + if (IS_HYBRID_KEM_NID(scfg->x.nid)) { + if (oap_kex_strip_nid(&client_pk, scfg->x.nid) < 0) { + log_err_id(id, "KEX algo mismatch in PK."); + return -ECRYPT; + } + + ct_len = kex_kem_encap_raw(scfg->x.str, client_pk, + kex->data + OAP_KEX_NIDSZ, scfg->k.nid, sk->key); - else + ct_len = oap_kex_tag_nid(kex->data, scfg->x.nid, ct_len); + } else { ct_len = kex_kem_encap(client_pk, kex->data, scfg->k.nid, sk->key); + } if (ct_len < 0) { log_err_id(id, "Failed to encapsulate KEM."); @@ -385,27 +403,27 @@ int oap_srv_process(const struct name_info * info, const buffer_t * cached_crt, buffer_t * peer_crt) { - struct oap_hdr peer_hdr; - struct oap_hdr local_hdr; - struct sec_config scfg; - uint8_t kex_buf[CRYPT_KEY_BUFSZ]; - uint8_t hash_buf[MAX_HASH_SIZE]; - uint8_t kc_buf[MAX_HASH_SIZE]; - uint8_t resp_hash_buf[MAX_HASH_SIZE]; - uint8_t hs_key[SYMMKEYSZ]; - const uint8_t * seal_key = NULL; - buffer_t req_hash = BUF_INIT; - buffer_t resp_hash = BUF_INIT; - buffer_t crt_der = BUF_INIT; - buffer_t rsp_tag = BUF_INIT; - ssize_t hash_ret; - char cli_name[NAME_SIZE + 1]; - uint8_t * id; - void * pkp = NULL; - void * crt = NULL; - int req_md_nid; - int enc_flags = 0; - int ret; + struct oap_hdr peer_hdr; + struct oap_hdr local_hdr; + struct sec_config scfg; + uint8_t kex_buf[OAP_KEX_NIDSZ + CRYPT_KEY_BUFSZ]; + uint8_t hash_buf[MAX_HASH_SIZE]; + uint8_t kc_buf[MAX_HASH_SIZE]; + uint8_t resp_hash_buf[MAX_HASH_SIZE]; + uint8_t hs_key[SYMMKEYSZ]; + const uint8_t * seal_key = NULL; + buffer_t req_hash = BUF_INIT; + buffer_t resp_hash = BUF_INIT; + buffer_t crt_der = BUF_INIT; + buffer_t rsp_tag = BUF_INIT; + ssize_t hash_ret; + char cli_name[NAME_SIZE + 1]; + uint8_t * id; + void * pkp = NULL; + void * crt = NULL; + int req_md_nid; + int enc_flags = 0; + int ret; assert(info != NULL); assert(rsp_buf != NULL); diff --git a/src/irmd/oap/tests/oap_test.c b/src/irmd/oap/tests/oap_test.c index b24bb786..3e2bae56 100644 --- a/src/irmd/oap/tests/oap_test.c +++ b/src/irmd/oap/tests/oap_test.c @@ -81,9 +81,9 @@ int mock_load_credentials(void ** pkp, } /* Stub KEM functions - ECDSA tests don't use KEM */ -int load_server_kem_keypair(__attribute__((unused)) const char * name, - __attribute__((unused)) bool raw_fmt, - __attribute__((unused)) void ** pkp) +int load_server_kem_keypair(__attribute__((unused)) const char * name, + __attribute__((unused)) struct sec_config * cfg, + __attribute__((unused)) void ** pkp) { return -1; } diff --git a/src/irmd/oap/tests/oap_test_ml_dsa.c b/src/irmd/oap/tests/oap_test_ml_dsa.c index 477b712b..b9132b13 100644 --- a/src/irmd/oap/tests/oap_test_ml_dsa.c +++ b/src/irmd/oap/tests/oap_test_ml_dsa.c @@ -37,6 +37,7 @@ #include <test/certs/ml_dsa.h> +#include "oap/hdr.h" #include "oap.h" #include "common.h" @@ -97,16 +98,16 @@ int mock_load_credentials(void ** pkp, return 0; } -int load_server_kem_keypair(const char * name, - bool raw_fmt, - void ** pkp) +int load_server_kem_keypair(const char * name, + struct sec_config * cfg, + void ** pkp) { #ifdef HAVE_OPENSSL struct sec_config local_cfg; ssize_t pk_len; (void) name; - (void) raw_fmt; + (void) cfg; /* * Uses reference counting. The caller will call @@ -150,7 +151,7 @@ int load_server_kem_keypair(const char * name, #else (void) name; - (void) raw_fmt; + (void) cfg; (void) pkp; return -1; #endif @@ -720,6 +721,44 @@ static int test_oap_kem_srv_uncfg_all(void) return ret; } +/* Server must reject a raw kex payload tagged with a bad NID */ +static int test_oap_kem_bad_nid_tag(uint16_t bad_nid) +{ + struct oap_test_ctx ctx; + + test_cfg_init(NID_X25519MLKEM768, NID_aes_256_gcm, + get_random_kdf(), SRV_ENCAP, NO_AUTH); + + TEST_START("(%u)", bad_nid); + + if (oap_test_setup_kem(&ctx, root_ca_crt_ml, im_ca_crt_ml) < 0) + goto fail; + + if (oap_cli_prepare_ctx(&ctx) < 0) { + printf("Client prepare failed.\n"); + goto fail_cleanup; + } + + /* NO_AUTH request: raw kex starts after the fixed header */ + oap_kex_set_nid(ctx.req_hdr.data + OAP_HDR_MIN_SIZE, bad_nid); + + if (oap_srv_process_ctx(&ctx) == 0) { + printf("Server accepted bad NID tag %u.\n", bad_nid); + goto fail_cleanup; + } + + oap_test_teardown_kem(&ctx); + + TEST_SUCCESS("(%u)", bad_nid); + + return TEST_RC_SUCCESS; + fail_cleanup: + oap_test_teardown_kem(&ctx); + fail: + TEST_FAIL("(%u)", bad_nid); + return TEST_RC_FAIL; +} + /* Client encap must be rejected when the server has no KEX config */ static int test_oap_kem_cli_encap_srv_uncfg(void) { @@ -782,6 +821,8 @@ int oap_test_ml_dsa(int argc, ret |= test_oap_kem_srv_uncfg_all(); ret |= test_oap_kem_cli_encap_srv_uncfg(); + ret |= test_oap_kem_bad_nid_tag(NID_X25519); /* known, not hybrid */ + ret |= test_oap_kem_bad_nid_tag(0xFFFF); /* unknown */ ret |= test_oap_corrupted_request(); ret |= test_oap_corrupted_response(); @@ -812,6 +853,7 @@ int oap_test_ml_dsa(int argc, (void) test_oap_kem_srv_uncfg; (void) test_oap_kem_srv_uncfg_all; (void) test_oap_kem_cli_encap_srv_uncfg; + (void) test_oap_kem_bad_nid_tag; (void) test_oap_corrupted_request; (void) test_oap_corrupted_response; (void) test_oap_truncated_request; diff --git a/src/lib/crypt.c b/src/lib/crypt.c index bc3dcd16..35007ef3 100644 --- a/src/lib/crypt.c +++ b/src/lib/crypt.c @@ -378,14 +378,16 @@ ssize_t kex_kem_encap(buffer_t pk, #endif } -ssize_t kex_kem_encap_raw(buffer_t pk, - uint8_t * ct, - int kdf, - uint8_t * s) +ssize_t kex_kem_encap_raw(const char * algo, + buffer_t pk, + uint8_t * ct, + int kdf, + uint8_t * s) { #ifdef HAVE_OPENSSL - return openssl_kem_encap_raw(pk, ct, kdf, s); + return openssl_kem_encap_raw(algo, pk, ct, kdf, s); #else + (void) algo; (void) pk; (void) ct; (void) kdf; @@ -427,19 +429,6 @@ int kex_get_algo_from_pk_der(buffer_t pk, #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) @@ -547,6 +536,11 @@ int kex_validate_nid(int nid) return -ENOTSUP; } +bool kex_nid_is_hybrid(uint16_t nid) +{ + return nid >= NID_HYBRID_KEM_MIN && nid <= NID_HYBRID_KEM_MAX; +} + const char * md_nid_to_str(uint16_t nid) { const struct nid_map * p; @@ -1032,14 +1026,16 @@ int crypt_load_pubkey_file_to_der(const char * path, } int crypt_load_pubkey_raw_file(const char * path, + const char * algo, buffer_t * buf) { assert(buf != NULL); #ifdef HAVE_OPENSSL - return openssl_load_pubkey_raw_file(path, buf); + return openssl_load_pubkey_raw_file(path, algo, buf); #else (void) path; + (void) algo; buf->data = NULL; buf->len = 0; @@ -1048,14 +1044,16 @@ int crypt_load_pubkey_raw_file(const char * path, } int crypt_load_privkey_raw_file(const char * path, + const char * algo, void ** key) { *key = NULL; #ifdef HAVE_OPENSSL - return openssl_load_privkey_raw_file(path, key); + return openssl_load_privkey_raw_file(path, algo, key); #else (void) path; + (void) algo; return 0; #endif diff --git a/src/lib/crypt/openssl.c b/src/lib/crypt/openssl.c index 7716f27f..8ec3b40f 100644 --- a/src/lib/crypt/openssl.c +++ b/src/lib/crypt/openssl.c @@ -721,23 +721,6 @@ static int __openssl_kem_gen_key(const char * algo, return -ECRYPT; } -/* 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) { @@ -900,18 +883,19 @@ ssize_t openssl_kem_encap(buffer_t pk, 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) +/* Hybrid KEM encapsulation: NID-tagged raw-encoded public key */ +ssize_t openssl_kem_encap_raw(const char * algo, + buffer_t pk, + uint8_t * ct, + int kdf, + uint8_t * s) { - EVP_PKEY * pub; - const char * algo; - uint8_t salt[HKDF_SALT_LEN]; - buffer_t salt_b; - ssize_t ret; + EVP_PKEY * pub; + uint8_t salt[HKDF_SALT_LEN]; + buffer_t salt_b; + ssize_t ret; + assert(algo != NULL); assert(pk.data != NULL); assert(ct != NULL); assert(s != NULL); @@ -922,10 +906,6 @@ ssize_t openssl_kem_encap_raw(buffer_t pk, if (derive_salt_from_pk_bytes(pk, salt_b) < 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) @@ -1071,23 +1051,6 @@ int openssl_get_algo_from_pk_der(buffer_t pk, return -ECRYPT; } -int openssl_get_algo_from_pk_raw(buffer_t pk, - char * algo) -{ - 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_dhe_derive(EVP_PKEY * pkp, buffer_t pk, int kdf, @@ -1519,14 +1482,16 @@ int openssl_load_pubkey_str(const char * str, } int openssl_load_pubkey_raw_file(const char * path, + const char * algo, buffer_t * buf) { - FILE * fp; - uint8_t tmp_buf[CRYPT_KEY_BUFSZ]; - size_t bytes_read; - const char * algo; + FILE * fp; + uint8_t tmp_buf[CRYPT_KEY_BUFSZ]; + size_t bytes_read; + EVP_PKEY * chk; assert(path != NULL); + assert(algo != NULL); assert(buf != NULL); fp = fopen(path, "rb"); @@ -1539,14 +1504,19 @@ int openssl_load_pubkey_raw_file(const char * path, pthread_cleanup_pop(false); - if (bytes_read == 0) + /* A full buffer means the file was truncated */ + if (bytes_read == 0 || bytes_read == CRYPT_KEY_BUFSZ) goto fail_read; - /* Validate that this is a known hybrid KEM format */ - algo = __openssl_hybrid_algo_from_len(bytes_read); - if (algo == NULL) + /* Trial import: reject bad keys at load time */ + chk = EVP_PKEY_new_raw_public_key_ex(NULL, algo, NULL, + tmp_buf, bytes_read); + + if (chk == NULL) goto fail_read; + EVP_PKEY_free(chk); + buf->data = malloc(bytes_read); if (buf->data == NULL) goto fail_malloc; @@ -1565,21 +1535,6 @@ int openssl_load_pubkey_raw_file(const char * path, 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; -} - /* Wipe the raw-key staging buffer if a cancel aborts the read. */ static void __cleanse_key_buf(void * o) { @@ -1587,15 +1542,16 @@ static void __cleanse_key_buf(void * o) } int openssl_load_privkey_raw_file(const char * path, + const char * algo, void ** key) { - FILE * fp; - uint8_t tmp_buf[CRYPT_KEY_BUFSZ]; - size_t bytes_read; - const char * algo; - EVP_PKEY * pkey; + FILE * fp; + uint8_t tmp_buf[CRYPT_KEY_BUFSZ]; + size_t bytes_read; + EVP_PKEY * pkey; assert(path != NULL); + assert(algo != NULL); assert(key != NULL); fp = fopen(path, "rb"); @@ -1613,11 +1569,6 @@ int openssl_load_privkey_raw_file(const char * path, 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 */ diff --git a/src/lib/crypt/openssl.h b/src/lib/crypt/openssl.h index e5cc35f7..6857e39b 100644 --- a/src/lib/crypt/openssl.h +++ b/src/lib/crypt/openssl.h @@ -28,72 +28,70 @@ struct ossl_crypt_ctx; -ssize_t openssl_pkp_create(const char * algo, - EVP_PKEY ** pkp, - uint8_t * pk); +ssize_t openssl_pkp_create(const char * algo, + EVP_PKEY ** pkp, + uint8_t * pk); -void openssl_pkp_destroy(EVP_PKEY * pkp); +void openssl_pkp_destroy(EVP_PKEY * pkp); -int openssl_dhe_derive(EVP_PKEY * pkp, - buffer_t pk, - int kdf_nid, - uint8_t * s); +int openssl_dhe_derive(EVP_PKEY * pkp, + buffer_t pk, + int kdf_nid, + uint8_t * s); -ssize_t openssl_kem_encap(buffer_t pk, - uint8_t * ct, - int kdf_nid, - uint8_t * s); +ssize_t openssl_kem_encap(buffer_t pk, + uint8_t * ct, + int kdf_nid, + uint8_t * s); /* 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); - -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_seal(struct ossl_crypt_ctx * ctx, - const uint8_t * key, - const uint8_t * nonce, - buffer_t aad, - buffer_t in, - uint8_t * out, - uint8_t * tag); - -int openssl_open(struct ossl_crypt_ctx * ctx, - const uint8_t * key, - const uint8_t * nonce, - buffer_t aad, - buffer_t in, - const uint8_t * tag, - buffer_t * out); - -int openssl_oneshot_seal(int nid, - const uint8_t * key, - const uint8_t * nonce, - buffer_t aad, - buffer_t in, - buffer_t * out); - -int openssl_oneshot_open(int nid, - const uint8_t * key, - const uint8_t * nonce, - buffer_t aad, - buffer_t in, - buffer_t * out); - -int openssl_hkdf_expand(buffer_t key, - buffer_t info, - buffer_t out); +ssize_t openssl_kem_encap_raw(const char * algo, + buffer_t pk, + uint8_t * ct, + int kdf_nid, + uint8_t * s); + +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_seal(struct ossl_crypt_ctx * ctx, + const uint8_t * key, + const uint8_t * nonce, + buffer_t aad, + buffer_t in, + uint8_t * out, + uint8_t * tag); + +int openssl_open(struct ossl_crypt_ctx * ctx, + const uint8_t * key, + const uint8_t * nonce, + buffer_t aad, + buffer_t in, + const uint8_t * tag, + buffer_t * out); + +int openssl_oneshot_seal(int nid, + const uint8_t * key, + const uint8_t * nonce, + buffer_t aad, + buffer_t in, + buffer_t * out); + +int openssl_oneshot_open(int nid, + const uint8_t * key, + const uint8_t * nonce, + buffer_t aad, + buffer_t in, + buffer_t * out); + +int openssl_hkdf_expand(buffer_t key, + buffer_t info, + buffer_t out); struct ossl_crypt_ctx * openssl_crypt_create_ctx(struct crypt_sk * sk); @@ -102,112 +100,113 @@ void openssl_crypt_destroy_ctx(struct ossl_crypt_ctx * ctx); int openssl_crypt_get_tagsz(struct ossl_crypt_ctx * ctx); /* AUTHENTICATION */ +int openssl_load_crt_file(const char * path, + void ** crt); -int openssl_load_crt_file(const char * path, - void ** crt); +int openssl_load_crt_str(const char * str, + void ** crt); -int openssl_load_crt_str(const char * str, - void ** crt); +int openssl_load_crt_der(buffer_t buf, + void ** crt); -int openssl_load_crt_der(buffer_t buf, - void ** crt); +int openssl_get_pubkey_crt(void * crt, + void ** pk); -int openssl_get_pubkey_crt(void * crt, - void ** pk); +void openssl_free_crt(void * crt); -void openssl_free_crt(void * crt); +int openssl_load_privkey_file(const char * path, + void ** key); -int openssl_load_privkey_file(const char * path, - void ** key); +int openssl_load_privkey_str(const char * str, + void ** key); -int openssl_load_privkey_str(const char * str, - void ** key); +int openssl_load_pubkey_file(const char * path, + void ** key); -int openssl_load_pubkey_file(const char * path, - void ** key); +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, + const char * algo, + buffer_t * buf); -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_load_privkey_raw_file(const char * path, + const char * algo, + void ** key); -int openssl_cmp_key(const EVP_PKEY * key1, - const EVP_PKEY * key2); +int openssl_cmp_key(const EVP_PKEY * key1, + const EVP_PKEY * key2); -void openssl_free_key(EVP_PKEY * key); +void openssl_free_key(EVP_PKEY * key); -int openssl_check_crt_name(void * crt, - const char * name); +int openssl_check_crt_name(void * crt, + const char * name); -int openssl_get_crt_name(void * crt, - char * name); +int openssl_get_crt_name(void * crt, + char * name); -int openssl_crt_str(const void * crt, - char * str); +int openssl_crt_str(const void * crt, + char * str); -int openssl_crt_der(const void * crt, - buffer_t * buf); +int openssl_crt_der(const void * crt, + buffer_t * buf); -void * openssl_auth_create_store(void); +void * openssl_auth_create_store(void); -void openssl_auth_destroy_store(void * store); +void openssl_auth_destroy_store(void * store); -int openssl_auth_add_crt_to_store(void * store, - void * crt); +int openssl_auth_add_crt_to_store(void * store, + void * crt); -void * openssl_auth_create_chain(void); +void * openssl_auth_create_chain(void); -void openssl_auth_destroy_chain(void * chain); +void openssl_auth_destroy_chain(void * chain); -int openssl_auth_add_crt_to_chain(void * chain, - void * crt); +int openssl_auth_add_crt_to_chain(void * chain, + void * crt); -int openssl_verify_crt(void * store, - void * untrusted, - void * crt); +int openssl_verify_crt(void * store, + void * untrusted, + void * crt); -int openssl_verify_crt_pin(void * store, - void * untrusted, - void * crt, - void * pin); +int openssl_verify_crt_pin(void * store, + void * untrusted, + void * crt, + void * pin); -bool openssl_pk_requires_md(const EVP_PKEY * pk); +bool openssl_pk_requires_md(const EVP_PKEY * pk); -int openssl_sign(EVP_PKEY * pkp, - int md_nid, - buffer_t msg, - buffer_t * sig); +int openssl_sign(EVP_PKEY * pkp, + int md_nid, + buffer_t msg, + buffer_t * sig); -int openssl_verify_sig(EVP_PKEY * pk, - int md_nid, - 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_digest(int md_nid, + buffer_t in, + uint8_t * out); -ssize_t openssl_md_len(int md_nid); +ssize_t openssl_md_len(int md_nid); /* Secure memory allocation */ -int openssl_secure_malloc_init(size_t max, - size_t guard); +int openssl_secure_malloc_init(size_t max, + size_t guard); -void openssl_secure_malloc_fini(void); +void openssl_secure_malloc_fini(void); -void * openssl_secure_malloc(size_t size); +void * openssl_secure_malloc(size_t size); -void openssl_secure_free(void * ptr, - size_t size); +void openssl_secure_free(void * ptr, + size_t size); -void openssl_secure_clear(void * ptr, - size_t size); +void openssl_secure_clear(void * ptr, + size_t size); -void openssl_cleanup(void); +void openssl_cleanup(void); #endif /* OUROBOROS_LIB_CRYPT_OPENSSL_H */ diff --git a/src/lib/tests/kex_test.c b/src/lib/tests/kex_test.c index 38835265..d7629f95 100644 --- a/src/lib/tests/kex_test.c +++ b/src/lib/tests/kex_test.c @@ -182,17 +182,9 @@ static int test_kex_get_algo_from_pk(const char * algo) 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; - } + 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 */ @@ -221,6 +213,11 @@ static int test_kex_get_algo_from_pk_all(void) for (i = 0; kex_supported_nids[i] != NID_undef; i++) { const char * algo = kex_nid_to_str(kex_supported_nids[i]); + + /* Raw hybrid PKs are opaque, OAP carries the NID */ + if (IS_HYBRID_KEM(algo)) + continue; + ret |= test_kex_get_algo_from_pk(algo); } diff --git a/src/lib/tests/kex_test_ml_kem.c b/src/lib/tests/kex_test_ml_kem.c index 1f7d7531..7521c5d5 100644 --- a/src/lib/tests/kex_test_ml_kem.c +++ b/src/lib/tests/kex_test_ml_kem.c @@ -220,7 +220,7 @@ static int test_kex_kem(const char * algo) pk.data = buf1; if (IS_HYBRID_KEM(algo)) - ct_len = kex_kem_encap_raw(pk, buf2, kdf, s1); + ct_len = kex_kem_encap_raw(algo, pk, buf2, kdf, s1); else ct_len = kex_kem_encap(pk, buf2, kdf, s1); @@ -285,7 +285,7 @@ static int test_kex_kem_corrupted_ciphertext(const char * algo) pk.data = buf1; if (IS_HYBRID_KEM(algo)) - ct_len = kex_kem_encap_raw(pk, buf2, kdf, s1); + ct_len = kex_kem_encap_raw(algo, pk, buf2, kdf, s1); else ct_len = kex_kem_encap(pk, buf2, kdf, s1); @@ -360,7 +360,7 @@ static int test_kex_kem_wrong_keypair(const char * algo) } if (IS_HYBRID_KEM(algo)) - ct_len = kex_kem_encap_raw(pk1, buf3, NID_sha256, s1); + ct_len = kex_kem_encap_raw(algo, pk1, buf3, NID_sha256, s1); else ct_len = kex_kem_encap(pk1, buf3, NID_sha256, s1); @@ -422,7 +422,7 @@ static int test_kex_kem_truncated_ciphertext(const char * algo) pk.data = buf1; if (IS_HYBRID_KEM(algo)) - ct_len = kex_kem_encap_raw(pk, buf2, NID_sha256, s1); + ct_len = kex_kem_encap_raw(algo, pk, buf2, NID_sha256, s1); else ct_len = kex_kem_encap(pk, buf2, NID_sha256, s1); |
