summaryrefslogtreecommitdiff
path: root/src/irmd/oap/srv.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/irmd/oap/srv.c')
-rw-r--r--src/irmd/oap/srv.c230
1 files changed, 162 insertions, 68 deletions
diff --git a/src/irmd/oap/srv.c b/src/irmd/oap/srv.c
index 36391e50..d78fc8d4 100644
--- a/src/irmd/oap/srv.c
+++ b/src/irmd/oap/srv.c
@@ -49,7 +49,7 @@
extern int load_srv_credentials(const struct name_info * info,
void ** pkp,
void ** crt);
-extern int load_srv_kex_config(const struct name_info * info,
+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,
@@ -67,13 +67,16 @@ int load_srv_credentials(const struct name_info * info,
return load_credentials(info->name, &info->s, pkp, crt);
}
-int load_srv_kex_config(const struct name_info * info,
+int load_srv_sec_config(const struct name_info * info,
struct sec_config * cfg)
{
assert(info != NULL);
assert(cfg != NULL);
- return load_kex_config(info->name, info->s.enc, cfg);
+ memset(cfg, 0, sizeof(*cfg));
+
+ /* Client auth stays opt-in (mTLS); enable with auth=required */
+ return load_sec_config(info->name, info->s.sec, cfg);
}
int load_server_kem_keypair(const char * name,
@@ -135,7 +138,7 @@ static int get_algo_from_peer_key(const struct oap_hdr * peer_hdr,
}
static int negotiate_cipher(const struct oap_hdr * peer_hdr,
- struct sec_config * kcfg)
+ struct sec_config * scfg)
{
uint8_t * id = peer_hdr->id.data;
int cli_nid;
@@ -143,27 +146,25 @@ static int negotiate_cipher(const struct oap_hdr * peer_hdr,
int srv_rank;
/* Cipher: select the strongest of client and server */
- cli_nid = peer_hdr->cipher_str != NULL
- ? (int) crypt_str_to_nid(peer_hdr->cipher_str)
- : NID_undef;
+ if (peer_hdr->cipher_str != NULL)
+ cli_nid = (int) crypt_str_to_nid(peer_hdr->cipher_str);
+ else
+ cli_nid = NID_undef;
- if (cli_nid != NID_undef
- && crypt_cipher_rank(cli_nid) < 0) {
+ if (cli_nid != NID_undef && crypt_cipher_rank(cli_nid) < 0) {
log_err_id(id, "Unsupported cipher '%s'.",
peer_hdr->cipher_str);
return -ENOTSUP;
}
cli_rank = crypt_cipher_rank(cli_nid);
- srv_rank = crypt_cipher_rank(kcfg->c.nid);
+ srv_rank = crypt_cipher_rank(scfg->c.nid);
if (cli_rank > srv_rank) {
- SET_KEX_CIPHER_NID(kcfg, cli_nid);
- log_dbg_id(id, "Selected client cipher %s.",
- kcfg->c.str);
+ SET_KEX_CIPHER_NID(scfg, cli_nid);
+ log_dbg_id(id, "Selected client cipher %s.", scfg->c.str);
} else if (srv_rank > 0) {
- log_dbg_id(id, "Selected server cipher %s.",
- kcfg->c.str);
+ log_dbg_id(id, "Selected server cipher %s.", scfg->c.str);
} else {
log_err_id(id, "Encryption requested, no cipher.");
return -ECRYPT;
@@ -178,31 +179,27 @@ static int negotiate_cipher(const struct oap_hdr * peer_hdr,
}
cli_rank = crypt_kdf_rank(peer_hdr->kdf_nid);
- srv_rank = crypt_kdf_rank(kcfg->k.nid);
+ srv_rank = crypt_kdf_rank(scfg->k.nid);
- /*
- * For client-encap KEM, the KDF is baked into
- * the ciphertext. The server must use the client's
- * KDF and can only verify the minimum.
- */
+ /* Client-encap KEM bakes KDF into ciphertext; verify min. */
if (OAP_KEX_ROLE(peer_hdr) == KEM_MODE_CLIENT_ENCAP) {
if (srv_rank > cli_rank) {
log_err_id(id, "Client KDF too weak.");
return -ECRYPT;
}
- SET_KEX_KDF_NID(kcfg, peer_hdr->kdf_nid);
+ SET_KEX_KDF_NID(scfg, peer_hdr->kdf_nid);
} else if (cli_rank > srv_rank) {
- SET_KEX_KDF_NID(kcfg, peer_hdr->kdf_nid);
+ SET_KEX_KDF_NID(scfg, peer_hdr->kdf_nid);
log_dbg_id(id, "Selected client KDF %s.",
- md_nid_to_str(kcfg->k.nid));
+ md_nid_to_str(scfg->k.nid));
} else if (srv_rank > 0) {
log_dbg_id(id, "Selected server KDF %s.",
- md_nid_to_str(kcfg->k.nid));
+ md_nid_to_str(scfg->k.nid));
}
- if (IS_KEX_ALGO_SET(kcfg))
+ if (IS_KEX_ALGO_SET(scfg))
log_info_id(id, "Negotiated %s + %s.",
- kcfg->x.str, kcfg->c.str);
+ scfg->x.str, scfg->c.str);
else
log_info_id(id, "No key exchange.");
@@ -211,7 +208,7 @@ static int negotiate_cipher(const struct oap_hdr * peer_hdr,
static int do_server_kem_decap(const struct name_info * info,
const struct oap_hdr * peer_hdr,
- struct sec_config * kcfg,
+ struct sec_config * scfg,
struct crypt_sk * sk)
{
buffer_t ct;
@@ -228,7 +225,7 @@ static int do_server_kem_decap(const struct name_info * info,
ct.data = peer_hdr->kex.data;
ct.len = peer_hdr->kex.len;
- ret = kex_kem_decap(server_pkp, ct, kcfg->k.nid, sk->key);
+ ret = kex_kem_decap(server_pkp, ct, scfg->k.nid, sk->key);
crypt_free_key(server_pkp);
@@ -243,7 +240,7 @@ static int do_server_kem_decap(const struct name_info * info,
}
static int do_server_kem_encap(const struct oap_hdr * peer_hdr,
- struct sec_config * kcfg,
+ struct sec_config * scfg,
buffer_t * kex,
struct crypt_sk * sk)
{
@@ -254,12 +251,12 @@ 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(kcfg->x.str))
+ if (IS_HYBRID_KEM(scfg->x.str))
ct_len = kex_kem_encap_raw(client_pk, kex->data,
- kcfg->k.nid, sk->key);
+ scfg->k.nid, sk->key);
else
ct_len = kex_kem_encap(client_pk, kex->data,
- kcfg->k.nid, sk->key);
+ scfg->k.nid, sk->key);
if (ct_len < 0) {
log_err_id(id, "Failed to encapsulate KEM.");
@@ -275,26 +272,26 @@ static int do_server_kem_encap(const struct oap_hdr * peer_hdr,
static int do_server_kex_kem(const struct name_info * info,
struct oap_hdr * peer_hdr,
- struct sec_config * kcfg,
+ struct sec_config * scfg,
buffer_t * kex,
struct crypt_sk * sk)
{
int ret;
- kcfg->x.mode = peer_hdr->kex_flags.role;
+ scfg->x.mode = peer_hdr->kex_flags.role;
- if (kcfg->x.mode == KEM_MODE_CLIENT_ENCAP) {
- ret = do_server_kem_decap(info, peer_hdr, kcfg, sk);
+ if (scfg->x.mode == KEM_MODE_CLIENT_ENCAP) {
+ ret = do_server_kem_decap(info, peer_hdr, scfg, sk);
kex->len = 0;
} else {
- ret = do_server_kem_encap(peer_hdr, kcfg, kex, sk);
+ ret = do_server_kem_encap(peer_hdr, scfg, kex, sk);
}
return ret;
}
static int do_server_kex_dhe(const struct oap_hdr * peer_hdr,
- struct sec_config * kcfg,
+ struct sec_config * scfg,
buffer_t * kex,
struct crypt_sk * sk)
{
@@ -303,7 +300,7 @@ static int do_server_kex_dhe(const struct oap_hdr * peer_hdr,
int ret;
uint8_t * id = peer_hdr->id.data;
- key_len = kex_pkp_create(kcfg, &epkp, kex->data);
+ key_len = kex_pkp_create(scfg, &epkp, kex->data);
if (key_len < 0) {
log_err_id(id, "Failed to generate key pair.");
return -ECRYPT;
@@ -311,9 +308,9 @@ static int do_server_kex_dhe(const struct oap_hdr * peer_hdr,
kex->len = (size_t) key_len;
- log_dbg_id(id, "Generated %s ephemeral keys.", kcfg->x.str);
+ log_dbg_id(id, "Generated %s ephemeral keys.", scfg->x.str);
- ret = kex_dhe_derive(kcfg, epkp, peer_hdr->kex, sk->key);
+ ret = kex_dhe_derive(scfg, epkp, peer_hdr->kex, sk->key);
if (ret < 0) {
log_err_id(id, "Failed to derive secret.");
kex_pkp_destroy(epkp);
@@ -327,7 +324,7 @@ static int do_server_kex_dhe(const struct oap_hdr * peer_hdr,
int do_server_kex(const struct name_info * info,
struct oap_hdr * peer_hdr,
- struct sec_config * kcfg,
+ struct sec_config * scfg,
buffer_t * kex,
struct crypt_sk * sk)
{
@@ -339,60 +336,72 @@ int do_server_kex(const struct name_info * info,
/* No KEX data from client */
if (peer_hdr->kex.len == 0) {
- if (IS_KEX_ALGO_SET(kcfg)) {
+ if (IS_KEX_ALGO_SET(scfg)) {
log_warn_id(id, "KEX requested without info.");
return -ECRYPT;
}
return 0;
}
- if (negotiate_cipher(peer_hdr, kcfg) < 0)
+ if (negotiate_cipher(peer_hdr, scfg) < 0)
return -ECRYPT;
/* Save server's configured KEX before overwriting */
- srv_kex_nid = kcfg->x.nid;
+ srv_kex_nid = scfg->x.nid;
if (OAP_KEX_ROLE(peer_hdr) != KEM_MODE_CLIENT_ENCAP) {
/* Server encapsulation or DHE: extract algo from DER PK */
if (get_algo_from_peer_key(peer_hdr, algo_buf) < 0)
return -ECRYPT;
- SET_KEX_ALGO(kcfg, algo_buf);
+ SET_KEX_ALGO(scfg, algo_buf);
/* Reject if client KEX is weaker than server's */
- if (crypt_kex_rank(kcfg->x.nid)
+ if (crypt_kex_rank(scfg->x.nid)
< crypt_kex_rank(srv_kex_nid)) {
log_err_id(id, "Client KEX %s too weak.",
- kcfg->x.str);
+ scfg->x.str);
return -ECRYPT;
}
}
/* Dispatch based on algorithm type */
- if (IS_KEM_ALGORITHM(kcfg->x.str))
- return do_server_kex_kem(info, peer_hdr, kcfg, kex, sk);
+ if (IS_KEM_ALGORITHM(scfg->x.str))
+ return do_server_kex_kem(info, peer_hdr, scfg, kex, sk);
else
- return do_server_kex_dhe(peer_hdr, kcfg, kex, sk);
+ return do_server_kex_dhe(peer_hdr, scfg, kex, sk);
}
int oap_srv_process(const struct name_info * info,
buffer_t req_buf,
buffer_t * rsp_buf,
buffer_t * data,
- struct crypt_sk * sk)
+ struct crypt_sk * sk,
+ bool rekey,
+ const buffer_t * cached_crt,
+ buffer_t * peer_crt)
{
struct oap_hdr peer_hdr;
struct oap_hdr local_hdr;
- struct sec_config kcfg;
- uint8_t kex_buf[MSGBUFSZ];
+ struct sec_config scfg;
+ uint8_t kex_buf[CRYPT_KEY_BUFSZ];
uint8_t hash_buf[MAX_HASH_SIZE];
- buffer_t req_hash = BUF_INIT;
+ 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]; /* TODO */
+ 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);
@@ -412,13 +421,22 @@ int oap_srv_process(const struct name_info * info,
goto fail_cred;
}
- if (load_srv_kex_config(info, &kcfg) < 0) {
- log_err("Failed to load KEX config for %s.", info->name);
+ /* Re-key omits the cert; the peer verifies against its cache. */
+ if (rekey && crt != NULL) {
+ crypt_free_crt(crt);
+ crt = NULL;
+ }
+
+ if (rekey)
+ enc_flags = OAP_ENC_REKEY;
+
+ if (load_srv_sec_config(info, &scfg) < 0) {
+ log_err("Failed to load security config for %s.", info->name);
goto fail_kex;
}
/* Decode incoming header (NID_undef = request, no hash) */
- if (oap_hdr_decode(&peer_hdr, req_buf, NID_undef) < 0) {
+ if (oap_hdr_decode(&peer_hdr, req_buf, NID_undef, rekey) < 0) {
log_err("Failed to decode OAP header.");
goto fail_auth;
}
@@ -427,22 +445,44 @@ int oap_srv_process(const struct name_info * info,
id = peer_hdr.id.data; /* Logging */
- if (oap_check_hdr(&peer_hdr) < 0) {
- log_err_id(id, "OAP header failed replay check.");
+ ret = oap_check_hdr(&peer_hdr);
+ if (ret == -EREPLAY) {
+ log_warn_id(id, "OAP header failed replay check.");
+ goto fail_replay;
+ }
+ if (ret < 0) {
+ log_err_id(id, "OAP header check failed.");
goto fail_auth;
}
oap_hdr_init(&local_hdr, peer_hdr.id, kex_buf, *data, NID_undef);
- if (oap_auth_peer(cli_name, &local_hdr, &peer_hdr) < 0) {
+ if (oap_auth_peer(cli_name, &scfg, &local_hdr, &peer_hdr,
+ cached_crt) < 0) {
log_err_id(id, "Failed to authenticate client.");
goto fail_auth;
}
- if (do_server_kex(info, &peer_hdr, &kcfg, &local_hdr.kex, sk) < 0)
+ /* Surface the peer cert so the caller can cache it for re-key. */
+ if (peer_crt != NULL && peer_hdr.crt.len > 0) {
+ peer_crt->data = malloc(peer_hdr.crt.len);
+ if (peer_crt->data == NULL)
+ goto fail_auth;
+
+ memcpy(peer_crt->data, peer_hdr.crt.data, peer_hdr.crt.len);
+ peer_crt->len = peer_hdr.crt.len;
+ }
+
+ /* A re-keyed flow is encrypted; refuse a plaintext re-key. */
+ if (rekey && peer_hdr.kex.len == 0) {
+ log_err_id(id, "Re-key request without KEX.");
+ goto fail_kex;
+ }
+
+ if (do_server_kex(info, &peer_hdr, &scfg, &local_hdr.kex, sk) < 0)
goto fail_kex;
- sk->nid = kcfg.c.nid;
+ sk->nid = scfg.c.nid;
/* Build response header with hash of client request */
local_hdr.nid = sk->nid;
@@ -458,10 +498,58 @@ int oap_srv_process(const struct name_info * info,
goto fail_auth;
}
req_hash.data = hash_buf;
- req_hash.len = (size_t) hash_ret;
+ req_hash.len = (size_t) hash_ret;
+
+ rsp_tag = req_hash;
+
+ /* Bind the key to the transcript and confirm it to the client */
+ if (sk->nid != NID_undef) {
+ if (crt != NULL && crypt_crt_der(crt, &crt_der) < 0) {
+ log_err_id(id, "Failed to serialize cert.");
+ goto fail_auth;
+ }
+
+ resp_hash.data = resp_hash_buf;
+
+ ret = oap_resp_hash(req_md_nid, local_hdr.kex, *data,
+ crt_der, &resp_hash);
+
+ freebuf(crt_der);
- if (oap_hdr_encode(&local_hdr, pkp, crt, &kcfg,
- req_hash, req_md_nid) < 0) {
+ if (ret < 0) {
+ log_err_id(id, "Failed to hash response.");
+ goto fail_auth;
+ }
+
+ /* Derive the identity-seal key before bind mutates sk->key */
+ if (oap_derive_hs_key(sk, req_hash, hs_key) < 0) {
+ log_err_id(id, "Failed to derive handshake key.");
+ goto fail_auth;
+ }
+
+ seal_key = hs_key;
+
+ if (oap_bind_session_key(sk, req_hash, resp_hash,
+ scfg.k.nid) < 0) {
+ log_err_id(id, "Failed to bind session key.");
+ goto fail_auth;
+ }
+
+ if (oap_key_confirm_tag(sk, req_hash, resp_hash, kc_buf,
+ (size_t) hash_ret) < 0) {
+ log_err_id(id, "Failed to confirm session key.");
+ goto fail_auth;
+ }
+
+ rsp_tag.data = kc_buf;
+ }
+
+ ret = oap_hdr_encode(&local_hdr, pkp, crt, &scfg,
+ rsp_tag, req_md_nid, seal_key, enc_flags);
+
+ crypt_secure_clear(hs_key, SYMMKEYSZ);
+
+ if (ret < 0) {
log_err_id(id, "Failed to create OAP response header.");
goto fail_auth;
}
@@ -486,11 +574,17 @@ int oap_srv_process(const struct name_info * info,
fail_data:
oap_hdr_fini(&local_hdr);
fail_auth:
+ crypt_secure_clear(hs_key, SYMMKEYSZ);
crypt_free_crt(crt);
crypt_free_key(pkp);
fail_cred:
return -EAUTH;
+ fail_replay:
+ crypt_free_crt(crt);
+ crypt_free_key(pkp);
+ return -EREPLAY;
+
fail_kex:
crypt_free_crt(crt);
crypt_free_key(pkp);