summaryrefslogtreecommitdiff
path: root/src/lib
diff options
context:
space:
mode:
authorDimitri Staessens <dimitri@ouroboros.rocks>2026-02-14 14:33:50 +0100
committerSander Vrijders <sander@ouroboros.rocks>2026-02-18 07:52:56 +0100
commitd85326a119c34789055c388fcd18bb0161fbfd21 (patch)
tree629b027f16bfcc6303609b915a4f0f86acc09fd8 /src/lib
parent86dba5441c686d037c493e5b498e27249aa6bd9d (diff)
downloadouroboros-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/lib')
-rw-r--r--src/lib/crypt.c70
-rw-r--r--src/lib/crypt/openssl.c2
2 files changed, 60 insertions, 12 deletions
diff --git a/src/lib/crypt.c b/src/lib/crypt.c
index 8c29cbb3..92da803d 100644
--- a/src/lib/crypt.c
+++ b/src/lib/crypt.c
@@ -56,15 +56,16 @@ static const struct nid_map cipher_nid_map[] = {
{NID_undef, NULL}
};
+/* Ordered in strength preference, lowest first */
const uint16_t crypt_supported_nids[] = {
#ifdef HAVE_OPENSSL
+ NID_aes_128_ctr,
+ NID_aes_192_ctr,
+ NID_aes_256_ctr,
NID_aes_128_gcm,
NID_aes_192_gcm,
NID_aes_256_gcm,
NID_chacha20_poly1305,
- NID_aes_128_ctr,
- NID_aes_192_ctr,
- NID_aes_256_ctr,
#endif
NID_undef
};
@@ -86,16 +87,17 @@ static const struct nid_map kex_nid_map[] = {
{NID_undef, NULL}
};
+/* Ordered in strength preference, lowest first */
const uint16_t kex_supported_nids[] = {
#ifdef HAVE_OPENSSL
+ NID_ffdhe2048,
NID_X9_62_prime256v1,
- NID_secp384r1,
- NID_secp521r1,
NID_X25519,
- NID_X448,
- NID_ffdhe2048,
NID_ffdhe3072,
+ NID_secp384r1,
NID_ffdhe4096,
+ NID_X448,
+ NID_secp521r1,
#ifdef HAVE_OPENSSL_PQC
NID_MLKEM512,
NID_MLKEM768,
@@ -119,16 +121,17 @@ static const struct nid_map md_nid_map[] = {
{NID_undef, NULL}
};
+/* Ordered in strength preference, lowest first */
const uint16_t md_supported_nids[] = {
#ifdef HAVE_OPENSSL
+ NID_blake2s256,
NID_sha256,
- NID_sha384,
- NID_sha512,
NID_sha3_256,
+ NID_sha384,
NID_sha3_384,
- NID_sha3_512,
NID_blake2b512,
- NID_blake2s256,
+ NID_sha512,
+ NID_sha3_512,
#endif
NID_undef
};
@@ -544,6 +547,51 @@ int md_validate_nid(int nid)
return -ENOTSUP;
}
+int crypt_cipher_rank(int nid)
+{
+ int i;
+
+ if (nid == NID_undef)
+ return 0;
+
+ for (i = 0; crypt_supported_nids[i] != NID_undef; i++) {
+ if ((int) crypt_supported_nids[i] == nid)
+ return i + 1;
+ }
+
+ return -1;
+}
+
+int crypt_kdf_rank(int nid)
+{
+ int i;
+
+ if (nid == NID_undef)
+ return 0;
+
+ for (i = 0; md_supported_nids[i] != NID_undef; i++) {
+ if ((int) md_supported_nids[i] == nid)
+ return i + 1;
+ }
+
+ return -1;
+}
+
+int crypt_kex_rank(int nid)
+{
+ int i;
+
+ if (nid == NID_undef)
+ return 0;
+
+ for (i = 0; kex_supported_nids[i] != NID_undef; i++) {
+ if ((int) kex_supported_nids[i] == nid)
+ return i + 1;
+ }
+
+ return -1;
+}
+
/* Hash length now returned by md_digest() */
int crypt_encrypt(struct crypt_ctx * ctx,
diff --git a/src/lib/crypt/openssl.c b/src/lib/crypt/openssl.c
index 232aa6c9..5d95f408 100644
--- a/src/lib/crypt/openssl.c
+++ b/src/lib/crypt/openssl.c
@@ -974,7 +974,7 @@ int openssl_encrypt(struct ossl_crypt_ctx * ctx,
if (random_buffer(iv, ctx->ivsz) < 0)
goto fail_encrypt;
- /* Set IV bit 7 to current key phase (bit KEY_ROTATION_BIT of counter) */
+ /* Set IV bit 7 to current key phase (KEY_ROTATION_BIT of counter) */
if (ctx->rot.cntr & ctx->rot.mask)
iv[0] |= 0x80;
else