crypto_dh_openssl.c (10995B)
1 /* Copyright (c) 2001, Matej Pfajfar. 2 * Copyright (c) 2001-2004, Roger Dingledine. 3 * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. 4 * Copyright (c) 2007-2021, The Tor Project, Inc. */ 5 /* See LICENSE for licensing information */ 6 7 /** 8 * \file crypto_dh_openssl.c 9 * \brief Implement Tor's Z_p diffie-hellman stuff for OpenSSL. 10 **/ 11 12 #include "lib/crypt_ops/compat_openssl.h" 13 #include "lib/crypt_ops/crypto_dh.h" 14 #include "lib/crypt_ops/crypto_digest.h" 15 #include "lib/crypt_ops/crypto_hkdf.h" 16 #include "lib/crypt_ops/crypto_util.h" 17 #include "lib/log/log.h" 18 #include "lib/log/util_bug.h" 19 20 DISABLE_GCC_WARNING("-Wredundant-decls") 21 22 #include <openssl/dh.h> 23 24 ENABLE_GCC_WARNING("-Wredundant-decls") 25 26 #include <openssl/bn.h> 27 #include <string.h> 28 29 #ifndef ENABLE_NSS 30 static int tor_check_dh_key(int severity, const BIGNUM *bn, 31 const BIGNUM *dh_p); 32 33 /** A structure to hold the first half (x, g^x) of a Diffie-Hellman handshake 34 * while we're waiting for the second.*/ 35 struct crypto_dh_t { 36 DH *dh; /**< The openssl DH object */ 37 }; 38 #endif /* !defined(ENABLE_NSS) */ 39 40 static DH *new_openssl_dh_from_params(BIGNUM *p, BIGNUM *g); 41 42 /** Shared P parameter for our circuit-crypto DH key exchanges. */ 43 static BIGNUM *dh_param_p = NULL; 44 /** Shared P parameter for our TLS DH key exchanges. */ 45 static BIGNUM *dh_param_p_tls = NULL; 46 /** Shared G parameter for our DH key exchanges. */ 47 static BIGNUM *dh_param_g = NULL; 48 49 /* This function is disabled unless we change the DH parameters. */ 50 #if 0 51 /** Validate a given set of Diffie-Hellman parameters. This is moderately 52 * computationally expensive (milliseconds), so should only be called when 53 * the DH parameters change. Returns 0 on success, * -1 on failure. 54 */ 55 static int 56 crypto_validate_dh_params(const BIGNUM *p, const BIGNUM *g) 57 { 58 DH *dh = NULL; 59 int ret = -1; 60 61 /* Copy into a temporary DH object, just so that DH_check() can be called. */ 62 if (!(dh = DH_new())) 63 goto out; 64 65 BIGNUM *dh_p, *dh_g; 66 if (!(dh_p = BN_dup(p))) 67 goto out; 68 if (!(dh_g = BN_dup(g))) 69 goto out; 70 if (!DH_set0_pqg(dh, dh_p, NULL, dh_g)) 71 goto out; 72 73 /* Perform the validation. */ 74 int codes = 0; 75 if (!DH_check(dh, &codes)) 76 goto out; 77 if (BN_is_word(g, DH_GENERATOR_2)) { 78 /* Per https://wiki.openssl.org/index.php/Diffie-Hellman_parameters 79 * 80 * OpenSSL checks the prime is congruent to 11 when g = 2; while the 81 * IETF's primes are congruent to 23 when g = 2. 82 */ 83 BN_ULONG residue = BN_mod_word(p, 24); 84 if (residue == 11 || residue == 23) 85 codes &= ~DH_NOT_SUITABLE_GENERATOR; 86 } 87 if (codes != 0) /* Specifics on why the params suck is irrelevant. */ 88 goto out; 89 90 /* Things are probably not evil. */ 91 ret = 0; 92 93 out: 94 if (dh) 95 DH_free(dh); 96 return ret; 97 } 98 #endif /* 0 */ 99 100 /** 101 * Helper: convert <b>hex</b> to a bignum, and return it. Assert that the 102 * operation was successful. 103 */ 104 static BIGNUM * 105 bignum_from_hex(const char *hex) 106 { 107 BIGNUM *result = BN_new(); 108 tor_assert(result); 109 110 int r = BN_hex2bn(&result, hex); 111 tor_assert(r); 112 tor_assert(result); 113 return result; 114 } 115 116 /** Set the global Diffie-Hellman generator, used for both TLS and internal 117 * DH stuff. 118 */ 119 static void 120 crypto_set_dh_generator(void) 121 { 122 BIGNUM *generator; 123 int r; 124 125 if (dh_param_g) 126 return; 127 128 generator = BN_new(); 129 tor_assert(generator); 130 131 r = BN_set_word(generator, DH_GENERATOR); 132 tor_assert(r); 133 134 dh_param_g = generator; 135 } 136 137 /** Initialize our DH parameters. Idempotent. */ 138 void 139 crypto_dh_init_openssl(void) 140 { 141 if (dh_param_p && dh_param_g && dh_param_p_tls) 142 return; 143 144 tor_assert(dh_param_g == NULL); 145 tor_assert(dh_param_p == NULL); 146 tor_assert(dh_param_p_tls == NULL); 147 148 crypto_set_dh_generator(); 149 dh_param_p = bignum_from_hex(OAKLEY_PRIME_2); 150 dh_param_p_tls = bignum_from_hex(TLS_DH_PRIME); 151 152 /* Checks below are disabled unless we change the hardcoded DH parameters. */ 153 #if 0 154 tor_assert(0 == crypto_validate_dh_params(dh_param_p, dh_param_g)); 155 tor_assert(0 == crypto_validate_dh_params(dh_param_p_tls, dh_param_g)); 156 #endif 157 } 158 159 /** Number of bits to use when choosing the x or y value in a Diffie-Hellman 160 * handshake. Since we exponentiate by this value, choosing a smaller one 161 * lets our handshake go faster. 162 */ 163 #define DH_PRIVATE_KEY_BITS 320 164 165 /** Used by tortls.c: Get the DH* for use with TLS. 166 */ 167 DH * 168 crypto_dh_new_openssl_tls(void) 169 { 170 return new_openssl_dh_from_params(dh_param_p_tls, dh_param_g); 171 } 172 173 #ifndef ENABLE_NSS 174 /** Allocate and return a new DH object for a key exchange. Returns NULL on 175 * failure. 176 */ 177 crypto_dh_t * 178 crypto_dh_new(int dh_type) 179 { 180 crypto_dh_t *res = tor_malloc_zero(sizeof(crypto_dh_t)); 181 182 tor_assert(dh_type == DH_TYPE_CIRCUIT || dh_type == DH_TYPE_TLS || 183 dh_type == DH_TYPE_REND); 184 185 if (!dh_param_p) 186 crypto_dh_init(); 187 188 BIGNUM *dh_p = NULL; 189 if (dh_type == DH_TYPE_TLS) { 190 dh_p = dh_param_p_tls; 191 } else { 192 dh_p = dh_param_p; 193 } 194 195 res->dh = new_openssl_dh_from_params(dh_p, dh_param_g); 196 if (res->dh == NULL) 197 tor_free(res); // sets res to NULL. 198 return res; 199 } 200 #endif /* !defined(ENABLE_NSS) */ 201 202 /** Create and return a new openssl DH from a given prime and generator. */ 203 static DH * 204 new_openssl_dh_from_params(BIGNUM *p, BIGNUM *g) 205 { 206 DH *res_dh; 207 if (!(res_dh = DH_new())) 208 goto err; 209 210 BIGNUM *dh_p = NULL, *dh_g = NULL; 211 dh_p = BN_dup(p); 212 if (!dh_p) 213 goto err; 214 215 dh_g = BN_dup(g); 216 if (!dh_g) { 217 BN_free(dh_p); 218 goto err; 219 } 220 221 if (!DH_set0_pqg(res_dh, dh_p, NULL, dh_g)) { 222 goto err; 223 } 224 225 if (!DH_set_length(res_dh, DH_PRIVATE_KEY_BITS)) 226 goto err; 227 228 return res_dh; 229 230 /* LCOV_EXCL_START 231 * This error condition is only reached when an allocation fails */ 232 err: 233 crypto_openssl_log_errors(LOG_WARN, "creating DH object"); 234 if (res_dh) DH_free(res_dh); /* frees p and g too */ 235 return NULL; 236 /* LCOV_EXCL_STOP */ 237 } 238 239 #ifndef ENABLE_NSS 240 /** Return a copy of <b>dh</b>, sharing its internal state. */ 241 crypto_dh_t * 242 crypto_dh_dup(const crypto_dh_t *dh) 243 { 244 crypto_dh_t *dh_new = tor_malloc_zero(sizeof(crypto_dh_t)); 245 tor_assert(dh); 246 tor_assert(dh->dh); 247 dh_new->dh = dh->dh; 248 DH_up_ref(dh->dh); 249 return dh_new; 250 } 251 252 /** Return the length of the DH key in <b>dh</b>, in bytes. 253 */ 254 int 255 crypto_dh_get_bytes(crypto_dh_t *dh) 256 { 257 tor_assert(dh); 258 return DH_size(dh->dh); 259 } 260 261 /** Generate \<x,g^x\> for our part of the key exchange. Return 0 on 262 * success, -1 on failure. 263 */ 264 int 265 crypto_dh_generate_public(crypto_dh_t *dh) 266 { 267 if (!DH_generate_key(dh->dh)) { 268 /* LCOV_EXCL_START 269 * To test this we would need some way to tell openssl to break DH. */ 270 crypto_openssl_log_errors(LOG_WARN, "generating DH key"); 271 return -1; 272 /* LCOV_EXCL_STOP */ 273 } 274 275 /* OpenSSL 1.1.x doesn't appear to let you regenerate a DH key, without 276 * recreating the DH object. I have no idea what sort of aliasing madness 277 * can occur here, so do the check, and just bail on failure. 278 */ 279 const BIGNUM *pub_key, *priv_key; 280 DH_get0_key(dh->dh, &pub_key, &priv_key); 281 if (tor_check_dh_key(LOG_WARN, pub_key, DH_get0_p(dh->dh))<0) { 282 log_warn(LD_CRYPTO, "Weird! Our own DH key was invalid. I guess once-in-" 283 "the-universe chances really do happen. Treating as a failure."); 284 return -1; 285 } 286 287 return 0; 288 } 289 290 /** Generate g^x as necessary, and write the g^x for the key exchange 291 * as a <b>pubkey_len</b>-byte value into <b>pubkey</b>. Return 0 on 292 * success, -1 on failure. <b>pubkey_len</b> must be \>= DH1024_KEY_LEN. 293 */ 294 int 295 crypto_dh_get_public(crypto_dh_t *dh, char *pubkey, size_t pubkey_len) 296 { 297 int bytes; 298 tor_assert(dh); 299 300 const BIGNUM *dh_pub; 301 302 const BIGNUM *dh_priv; 303 DH_get0_key(dh->dh, &dh_pub, &dh_priv); 304 305 if (!dh_pub) { 306 if (crypto_dh_generate_public(dh)<0) 307 return -1; 308 else { 309 DH_get0_key(dh->dh, &dh_pub, &dh_priv); 310 } 311 } 312 313 tor_assert(dh_pub); 314 bytes = BN_num_bytes(dh_pub); 315 tor_assert(bytes >= 0); 316 if (pubkey_len < (size_t)bytes) { 317 log_warn(LD_CRYPTO, 318 "Weird! pubkey_len (%d) was smaller than key length (%d)", 319 (int) pubkey_len, bytes); 320 return -1; 321 } 322 323 memset(pubkey, 0, pubkey_len); 324 BN_bn2bin(dh_pub, (unsigned char*)(pubkey+(pubkey_len-bytes))); 325 326 return 0; 327 } 328 329 /** Check for bad Diffie-Hellman public keys (g^x). Return 0 if the key is 330 * okay (in the subgroup [2,p-2]), or -1 if it's bad. 331 * See http://www.cl.cam.ac.uk/ftp/users/rja14/psandqs.ps.gz for some tips. 332 */ 333 static int 334 tor_check_dh_key(int severity, const BIGNUM *bn, const BIGNUM *dh_p) 335 { 336 BIGNUM *x; 337 char *s; 338 tor_assert(bn); 339 x = BN_new(); 340 tor_assert(x); 341 BN_set_word(x, 1); 342 if (BN_cmp(bn,x)<=0) { 343 log_fn(severity, LD_CRYPTO, "DH key must be at least 2."); 344 goto err; 345 } 346 BN_copy(x,dh_p); 347 BN_sub_word(x, 1); 348 if (BN_cmp(bn,x)>=0) { 349 log_fn(severity, LD_CRYPTO, "DH key must be at most p-2."); 350 goto err; 351 } 352 BN_clear_free(x); 353 return 0; 354 err: 355 BN_clear_free(x); 356 s = BN_bn2hex(bn); 357 log_fn(severity, LD_CRYPTO, "Rejecting insecure DH key [%s]", s); 358 OPENSSL_free(s); 359 return -1; 360 } 361 362 /** Given a DH key exchange object, and our peer's value of g^y (as a 363 * <b>pubkey_len</b>-byte value in <b>pubkey</b>) generate 364 * g^xy as a big-endian integer in <b>secret_out</b>. 365 * Return the number of bytes generated on success, 366 * or -1 on failure. 367 * 368 * This function MUST validate that g^y is actually in the group. 369 */ 370 ssize_t 371 crypto_dh_handshake(int severity, crypto_dh_t *dh, 372 const char *pubkey, size_t pubkey_len, 373 unsigned char *secret_out, size_t secret_bytes_out) 374 { 375 BIGNUM *pubkey_bn = NULL; 376 size_t secret_len=0; 377 int result=0; 378 379 tor_assert(dh); 380 tor_assert(secret_bytes_out/DIGEST_LEN <= 255); 381 tor_assert(pubkey_len < INT_MAX); 382 383 if (BUG(crypto_dh_get_bytes(dh) > (int)secret_bytes_out)) { 384 goto error; 385 } 386 387 if (!(pubkey_bn = BN_bin2bn((const unsigned char*)pubkey, 388 (int)pubkey_len, NULL))) 389 goto error; 390 if (tor_check_dh_key(severity, pubkey_bn, DH_get0_p(dh->dh))<0) { 391 /* Check for invalid public keys. */ 392 log_fn(severity, LD_CRYPTO,"Rejected invalid g^x"); 393 goto error; 394 } 395 result = DH_compute_key(secret_out, pubkey_bn, dh->dh); 396 if (result < 0) { 397 log_warn(LD_CRYPTO,"DH_compute_key() failed."); 398 goto error; 399 } 400 secret_len = result; 401 402 goto done; 403 error: 404 result = -1; 405 done: 406 crypto_openssl_log_errors(LOG_WARN, "completing DH handshake"); 407 if (pubkey_bn) 408 BN_clear_free(pubkey_bn); 409 if (result < 0) 410 return result; 411 else 412 return secret_len; 413 } 414 415 /** Free a DH key exchange object. 416 */ 417 void 418 crypto_dh_free_(crypto_dh_t *dh) 419 { 420 if (!dh) 421 return; 422 tor_assert(dh->dh); 423 DH_free(dh->dh); 424 tor_free(dh); 425 } 426 #endif /* !defined(ENABLE_NSS) */ 427 428 void 429 crypto_dh_free_all_openssl(void) 430 { 431 if (dh_param_p) 432 BN_clear_free(dh_param_p); 433 if (dh_param_p_tls) 434 BN_clear_free(dh_param_p_tls); 435 if (dh_param_g) 436 BN_clear_free(dh_param_g); 437 438 dh_param_p = dh_param_p_tls = dh_param_g = NULL; 439 }