diff options
Diffstat (limited to 'src/irmd/oap/srv.c')
| -rw-r--r-- | src/irmd/oap/srv.c | 230 |
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); |
