summaryrefslogtreecommitdiff
path: root/src/irmd/oap/cli.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/irmd/oap/cli.c')
-rw-r--r--src/irmd/oap/cli.c268
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;
}