diff options
| author | Dimitri Staessens <dimitri@ouroboros.rocks> | 2026-02-14 14:33:50 +0100 |
|---|---|---|
| committer | Sander Vrijders <sander@ouroboros.rocks> | 2026-02-18 07:52:56 +0100 |
| commit | d85326a119c34789055c388fcd18bb0161fbfd21 (patch) | |
| tree | 629b027f16bfcc6303609b915a4f0f86acc09fd8 /src/irmd/oap/cli.c | |
| parent | 86dba5441c686d037c493e5b498e27249aa6bd9d (diff) | |
| download | ouroboros-d85326a119c34789055c388fcd18bb0161fbfd21.tar.gz ouroboros-d85326a119c34789055c388fcd18bb0161fbfd21.zip | |
irmd: Add strength-based crypto negotiation
Each side's configured cipher, KDF, and KEX algorithm now
represents a minimum security floor ("at least this strong").
Cipher and KDF use strongest-wins: the server compares ranks
and selects the stronger of client vs server config. The
negotiated values are sent in the response header. The client
verifies the server's response meets its own minimum, which
prevents downgrade attacks on the wire.
KEX uses a minimum-floor check: the server extracts the
client's algorithm from its public key and rejects if it
ranks below the server's configured algorithm. A server
configured with ML-KEM will reject all classical algorithms.
Special case: for client-encap KEM, the client has already
derived its key using its KDF, so the server must use the
same KDF and can only reject if it is too weak.
The supported_nids arrays are ordered weakest to strongest
and serve as the single source of truth for ranking.
Cipher ranking (weakest to strongest):
aes-128-ctr, aes-192-ctr, aes-256-ctr,
aes-128-gcm, aes-192-gcm, aes-256-gcm,
chacha20-poly1305
KDF ranking (weakest to strongest):
blake2s256, sha256, sha3-256, sha384,
sha3-384, blake2b512, sha512, sha3-512
KEX ranking (weakest to strongest):
ffdhe2048, prime256v1, X25519, ffdhe3072,
secp384r1, ffdhe4096, X448, secp521r1,
ML-KEM-512, ML-KEM-768, ML-KEM-1024,
X25519MLKEM768, X448MLKEM1024
Negotiation outcomes:
strong srv cipher + weak cli cipher -> use strongest
weak srv cipher + strong cli cipher -> use strongest
srv encryption + cli none -> server rejects
srv none + cli encryption -> use client's
strong srv KEX + weak cli KEX -> server rejects
weak srv KEX + strong cli KEX -> succeeds
wire tamper to weaker cipher -> client rejects
Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
Diffstat (limited to 'src/irmd/oap/cli.c')
| -rw-r--r-- | src/irmd/oap/cli.c | 29 |
1 files changed, 28 insertions, 1 deletions
diff --git a/src/irmd/oap/cli.c b/src/irmd/oap/cli.c index ea2a25d1..9472e331 100644 --- a/src/irmd/oap/cli.c +++ b/src/irmd/oap/cli.c @@ -439,10 +439,16 @@ static int do_client_kex_complete(struct oap_cli_ctx * s, { struct sec_config * kcfg = &s->kcfg; uint8_t * id = s->id.data; + int cipher_nid; + int kdf_nid; if (!IS_KEX_ALGO_SET(kcfg)) return 0; + /* Save client's configured minimums */ + cipher_nid = kcfg->c.nid; + kdf_nid = kcfg->k.nid; + /* Accept server's cipher choice */ if (peer_hdr->cipher_str == NULL) { log_err_id(id, "Server did not provide cipher."); @@ -456,7 +462,28 @@ static int do_client_kex_complete(struct oap_cli_ctx * s, return -ENOTSUP; } - log_dbg_id(id, "Accepted server cipher %s.", peer_hdr->cipher_str); + /* Verify server cipher >= client's minimum */ + if (crypt_cipher_rank(kcfg->c.nid) < crypt_cipher_rank(cipher_nid)) { + log_err_id(id, "Server cipher %s too weak.", + peer_hdr->cipher_str); + return -ECRYPT; + } + + log_dbg_id(id, "Accepted server cipher %s.", + peer_hdr->cipher_str); + + /* Accept server's KDF for non-client-encap modes */ + if (kcfg->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); + log_dbg_id(id, "Accepted server KDF %s.", + md_nid_to_str(kcfg->k.nid)); + } /* Derive shared secret */ if (IS_KEM_ALGORITHM(kcfg->x.str)) |
