diff options
Diffstat (limited to 'src/irmd/oap/cli.c')
| -rw-r--r-- | src/irmd/oap/cli.c | 268 |
1 files changed, 187 insertions, 81 deletions
diff --git a/src/irmd/oap/cli.c b/src/irmd/oap/cli.c index 8ecd317d..ebfcd71f 100644 --- a/src/irmd/oap/cli.c +++ b/src/irmd/oap/cli.c @@ -50,11 +50,11 @@ struct oap_cli_ctx { uint8_t __id[OAP_ID_SIZE]; buffer_t id; - uint8_t kex_buf[MSGBUFSZ]; + uint8_t kex_buf[CRYPT_KEY_BUFSZ]; uint8_t req_hash[MAX_HASH_SIZE]; size_t req_hash_len; int req_md_nid; - struct sec_config kcfg; + struct sec_config scfg; struct oap_hdr local_hdr; void * pkp; /* Ephemeral keypair */ uint8_t * key; /* For client-encap KEM */ @@ -69,7 +69,7 @@ struct oap_cli_ctx { extern int load_cli_credentials(const struct name_info * info, void ** pkp, void ** crt); -extern int load_cli_kex_config(const struct name_info * info, +extern int load_cli_sec_config(const struct name_info * info, struct sec_config * cfg); extern int load_server_kem_pk(const char * name, struct sec_config * cfg, @@ -87,13 +87,18 @@ int load_cli_credentials(const struct name_info * info, return load_credentials(info->name, &info->c, pkp, crt); } -int load_cli_kex_config(const struct name_info * info, +int load_cli_sec_config(const struct name_info * info, struct sec_config * cfg) { assert(info != NULL); assert(cfg != NULL); - return load_kex_config(info->name, info->c.enc, cfg); + memset(cfg, 0, sizeof(*cfg)); + + /* A client authenticates the server by default, like an https client */ + cfg->a.req = OAP_CLIENT_AUTH_DEFAULT; + + return load_sec_config(info->name, info->c.sec, cfg); } int load_server_kem_pk(const char * name, @@ -133,13 +138,13 @@ int load_server_kem_pk(const char * name, static int do_client_kex_prepare_dhe(struct oap_cli_ctx * s) { - struct sec_config * kcfg = &s->kcfg; + struct sec_config * scfg = &s->scfg; buffer_t * kex = &s->local_hdr.kex; uint8_t * id = s->id.data; ssize_t len; /* Generate ephemeral keypair, send PK */ - len = kex_pkp_create(kcfg, &s->pkp, kex->data); + len = kex_pkp_create(scfg, &s->pkp, kex->data); if (len < 0) { log_err_id(id, "Failed to generate DHE keypair."); return -ECRYPT; @@ -147,7 +152,7 @@ static int do_client_kex_prepare_dhe(struct oap_cli_ctx * s) kex->len = (size_t) len; log_dbg_id(id, "Generated ephemeral %s keys (%zd bytes).", - kcfg->x.str, len); + scfg->x.str, len); return 0; } @@ -155,24 +160,24 @@ static int do_client_kex_prepare_dhe(struct oap_cli_ctx * s) static int do_client_kex_prepare_kem_encap(const char * server_name, struct oap_cli_ctx * s) { - struct sec_config * kcfg = &s->kcfg; + struct sec_config * scfg = &s->scfg; buffer_t * kex = &s->local_hdr.kex; uint8_t * id = s->id.data; buffer_t server_pk = BUF_INIT; uint8_t key_buf[SYMMKEYSZ]; ssize_t len; - if (load_server_kem_pk(server_name, kcfg, &server_pk) < 0) { + if (load_server_kem_pk(server_name, scfg, &server_pk) < 0) { log_err_id(id, "Failed to load server KEM pk."); return -ECRYPT; } - if (IS_HYBRID_KEM(kcfg->x.str)) + if (IS_HYBRID_KEM(scfg->x.str)) len = kex_kem_encap_raw(server_pk, kex->data, - kcfg->k.nid, key_buf); + scfg->k.nid, key_buf); else len = kex_kem_encap(server_pk, kex->data, - kcfg->k.nid, key_buf); + scfg->k.nid, key_buf); freebuf(server_pk); @@ -198,13 +203,13 @@ static int do_client_kex_prepare_kem_encap(const char * server_name, static int do_client_kex_prepare_kem_decap(struct oap_cli_ctx * s) { - struct sec_config * kcfg = &s->kcfg; + struct sec_config * scfg = &s->scfg; buffer_t * kex = &s->local_hdr.kex; uint8_t * id = s->id.data; ssize_t len; /* Server encaps: generate keypair, send PK */ - len = kex_pkp_create(kcfg, &s->pkp, kex->data); + len = kex_pkp_create(scfg, &s->pkp, kex->data); if (len < 0) { log_err_id(id, "Failed to generate KEM keypair."); return -ECRYPT; @@ -219,13 +224,13 @@ static int do_client_kex_prepare_kem_decap(struct oap_cli_ctx * s) static int do_client_kex_prepare(const char * server_name, struct oap_cli_ctx * s) { - struct sec_config * kcfg = &s->kcfg; + struct sec_config * scfg = &s->scfg; - if (!IS_KEX_ALGO_SET(kcfg)) + if (!IS_KEX_ALGO_SET(scfg)) return 0; - if (IS_KEM_ALGORITHM(kcfg->x.str)) { - if (kcfg->x.mode == KEM_MODE_CLIENT_ENCAP) + if (IS_KEM_ALGORITHM(scfg->x.str)) { + if (scfg->x.mode == KEM_MODE_CLIENT_ENCAP) return do_client_kex_prepare_kem_encap(server_name, s); else return do_client_kex_prepare_kem_decap(s); @@ -237,12 +242,15 @@ static int do_client_kex_prepare(const char * server_name, int oap_cli_prepare(void ** ctx, const struct name_info * info, buffer_t * req_buf, - buffer_t data) + buffer_t data, + bool rekey) { struct oap_cli_ctx * s; void * pkp = NULL; void * crt = NULL; + buffer_t no_tag = BUF_INIT; ssize_t ret; + int enc_flags = 0; assert(ctx != NULL); assert(info != NULL); @@ -276,22 +284,44 @@ int oap_cli_prepare(void ** ctx, goto fail_id; } - /* Load KEX config */ - if (load_cli_kex_config(info, &s->kcfg) < 0) { - log_err_id(s->id.data, "Failed to load KEX config for %s.", + /* Load security config */ + if (load_cli_sec_config(info, &s->scfg) < 0) { + log_err_id(s->id.data, "Failed to load security config for %s.", + info->name); + goto fail_kex; + } + + /* A re-keyed flow is encrypted; absent config must fail closed. */ + if (rekey && !IS_KEX_ALGO_SET(&s->scfg)) { + log_err_id(s->id.data, "Refusing re-key without KEX for %s.", info->name); goto fail_kex; } - oap_hdr_init(&s->local_hdr, s->id, s->kex_buf, data, s->kcfg.c.nid); + /* Re-key forces server-encap: client-encap forfeits FS/PCS. */ + if (rekey && s->scfg.x.mode == KEM_MODE_CLIENT_ENCAP) { + s->scfg.x.mode = KEM_MODE_SERVER_ENCAP; + log_dbg_id(s->id.data, "Re-key forcing ephemeral server KEX."); + } + + /* Re-key omits the cert; the server verifies against its cache. */ + if (rekey && crt != NULL) { + crypt_free_crt(crt); + crt = NULL; + } + + if (rekey) + enc_flags = OAP_ENC_REKEY; + + oap_hdr_init(&s->local_hdr, s->id, s->kex_buf, data, s->scfg.c.nid); if (do_client_kex_prepare(info->name, s) < 0) { log_err_id(s->id.data, "Failed to prepare client KEX."); goto fail_kex; } - if (oap_hdr_encode(&s->local_hdr, pkp, crt, &s->kcfg, - (buffer_t) BUF_INIT, NID_undef)) { + if (oap_hdr_encode(&s->local_hdr, pkp, crt, &s->scfg, + no_tag, NID_undef, NULL, enc_flags)) { log_err_id(s->id.data, "Failed to create OAP request header."); goto fail_hdr; } @@ -299,7 +329,7 @@ int oap_cli_prepare(void ** ctx, debug_oap_hdr_snd(&s->local_hdr); /* Compute and store hash of request for verification in complete */ - s->req_md_nid = s->kcfg.d.nid != NID_undef ? s->kcfg.d.nid : NID_sha384; + s->req_md_nid = s->scfg.d.nid != NID_undef ? s->scfg.d.nid : NID_sha384; ret = md_digest(s->req_md_nid, s->local_hdr.hdr, s->req_hash); if (ret < 0) { log_err_id(s->id.data, "Failed to hash request."); @@ -324,6 +354,7 @@ int oap_cli_prepare(void ** ctx, return 0; fail_hash: + oap_hdr_fini(&s->local_hdr); fail_hdr: crypt_secure_free(s->key, SYMMKEYSZ); crypt_free_key(s->pkp); @@ -358,42 +389,41 @@ static int do_client_kex_complete_kem(struct oap_cli_ctx * s, const struct oap_hdr * peer_hdr, struct crypt_sk * sk) { - struct sec_config * kcfg = &s->kcfg; + struct sec_config * scfg = &s->scfg; uint8_t * id = s->id.data; uint8_t key_buf[SYMMKEYSZ]; + buffer_t ct; - if (kcfg->x.mode == KEM_MODE_SERVER_ENCAP) { - buffer_t ct; - - if (peer_hdr->kex.len == 0) { - log_err_id(id, "Server did not send KEM CT."); - return -ECRYPT; - } - - ct.data = peer_hdr->kex.data; - ct.len = peer_hdr->kex.len; - - if (kex_kem_decap(s->pkp, ct, kcfg->k.nid, key_buf) < 0) { - log_err_id(id, "Failed to decapsulate KEM."); - return -ECRYPT; - } - - log_dbg_id(id, "Client decapsulated server CT."); - - } else if (kcfg->x.mode == KEM_MODE_CLIENT_ENCAP) { + if (scfg->x.mode == KEM_MODE_CLIENT_ENCAP) { /* Key already derived during prepare */ memcpy(sk->key, s->key, SYMMKEYSZ); - sk->nid = kcfg->c.nid; - log_info_id(id, "Negotiated %s + %s.", kcfg->x.str, - kcfg->c.str); + sk->nid = scfg->c.nid; + log_info_id(id, "Negotiated %s + %s.", scfg->x.str, + scfg->c.str); return 0; } + /* KEM_MODE_SERVER_ENCAP */ + if (peer_hdr->kex.len == 0) { + log_err_id(id, "Server did not send KEM CT."); + return -ECRYPT; + } + + ct.data = peer_hdr->kex.data; + ct.len = peer_hdr->kex.len; + + if (kex_kem_decap(s->pkp, ct, scfg->k.nid, key_buf) < 0) { + log_err_id(id, "Failed to decapsulate KEM."); + return -ECRYPT; + } + + log_dbg_id(id, "Client decapsulated server CT."); + memcpy(sk->key, key_buf, SYMMKEYSZ); - sk->nid = kcfg->c.nid; + sk->nid = scfg->c.nid; crypt_secure_clear(key_buf, SYMMKEYSZ); - log_info_id(id, "Negotiated %s + %s.", kcfg->x.str, kcfg->c.str); + log_info_id(id, "Negotiated %s + %s.", scfg->x.str, scfg->c.str); return 0; } @@ -402,7 +432,7 @@ static int do_client_kex_complete_dhe(struct oap_cli_ctx * s, const struct oap_hdr * peer_hdr, struct crypt_sk * sk) { - struct sec_config * kcfg = &s->kcfg; + struct sec_config * scfg = &s->scfg; uint8_t * id = s->id.data; uint8_t key_buf[SYMMKEYSZ]; @@ -412,7 +442,7 @@ static int do_client_kex_complete_dhe(struct oap_cli_ctx * s, return -ECRYPT; } - if (kex_dhe_derive(kcfg, s->pkp, peer_hdr->kex, key_buf) < 0) { + if (kex_dhe_derive(scfg, s->pkp, peer_hdr->kex, key_buf) < 0) { log_err_id(id, "Failed to derive DHE secret."); return -ECRYPT; } @@ -420,10 +450,10 @@ static int do_client_kex_complete_dhe(struct oap_cli_ctx * s, log_dbg_id(id, "DHE: derived shared secret."); memcpy(sk->key, key_buf, SYMMKEYSZ); - sk->nid = kcfg->c.nid; + sk->nid = scfg->c.nid; crypt_secure_clear(key_buf, SYMMKEYSZ); - log_info_id(id, "Negotiated %s + %s.", kcfg->x.str, kcfg->c.str); + log_info_id(id, "Negotiated %s + %s.", scfg->x.str, scfg->c.str); return 0; } @@ -433,17 +463,17 @@ static int do_client_kex_complete(struct oap_cli_ctx * s, const struct oap_hdr * peer_hdr, struct crypt_sk * sk) { - struct sec_config * kcfg = &s->kcfg; + struct sec_config * scfg = &s->scfg; uint8_t * id = s->id.data; int cipher_nid; int kdf_nid; - if (!IS_KEX_ALGO_SET(kcfg)) + if (!IS_KEX_ALGO_SET(scfg)) return 0; /* Save client's configured minimums */ - cipher_nid = kcfg->c.nid; - kdf_nid = kcfg->k.nid; + cipher_nid = scfg->c.nid; + kdf_nid = scfg->k.nid; /* Accept server's cipher choice */ if (peer_hdr->cipher_str == NULL) { @@ -451,15 +481,15 @@ static int do_client_kex_complete(struct oap_cli_ctx * s, return -ECRYPT; } - SET_KEX_CIPHER(kcfg, peer_hdr->cipher_str); - if (crypt_validate_nid(kcfg->c.nid) < 0) { + SET_KEX_CIPHER(scfg, peer_hdr->cipher_str); + if (crypt_validate_nid(scfg->c.nid) < 0) { log_err_id(id, "Server cipher '%s' not supported.", peer_hdr->cipher_str); return -ENOTSUP; } /* Verify server cipher >= client's minimum */ - if (crypt_cipher_rank(kcfg->c.nid) < crypt_cipher_rank(cipher_nid)) { + if (crypt_cipher_rank(scfg->c.nid) < crypt_cipher_rank(cipher_nid)) { log_err_id(id, "Server cipher %s too weak.", peer_hdr->cipher_str); return -ECRYPT; @@ -469,20 +499,20 @@ static int do_client_kex_complete(struct oap_cli_ctx * s, peer_hdr->cipher_str); /* Accept server's KDF for non-client-encap modes */ - if (kcfg->x.mode != KEM_MODE_CLIENT_ENCAP + if (scfg->x.mode != KEM_MODE_CLIENT_ENCAP && peer_hdr->kdf_nid != NID_undef) { if (crypt_kdf_rank(peer_hdr->kdf_nid) < crypt_kdf_rank(kdf_nid)) { log_err_id(id, "Server KDF too weak."); return -ECRYPT; } - SET_KEX_KDF_NID(kcfg, peer_hdr->kdf_nid); + SET_KEX_KDF_NID(scfg, peer_hdr->kdf_nid); log_dbg_id(id, "Accepted server KDF %s.", - md_nid_to_str(kcfg->k.nid)); + md_nid_to_str(scfg->k.nid)); } /* Derive shared secret */ - if (IS_KEM_ALGORITHM(kcfg->x.str)) + if (IS_KEM_ALGORITHM(scfg->x.str)) return do_client_kex_complete_kem(s, peer_hdr, sk); return do_client_kex_complete_dhe(s, peer_hdr, sk); @@ -492,12 +522,20 @@ int oap_cli_complete(void * ctx, const struct name_info * info, buffer_t rsp_buf, buffer_t * data, - struct crypt_sk * sk) + struct crypt_sk * sk, + const buffer_t * cached_crt, + buffer_t * peer_crt) { struct oap_cli_ctx * s = ctx; struct oap_hdr peer_hdr; char peer[NAME_SIZE + 1]; + uint8_t kc_buf[MAX_HASH_SIZE]; + uint8_t resp_hash_buf[MAX_HASH_SIZE]; + uint8_t hs_key[SYMMKEYSZ]; + buffer_t req_hash = BUF_INIT; + buffer_t resp_hash = BUF_INIT; uint8_t * id; + int rc; assert(ctx != NULL); assert(info != NULL); @@ -515,7 +553,7 @@ int oap_cli_complete(void * ctx, log_dbg_id(id, "Completing OAP for %s.", info->name); /* Decode response header using client's md_nid for hash length */ - if (oap_hdr_decode(&peer_hdr, rsp_buf, s->req_md_nid) < 0) { + if (oap_hdr_decode(&peer_hdr, rsp_buf, s->req_md_nid, false) < 0) { log_err_id(id, "Failed to decode OAP response header."); goto fail_oap; } @@ -528,20 +566,52 @@ int oap_cli_complete(void * ctx, goto fail_oap; } - /* Authenticate server */ - if (oap_auth_peer(peer, &s->local_hdr, &peer_hdr) < 0) { - log_err_id(id, "Failed to authenticate server."); + /* Complete key exchange first; the sealed identity needs the secret */ + if (do_client_kex_complete(s, &peer_hdr, sk) < 0) { + log_err_id(id, "Failed to complete key exchange."); goto fail_oap; } - /* Verify request hash in authenticated response */ - if (peer_hdr.req_hash.len == 0) { - log_err_id(id, "Response missing req_hash."); + req_hash.data = s->req_hash; + req_hash.len = s->req_hash_len; + + /* Decrypt the sealed server identity (data+cert+sig) before auth */ + if (sk->nid != NID_undef && peer_hdr.sealed.data != NULL) { + if (oap_derive_hs_key(sk, req_hash, hs_key) < 0) { + log_err_id(id, "Failed to derive handshake key."); + goto fail_oap; + } + + rc = oap_hdr_unseal(&peer_hdr, hs_key); + + crypt_secure_clear(hs_key, SYMMKEYSZ); + + if (rc < 0) { + log_err_id(id, "Failed to unseal server identity."); + goto fail_oap; + } + } + + /* Authenticate server (cert + signature now in cleartext) */ + if (oap_auth_peer(peer, &s->scfg, &s->local_hdr, &peer_hdr, + cached_crt) < 0) { + log_err_id(id, "Failed to authenticate server."); goto fail_oap; } - if (memcmp(peer_hdr.req_hash.data, s->req_hash, s->req_hash_len) != 0) { - log_err_id(id, "Response req_hash mismatch."); + /* 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_oap; + + memcpy(peer_crt->data, peer_hdr.crt.data, peer_hdr.crt.len); + peer_crt->len = peer_hdr.crt.len; + } + + /* Response must carry a transcript tag of the expected length */ + if (peer_hdr.rsp_tag.len != s->req_hash_len) { + log_err_id(id, "Response transcript tag mismatch."); goto fail_oap; } @@ -552,10 +622,43 @@ int oap_cli_complete(void * ctx, goto fail_oap; } - /* Complete key exchange */ - if (do_client_kex_complete(s, &peer_hdr, sk) < 0) { - log_err_id(id, "Failed to complete key exchange."); - goto fail_oap; + if (sk->nid != NID_undef) { + /* Encrypted: bind the key and verify key confirmation */ + resp_hash.data = resp_hash_buf; + + if (oap_resp_hash(s->req_md_nid, peer_hdr.kex, + peer_hdr.data, peer_hdr.crt, + &resp_hash) < 0) { + log_err_id(id, "Failed to hash response."); + goto fail_oap; + } + + if (oap_bind_session_key(sk, req_hash, resp_hash, + s->scfg.k.nid) < 0) { + log_err_id(id, "Failed to bind session key."); + goto fail_oap; + } + + if (oap_key_confirm_tag(sk, req_hash, resp_hash, kc_buf, + s->req_hash_len) < 0) { + log_err_id(id, "Failed to confirm session key."); + goto fail_oap; + } + + if (crypt_ct_cmp(peer_hdr.rsp_tag.data, kc_buf, + s->req_hash_len) != 0) { + log_err_id(id, "Key confirmation mismatch."); + goto fail_oap; + } + } else { + /* Cleartext path is config-driven, never a wire downgrade */ + assert(!IS_KEX_ALGO_SET(&s->scfg)); + /* Unencrypted: verify request-echo integrity */ + if (crypt_ct_cmp(peer_hdr.rsp_tag.data, s->req_hash, + s->req_hash_len) != 0) { + log_err_id(id, "Response tag mismatch."); + goto fail_oap; + } } /* Copy piggybacked data from server response */ @@ -566,11 +669,14 @@ int oap_cli_complete(void * ctx, log_info_id(id, "OAP completed for %s.", info->name); + freebuf(peer_hdr.sealed_pt); + oap_ctx_free(s); return 0; fail_oap: + freebuf(peer_hdr.sealed_pt); oap_ctx_free(s); return -ECRYPT; } |
