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