Mercurial > hg > nginx
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 |