summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/ouroboros/crypt.h35
-rw-r--r--sec.conf.in2
-rw-r--r--src/irmd/oap/cli.c35
-rw-r--r--src/irmd/oap/hdr.c52
-rw-r--r--src/irmd/oap/hdr.h68
-rw-r--r--src/irmd/oap/srv.c106
-rw-r--r--src/irmd/oap/tests/oap_test.c6
-rw-r--r--src/irmd/oap/tests/oap_test_ml_dsa.c52
-rw-r--r--src/lib/crypt.c38
-rw-r--r--src/lib/crypt/openssl.c113
-rw-r--r--src/lib/crypt/openssl.h265
-rw-r--r--src/lib/tests/kex_test.c19
-rw-r--r--src/lib/tests/kex_test_ml_kem.c8
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);