comparison src/event/ngx_event_openssl.c @ 8084:0f3d98e4bcc5

SSL: automatic rotation of session ticket keys. As long as ssl_session_cache in shared memory is configured, session ticket keys are now automatically generated in shared memory, and rotated periodically. This can be beneficial from forward secrecy point of view, and also avoids increased CPU usage after configuration reloads. This also helps BoringSSL to properly resume sessions in configurations with multiple worker processes and no ssl_session_ticket_key directives, as BoringSSL tries to automatically rotate session ticket keys and does this independently in different worker processes, thus breaking session resumption between worker processes.
author Maxim Dounin <mdounin@mdounin.ru>
date Wed, 12 Oct 2022 20:14:53 +0300
parents e13a271bdd40
children 043006e5a0b1
comparison
equal deleted inserted replaced
8083:e13a271bdd40 8084:0f3d98e4bcc5
72 72
73 #ifdef SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB 73 #ifdef SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB
74 static int ngx_ssl_ticket_key_callback(ngx_ssl_conn_t *ssl_conn, 74 static int ngx_ssl_ticket_key_callback(ngx_ssl_conn_t *ssl_conn,
75 unsigned char *name, unsigned char *iv, EVP_CIPHER_CTX *ectx, 75 unsigned char *name, unsigned char *iv, EVP_CIPHER_CTX *ectx,
76 HMAC_CTX *hctx, int enc); 76 HMAC_CTX *hctx, int enc);
77 static ngx_int_t ngx_ssl_rotate_ticket_keys(SSL_CTX *ssl_ctx, ngx_log_t *log);
77 static void ngx_ssl_ticket_keys_cleanup(void *data); 78 static void ngx_ssl_ticket_keys_cleanup(void *data);
78 #endif 79 #endif
79 80
80 #ifndef X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT 81 #ifndef X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT
81 static ngx_int_t ngx_ssl_check_name(ngx_str_t *name, ASN1_STRING *str); 82 static ngx_int_t ngx_ssl_check_name(ngx_str_t *name, ASN1_STRING *str);
3768 ngx_rbtree_init(&cache->session_rbtree, &cache->sentinel, 3769 ngx_rbtree_init(&cache->session_rbtree, &cache->sentinel,
3769 ngx_ssl_session_rbtree_insert_value); 3770 ngx_ssl_session_rbtree_insert_value);
3770 3771
3771 ngx_queue_init(&cache->expire_queue); 3772 ngx_queue_init(&cache->expire_queue);
3772 3773
3774 cache->ticket_keys[0].expire = 0;
3775 cache->ticket_keys[1].expire = 0;
3776
3773 cache->fail_time = 0; 3777 cache->fail_time = 0;
3774 3778
3775 len = sizeof(" in SSL session shared cache \"\"") + shm_zone->shm.name.len; 3779 len = sizeof(" in SSL session shared cache \"\"") + shm_zone->shm.name.len;
3776 3780
3777 shpool->log_ctx = ngx_slab_alloc(shpool, len); 3781 shpool->log_ctx = ngx_slab_alloc(shpool, len);
4238 ngx_array_t *keys; 4242 ngx_array_t *keys;
4239 ngx_file_info_t fi; 4243 ngx_file_info_t fi;
4240 ngx_pool_cleanup_t *cln; 4244 ngx_pool_cleanup_t *cln;
4241 ngx_ssl_ticket_key_t *key; 4245 ngx_ssl_ticket_key_t *key;
4242 4246
4243 if (paths == NULL) { 4247 if (paths == NULL
4248 && SSL_CTX_get_ex_data(ssl->ctx, ngx_ssl_session_cache_index) == NULL)
4249 {
4244 return NGX_OK; 4250 return NGX_OK;
4245 } 4251 }
4246 4252
4247 keys = ngx_array_create(cf->pool, paths->nelts, 4253 keys = ngx_array_create(cf->pool, paths ? paths->nelts : 2,
4248 sizeof(ngx_ssl_ticket_key_t)); 4254 sizeof(ngx_ssl_ticket_key_t));
4249 if (keys == NULL) { 4255 if (keys == NULL) {
4250 return NGX_ERROR; 4256 return NGX_ERROR;
4251 } 4257 }
4252 4258
4255 return NGX_ERROR; 4261 return NGX_ERROR;
4256 } 4262 }
4257 4263
4258 cln->handler = ngx_ssl_ticket_keys_cleanup; 4264 cln->handler = ngx_ssl_ticket_keys_cleanup;
4259 cln->data = keys; 4265 cln->data = keys;
4260
4261 path = paths->elts;
4262 for (i = 0; i < paths->nelts; i++) {
4263
4264 if (ngx_conf_full_name(cf->cycle, &path[i], 1) != NGX_OK) {
4265 return NGX_ERROR;
4266 }
4267
4268 ngx_memzero(&file, sizeof(ngx_file_t));
4269 file.name = path[i];
4270 file.log = cf->log;
4271
4272 file.fd = ngx_open_file(file.name.data, NGX_FILE_RDONLY,
4273 NGX_FILE_OPEN, 0);
4274
4275 if (file.fd == NGX_INVALID_FILE) {
4276 ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno,
4277 ngx_open_file_n " \"%V\" failed", &file.name);
4278 return NGX_ERROR;
4279 }
4280
4281 if (ngx_fd_info(file.fd, &fi) == NGX_FILE_ERROR) {
4282 ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno,
4283 ngx_fd_info_n " \"%V\" failed", &file.name);
4284 goto failed;
4285 }
4286
4287 size = ngx_file_size(&fi);
4288
4289 if (size != 48 && size != 80) {
4290 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
4291 "\"%V\" must be 48 or 80 bytes", &file.name);
4292 goto failed;
4293 }
4294
4295 n = ngx_read_file(&file, buf, size, 0);
4296
4297 if (n == NGX_ERROR) {
4298 ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno,
4299 ngx_read_file_n " \"%V\" failed", &file.name);
4300 goto failed;
4301 }
4302
4303 if ((size_t) n != size) {
4304 ngx_conf_log_error(NGX_LOG_CRIT, cf, 0,
4305 ngx_read_file_n " \"%V\" returned only "
4306 "%z bytes instead of %uz", &file.name, n, size);
4307 goto failed;
4308 }
4309
4310 key = ngx_array_push(keys);
4311 if (key == NULL) {
4312 goto failed;
4313 }
4314
4315 if (size == 48) {
4316 key->size = 48;
4317 ngx_memcpy(key->name, buf, 16);
4318 ngx_memcpy(key->aes_key, buf + 16, 16);
4319 ngx_memcpy(key->hmac_key, buf + 32, 16);
4320
4321 } else {
4322 key->size = 80;
4323 ngx_memcpy(key->name, buf, 16);
4324 ngx_memcpy(key->hmac_key, buf + 16, 32);
4325 ngx_memcpy(key->aes_key, buf + 48, 32);
4326 }
4327
4328 if (ngx_close_file(file.fd) == NGX_FILE_ERROR) {
4329 ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno,
4330 ngx_close_file_n " \"%V\" failed", &file.name);
4331 }
4332
4333 ngx_explicit_memzero(&buf, 80);
4334 }
4335 4266
4336 if (SSL_CTX_set_ex_data(ssl->ctx, ngx_ssl_ticket_keys_index, keys) == 0) { 4267 if (SSL_CTX_set_ex_data(ssl->ctx, ngx_ssl_ticket_keys_index, keys) == 0) {
4337 ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, 4268 ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
4338 "SSL_CTX_set_ex_data() failed"); 4269 "SSL_CTX_set_ex_data() failed");
4339 return NGX_ERROR; 4270 return NGX_ERROR;
4345 ngx_log_error(NGX_LOG_WARN, cf->log, 0, 4276 ngx_log_error(NGX_LOG_WARN, cf->log, 0,
4346 "nginx was built with Session Tickets support, however, " 4277 "nginx was built with Session Tickets support, however, "
4347 "now it is linked dynamically to an OpenSSL library " 4278 "now it is linked dynamically to an OpenSSL library "
4348 "which has no tlsext support, therefore Session Tickets " 4279 "which has no tlsext support, therefore Session Tickets "
4349 "are not available"); 4280 "are not available");
4281 return NGX_OK;
4282 }
4283
4284 if (paths == NULL) {
4285
4286 /* placeholder for keys in shared memory */
4287
4288 key = ngx_array_push_n(keys, 2);
4289 key[0].shared = 1;
4290 key[1].shared = 1;
4291
4292 return NGX_OK;
4293 }
4294
4295 path = paths->elts;
4296 for (i = 0; i < paths->nelts; i++) {
4297
4298 if (ngx_conf_full_name(cf->cycle, &path[i], 1) != NGX_OK) {
4299 return NGX_ERROR;
4300 }
4301
4302 ngx_memzero(&file, sizeof(ngx_file_t));
4303 file.name = path[i];
4304 file.log = cf->log;
4305
4306 file.fd = ngx_open_file(file.name.data, NGX_FILE_RDONLY,
4307 NGX_FILE_OPEN, 0);
4308
4309 if (file.fd == NGX_INVALID_FILE) {
4310 ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno,
4311 ngx_open_file_n " \"%V\" failed", &file.name);
4312 return NGX_ERROR;
4313 }
4314
4315 if (ngx_fd_info(file.fd, &fi) == NGX_FILE_ERROR) {
4316 ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno,
4317 ngx_fd_info_n " \"%V\" failed", &file.name);
4318 goto failed;
4319 }
4320
4321 size = ngx_file_size(&fi);
4322
4323 if (size != 48 && size != 80) {
4324 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
4325 "\"%V\" must be 48 or 80 bytes", &file.name);
4326 goto failed;
4327 }
4328
4329 n = ngx_read_file(&file, buf, size, 0);
4330
4331 if (n == NGX_ERROR) {
4332 ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno,
4333 ngx_read_file_n " \"%V\" failed", &file.name);
4334 goto failed;
4335 }
4336
4337 if ((size_t) n != size) {
4338 ngx_conf_log_error(NGX_LOG_CRIT, cf, 0,
4339 ngx_read_file_n " \"%V\" returned only "
4340 "%z bytes instead of %uz", &file.name, n, size);
4341 goto failed;
4342 }
4343
4344 key = ngx_array_push(keys);
4345 if (key == NULL) {
4346 goto failed;
4347 }
4348
4349 key->shared = 0;
4350
4351 if (size == 48) {
4352 key->size = 48;
4353 ngx_memcpy(key->name, buf, 16);
4354 ngx_memcpy(key->aes_key, buf + 16, 16);
4355 ngx_memcpy(key->hmac_key, buf + 32, 16);
4356
4357 } else {
4358 key->size = 80;
4359 ngx_memcpy(key->name, buf, 16);
4360 ngx_memcpy(key->hmac_key, buf + 16, 32);
4361 ngx_memcpy(key->aes_key, buf + 48, 32);
4362 }
4363
4364 if (ngx_close_file(file.fd) == NGX_FILE_ERROR) {
4365 ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno,
4366 ngx_close_file_n " \"%V\" failed", &file.name);
4367 }
4368
4369 ngx_explicit_memzero(&buf, 80);
4350 } 4370 }
4351 4371
4352 return NGX_OK; 4372 return NGX_OK;
4353 4373
4354 failed: 4374 failed:
4379 const EVP_CIPHER *cipher; 4399 const EVP_CIPHER *cipher;
4380 4400
4381 c = ngx_ssl_get_connection(ssl_conn); 4401 c = ngx_ssl_get_connection(ssl_conn);
4382 ssl_ctx = c->ssl->session_ctx; 4402 ssl_ctx = c->ssl->session_ctx;
4383 4403
4404 if (ngx_ssl_rotate_ticket_keys(ssl_ctx, c->log) != NGX_OK) {
4405 return -1;
4406 }
4407
4384 #ifdef OPENSSL_NO_SHA256 4408 #ifdef OPENSSL_NO_SHA256
4385 digest = EVP_sha1(); 4409 digest = EVP_sha1();
4386 #else 4410 #else
4387 digest = EVP_sha256(); 4411 digest = EVP_sha256();
4388 #endif 4412 #endif
4494 return 2; 4518 return 2;
4495 } 4519 }
4496 4520
4497 return 1; 4521 return 1;
4498 } 4522 }
4523 }
4524
4525
4526 static ngx_int_t
4527 ngx_ssl_rotate_ticket_keys(SSL_CTX *ssl_ctx, ngx_log_t *log)
4528 {
4529 time_t now, expire;
4530 ngx_array_t *keys;
4531 ngx_shm_zone_t *shm_zone;
4532 ngx_slab_pool_t *shpool;
4533 ngx_ssl_ticket_key_t *key;
4534 ngx_ssl_session_cache_t *cache;
4535 u_char buf[80];
4536
4537 keys = SSL_CTX_get_ex_data(ssl_ctx, ngx_ssl_ticket_keys_index);
4538 if (keys == NULL) {
4539 return NGX_OK;
4540 }
4541
4542 key = keys->elts;
4543
4544 if (!key[0].shared) {
4545 return NGX_OK;
4546 }
4547
4548 now = ngx_time();
4549 expire = now + SSL_CTX_get_timeout(ssl_ctx);
4550
4551 shm_zone = SSL_CTX_get_ex_data(ssl_ctx, ngx_ssl_session_cache_index);
4552
4553 cache = shm_zone->data;
4554 shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
4555
4556 ngx_shmtx_lock(&shpool->mutex);
4557
4558 key = cache->ticket_keys;
4559
4560 if (key[0].expire == 0) {
4561
4562 /* initialize the current key */
4563
4564 if (RAND_bytes(buf, 80) != 1) {
4565 ngx_ssl_error(NGX_LOG_ALERT, log, 0, "RAND_bytes() failed");
4566 ngx_shmtx_unlock(&shpool->mutex);
4567 return NGX_ERROR;
4568 }
4569
4570 key->shared = 1;
4571 key->expire = expire;
4572 key->size = 80;
4573 ngx_memcpy(key->name, buf, 16);
4574 ngx_memcpy(key->hmac_key, buf + 16, 32);
4575 ngx_memcpy(key->aes_key, buf + 48, 32);
4576
4577 ngx_explicit_memzero(&buf, 80);
4578
4579 ngx_log_debug2(NGX_LOG_DEBUG_EVENT, log, 0,
4580 "ssl ticket key: \"%*xs\"",
4581 (size_t) 16, key->name);
4582 }
4583
4584 if (key[1].expire < now) {
4585
4586 /*
4587 * if the previous key is no longer needed (or not initialized),
4588 * replace it with the current key and generate new current key
4589 */
4590
4591 key[1] = key[0];
4592
4593 if (RAND_bytes(buf, 80) != 1) {
4594 ngx_ssl_error(NGX_LOG_ALERT, log, 0, "RAND_bytes() failed");
4595 ngx_shmtx_unlock(&shpool->mutex);
4596 return NGX_ERROR;
4597 }
4598
4599 key->shared = 1;
4600 key->expire = expire;
4601 key->size = 80;
4602 ngx_memcpy(key->name, buf, 16);
4603 ngx_memcpy(key->hmac_key, buf + 16, 32);
4604 ngx_memcpy(key->aes_key, buf + 48, 32);
4605
4606 ngx_explicit_memzero(&buf, 80);
4607
4608 ngx_log_debug2(NGX_LOG_DEBUG_EVENT, log, 0,
4609 "ssl ticket key: \"%*xs\"",
4610 (size_t) 16, key->name);
4611 }
4612
4613 /*
4614 * update expiration of the current key: it is going to be needed
4615 * at least till the session being created expires
4616 */
4617
4618 if (expire > key[0].expire) {
4619 key[0].expire = expire;
4620 }
4621
4622 /* sync keys to the worker process memory */
4623
4624 ngx_memcpy(keys->elts, cache->ticket_keys,
4625 2 * sizeof(ngx_ssl_ticket_key_t));
4626
4627 ngx_shmtx_unlock(&shpool->mutex);
4628
4629 return NGX_OK;
4499 } 4630 }
4500 4631
4501 4632
4502 static void 4633 static void
4503 ngx_ssl_ticket_keys_cleanup(void *data) 4634 ngx_ssl_ticket_keys_cleanup(void *data)