comparison src/event/ngx_event_openssl.c @ 8085:043006e5a0b1

SSL: optimized rotation of session ticket keys. Instead of syncing keys with shared memory on each ticket operation, the code now does this only when the worker is going to change expiration of the current key, or going to switch to a new key: that is, usually at most once per second. To do so without races, the code maintains 3 keys: current, previous, and next. If a worker will switch to the next key earlier, other workers will still be able to decrypt new tickets, since they will be encrypted with the next key.
author Maxim Dounin <mdounin@mdounin.ru>
date Wed, 12 Oct 2022 20:14:55 +0300
parents 0f3d98e4bcc5
children 496241338da5
comparison
equal deleted inserted replaced
8084:0f3d98e4bcc5 8085:043006e5a0b1
3771 3771
3772 ngx_queue_init(&cache->expire_queue); 3772 ngx_queue_init(&cache->expire_queue);
3773 3773
3774 cache->ticket_keys[0].expire = 0; 3774 cache->ticket_keys[0].expire = 0;
3775 cache->ticket_keys[1].expire = 0; 3775 cache->ticket_keys[1].expire = 0;
3776 cache->ticket_keys[2].expire = 0;
3776 3777
3777 cache->fail_time = 0; 3778 cache->fail_time = 0;
3778 3779
3779 len = sizeof(" in SSL session shared cache \"\"") + shm_zone->shm.name.len; 3780 len = sizeof(" in SSL session shared cache \"\"") + shm_zone->shm.name.len;
3780 3781
4248 && SSL_CTX_get_ex_data(ssl->ctx, ngx_ssl_session_cache_index) == NULL) 4249 && SSL_CTX_get_ex_data(ssl->ctx, ngx_ssl_session_cache_index) == NULL)
4249 { 4250 {
4250 return NGX_OK; 4251 return NGX_OK;
4251 } 4252 }
4252 4253
4253 keys = ngx_array_create(cf->pool, paths ? paths->nelts : 2, 4254 keys = ngx_array_create(cf->pool, paths ? paths->nelts : 3,
4254 sizeof(ngx_ssl_ticket_key_t)); 4255 sizeof(ngx_ssl_ticket_key_t));
4255 if (keys == NULL) { 4256 if (keys == NULL) {
4256 return NGX_ERROR; 4257 return NGX_ERROR;
4257 } 4258 }
4258 4259
4283 4284
4284 if (paths == NULL) { 4285 if (paths == NULL) {
4285 4286
4286 /* placeholder for keys in shared memory */ 4287 /* placeholder for keys in shared memory */
4287 4288
4288 key = ngx_array_push_n(keys, 2); 4289 key = ngx_array_push_n(keys, 3);
4289 key[0].shared = 1; 4290 key[0].shared = 1;
4291 key[0].expire = 0;
4290 key[1].shared = 1; 4292 key[1].shared = 1;
4293 key[1].expire = 0;
4294 key[2].shared = 1;
4295 key[2].expire = 0;
4291 4296
4292 return NGX_OK; 4297 return NGX_OK;
4293 } 4298 }
4294 4299
4295 path = paths->elts; 4300 path = paths->elts;
4345 if (key == NULL) { 4350 if (key == NULL) {
4346 goto failed; 4351 goto failed;
4347 } 4352 }
4348 4353
4349 key->shared = 0; 4354 key->shared = 0;
4355 key->expire = 1;
4350 4356
4351 if (size == 48) { 4357 if (size == 48) {
4352 key->size = 48; 4358 key->size = 48;
4353 ngx_memcpy(key->name, buf, 16); 4359 ngx_memcpy(key->name, buf, 16);
4354 ngx_memcpy(key->aes_key, buf + 16, 16); 4360 ngx_memcpy(key->aes_key, buf + 16, 16);
4512 } 4518 }
4513 #endif 4519 #endif
4514 4520
4515 /* renew if non-default key */ 4521 /* renew if non-default key */
4516 4522
4517 if (i != 0) { 4523 if (i != 0 && key[i].expire) {
4518 return 2; 4524 return 2;
4519 } 4525 }
4520 4526
4521 return 1; 4527 return 1;
4522 } 4528 }
4543 4549
4544 if (!key[0].shared) { 4550 if (!key[0].shared) {
4545 return NGX_OK; 4551 return NGX_OK;
4546 } 4552 }
4547 4553
4554 /*
4555 * if we don't need to update expiration of the current key
4556 * and the previous key is still needed, don't sync with shared
4557 * memory to save some work; in the worst case other worker process
4558 * will switch to the next key, but this process will still be able
4559 * to decrypt tickets encrypted with it
4560 */
4561
4548 now = ngx_time(); 4562 now = ngx_time();
4549 expire = now + SSL_CTX_get_timeout(ssl_ctx); 4563 expire = now + SSL_CTX_get_timeout(ssl_ctx);
4564
4565 if (key[0].expire >= expire && key[1].expire >= now) {
4566 return NGX_OK;
4567 }
4550 4568
4551 shm_zone = SSL_CTX_get_ex_data(ssl_ctx, ngx_ssl_session_cache_index); 4569 shm_zone = SSL_CTX_get_ex_data(ssl_ctx, ngx_ssl_session_cache_index);
4552 4570
4553 cache = shm_zone->data; 4571 cache = shm_zone->data;
4554 shpool = (ngx_slab_pool_t *) shm_zone->shm.addr; 4572 shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
4565 ngx_ssl_error(NGX_LOG_ALERT, log, 0, "RAND_bytes() failed"); 4583 ngx_ssl_error(NGX_LOG_ALERT, log, 0, "RAND_bytes() failed");
4566 ngx_shmtx_unlock(&shpool->mutex); 4584 ngx_shmtx_unlock(&shpool->mutex);
4567 return NGX_ERROR; 4585 return NGX_ERROR;
4568 } 4586 }
4569 4587
4570 key->shared = 1; 4588 key[0].shared = 1;
4571 key->expire = expire; 4589 key[0].expire = expire;
4572 key->size = 80; 4590 key[0].size = 80;
4573 ngx_memcpy(key->name, buf, 16); 4591 ngx_memcpy(key[0].name, buf, 16);
4574 ngx_memcpy(key->hmac_key, buf + 16, 32); 4592 ngx_memcpy(key[0].hmac_key, buf + 16, 32);
4575 ngx_memcpy(key->aes_key, buf + 48, 32); 4593 ngx_memcpy(key[0].aes_key, buf + 48, 32);
4576 4594
4577 ngx_explicit_memzero(&buf, 80); 4595 ngx_explicit_memzero(&buf, 80);
4578 4596
4579 ngx_log_debug2(NGX_LOG_DEBUG_EVENT, log, 0, 4597 ngx_log_debug2(NGX_LOG_DEBUG_EVENT, log, 0,
4580 "ssl ticket key: \"%*xs\"", 4598 "ssl ticket key: \"%*xs\"",
4581 (size_t) 16, key->name); 4599 (size_t) 16, key[0].name);
4600
4601 /*
4602 * copy the current key to the next key, as initialization of
4603 * the previous key will replace the current key with the next
4604 * key
4605 */
4606
4607 key[2] = key[0];
4582 } 4608 }
4583 4609
4584 if (key[1].expire < now) { 4610 if (key[1].expire < now) {
4585 4611
4586 /* 4612 /*
4587 * if the previous key is no longer needed (or not initialized), 4613 * if the previous key is no longer needed (or not initialized),
4588 * replace it with the current key and generate new current key 4614 * replace it with the current key, replace the current key with
4615 * the next key, and generate new next key
4589 */ 4616 */
4590 4617
4591 key[1] = key[0]; 4618 key[1] = key[0];
4619 key[0] = key[2];
4592 4620
4593 if (RAND_bytes(buf, 80) != 1) { 4621 if (RAND_bytes(buf, 80) != 1) {
4594 ngx_ssl_error(NGX_LOG_ALERT, log, 0, "RAND_bytes() failed"); 4622 ngx_ssl_error(NGX_LOG_ALERT, log, 0, "RAND_bytes() failed");
4595 ngx_shmtx_unlock(&shpool->mutex); 4623 ngx_shmtx_unlock(&shpool->mutex);
4596 return NGX_ERROR; 4624 return NGX_ERROR;
4597 } 4625 }
4598 4626
4599 key->shared = 1; 4627 key[2].shared = 1;
4600 key->expire = expire; 4628 key[2].expire = 0;
4601 key->size = 80; 4629 key[2].size = 80;
4602 ngx_memcpy(key->name, buf, 16); 4630 ngx_memcpy(key[2].name, buf, 16);
4603 ngx_memcpy(key->hmac_key, buf + 16, 32); 4631 ngx_memcpy(key[2].hmac_key, buf + 16, 32);
4604 ngx_memcpy(key->aes_key, buf + 48, 32); 4632 ngx_memcpy(key[2].aes_key, buf + 48, 32);
4605 4633
4606 ngx_explicit_memzero(&buf, 80); 4634 ngx_explicit_memzero(&buf, 80);
4607 4635
4608 ngx_log_debug2(NGX_LOG_DEBUG_EVENT, log, 0, 4636 ngx_log_debug2(NGX_LOG_DEBUG_EVENT, log, 0,
4609 "ssl ticket key: \"%*xs\"", 4637 "ssl ticket key: \"%*xs\"",
4610 (size_t) 16, key->name); 4638 (size_t) 16, key[2].name);
4611 } 4639 }
4612 4640
4613 /* 4641 /*
4614 * update expiration of the current key: it is going to be needed 4642 * update expiration of the current key: it is going to be needed
4615 * at least till the session being created expires 4643 * at least till the session being created expires