diff options
| author | tb <> | 2026-01-23 08:21:52 +0000 |
|---|---|---|
| committer | tb <> | 2026-01-23 08:21:52 +0000 |
| commit | e8699208c6c4338d425f0979a68ec6ea04009819 (patch) | |
| tree | ef36c010a2e7aca4c89ff821d120c112cea99020 /src/lib | |
| parent | abaacba9756060fd447f010a2698e3150d3f5378 (diff) | |
| download | openbsd-e8699208c6c4338d425f0979a68ec6ea04009819.tar.gz openbsd-e8699208c6c4338d425f0979a68ec6ea04009819.tar.bz2 openbsd-e8699208c6c4338d425f0979a68ec6ea04009819.zip | |
Scapy special for DH_check()
The latest release of Scapy calls DH_check() on all the well-known
Diffie-Hellman parameters for RFCs 2409, 3526, and 7919. It does this
via pyca/cryptography at startup. Every single time. This is obviously
very expensive, due to our 64 MR rounds (which are complete overkill
now that we have BPSW). Instead of pondering the ideal number of rounds
for BPSW with FFDH, simply skip the check if the parameter matches a
well-known prime. These are known to be safe primes, so we can skip
those super-expensive and pointless checks without any risk.
This is only done for the public dh->p parameter. It could be further
optimized, but with the follow-up commit adding the RFC 7919 primes this
reduces the startup time to what it was before Scapy 2.7.0: < 1s.
Reverting from 64 MR rounds to BN_check_primes rounds, we would still
have ~8s startup time without this optimization, which isn't great for
an interactive tool.
Clearly, it's not entirely our fault, it's also Scapy and cryptography
that do something ... suboptimal, but I think we're better off if
DH_check() isn't a complete DoS vector. If you're using non-standard
parameters with FFDH, you deserve it.
We could consider adding a flag for non-well-known p and thus making
DH_check() indicate failure for candidate primes larger than, say, 4k.
https://github.com/pyca/cryptography/issues/14048
ok beck kenjiro
Diffstat (limited to 'src/lib')
| -rw-r--r-- | src/lib/libcrypto/dh/dh_check.c | 62 |
1 files changed, 60 insertions, 2 deletions
diff --git a/src/lib/libcrypto/dh/dh_check.c b/src/lib/libcrypto/dh/dh_check.c index 1ba85bc824..143699b4f6 100644 --- a/src/lib/libcrypto/dh/dh_check.c +++ b/src/lib/libcrypto/dh/dh_check.c | |||
| @@ -1,4 +1,4 @@ | |||
| 1 | /* $OpenBSD: dh_check.c,v 1.31 2025/05/10 05:54:38 tb Exp $ */ | 1 | /* $OpenBSD: dh_check.c,v 1.32 2026/01/23 08:21:52 tb Exp $ */ |
| 2 | /* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) | 2 | /* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) |
| 3 | * All rights reserved. | 3 | * All rights reserved. |
| 4 | * | 4 | * |
| @@ -104,6 +104,58 @@ DH_check_params(const DH *dh, int *flags) | |||
| 104 | return ok; | 104 | return ok; |
| 105 | } | 105 | } |
| 106 | 106 | ||
| 107 | typedef BIGNUM *(*get_p_fn)(BIGNUM *); | ||
| 108 | |||
| 109 | static const get_p_fn get_well_known_p[] = { | ||
| 110 | BN_get_rfc2409_prime_768, | ||
| 111 | BN_get_rfc2409_prime_1024, | ||
| 112 | BN_get_rfc3526_prime_1536, | ||
| 113 | BN_get_rfc3526_prime_2048, | ||
| 114 | BN_get_rfc3526_prime_3072, | ||
| 115 | BN_get_rfc3526_prime_4096, | ||
| 116 | BN_get_rfc3526_prime_6144, | ||
| 117 | BN_get_rfc3526_prime_8192, | ||
| 118 | }; | ||
| 119 | |||
| 120 | #define N_WELL_KNOWN_P_FN (sizeof(get_well_known_p) / sizeof(get_well_known_p[0])) | ||
| 121 | |||
| 122 | /* | ||
| 123 | * Scapy special: on startup it now calls DH_check() on all the well-known DH | ||
| 124 | * primes, which is a sensible thing to do. In any case, using BN_is_prime_ex() | ||
| 125 | * on a standardized domain parameter is dumb, so avoid it. | ||
| 126 | */ | ||
| 127 | static int | ||
| 128 | dh_is_well_known_p(const BIGNUM *p, BN_CTX *ctx, int *is_well_known) | ||
| 129 | { | ||
| 130 | BIGNUM *bn; | ||
| 131 | size_t i; | ||
| 132 | int ret = 0; | ||
| 133 | |||
| 134 | *is_well_known = 0; | ||
| 135 | |||
| 136 | BN_CTX_start(ctx); | ||
| 137 | if ((bn = BN_CTX_get(ctx)) == NULL) | ||
| 138 | goto err; | ||
| 139 | |||
| 140 | for (i = 0; i < N_WELL_KNOWN_P_FN; i++) { | ||
| 141 | get_p_fn get_p = get_well_known_p[i]; | ||
| 142 | |||
| 143 | if (get_p(bn) == NULL) | ||
| 144 | goto err; | ||
| 145 | if (BN_cmp(bn, p) == 0) { | ||
| 146 | *is_well_known = 1; | ||
| 147 | break; | ||
| 148 | } | ||
| 149 | } | ||
| 150 | |||
| 151 | ret = 1; | ||
| 152 | |||
| 153 | err: | ||
| 154 | BN_CTX_end(ctx); | ||
| 155 | |||
| 156 | return ret; | ||
| 157 | } | ||
| 158 | |||
| 107 | /* | 159 | /* |
| 108 | * Check that p is a safe prime and that g is a suitable generator. | 160 | * Check that p is a safe prime and that g is a suitable generator. |
| 109 | */ | 161 | */ |
| @@ -112,7 +164,7 @@ int | |||
| 112 | DH_check(const DH *dh, int *flags) | 164 | DH_check(const DH *dh, int *flags) |
| 113 | { | 165 | { |
| 114 | BN_CTX *ctx = NULL; | 166 | BN_CTX *ctx = NULL; |
| 115 | int is_prime; | 167 | int is_prime, is_well_known; |
| 116 | int ok = 0; | 168 | int ok = 0; |
| 117 | 169 | ||
| 118 | *flags = 0; | 170 | *flags = 0; |
| @@ -150,6 +202,11 @@ DH_check(const DH *dh, int *flags) | |||
| 150 | *flags |= DH_CHECK_INVALID_Q_VALUE; | 202 | *flags |= DH_CHECK_INVALID_Q_VALUE; |
| 151 | } | 203 | } |
| 152 | 204 | ||
| 205 | if (!dh_is_well_known_p(dh->p, ctx, &is_well_known)) | ||
| 206 | goto err; | ||
| 207 | if (is_well_known) | ||
| 208 | goto done; | ||
| 209 | |||
| 153 | is_prime = BN_is_prime_ex(dh->p, DH_NUMBER_ITERATIONS_FOR_PRIME, | 210 | is_prime = BN_is_prime_ex(dh->p, DH_NUMBER_ITERATIONS_FOR_PRIME, |
| 154 | ctx, NULL); | 211 | ctx, NULL); |
| 155 | if (is_prime < 0) | 212 | if (is_prime < 0) |
| @@ -171,6 +228,7 @@ DH_check(const DH *dh, int *flags) | |||
| 171 | *flags |= DH_CHECK_P_NOT_SAFE_PRIME; | 228 | *flags |= DH_CHECK_P_NOT_SAFE_PRIME; |
| 172 | } | 229 | } |
| 173 | 230 | ||
| 231 | done: | ||
| 174 | ok = 1; | 232 | ok = 1; |
| 175 | 233 | ||
| 176 | err: | 234 | err: |
