| // Copyright 1995-2016 The OpenSSL Project Authors. All Rights Reserved. | |
| // Copyright 2005 Nokia. All rights reserved. | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // https://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| #include <openssl/ssl.h> | |
| #include <assert.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <utility> | |
| #include <openssl/cipher.h> | |
| #include <openssl/err.h> | |
| #include <openssl/evp.h> | |
| #include <openssl/hmac.h> | |
| #include <openssl/mem.h> | |
| #include <openssl/pool.h> | |
| #include <openssl/rand.h> | |
| #include "../crypto/internal.h" | |
| #include "internal.h" | |
| BSSL_NAMESPACE_BEGIN | |
| // The address of this is a magic value, a pointer to which is returned by | |
| // SSL_magic_pending_session_ptr(). It allows a session callback to indicate | |
| // that it needs to asynchronously fetch session information. | |
| static const char g_pending_session_magic = 0; | |
| static ExDataClass g_ex_data_class(/*with_app_data=*/true); | |
| static void SSL_SESSION_list_remove(SSLContext *ctx, SSLSession *session); | |
| static void SSL_SESSION_list_add(SSLContext *ctx, SSLSession *session); | |
| static Span<const uint8_t> ssl_handshake_session_id_context( | |
| const SSL_HANDSHAKE *hs) { | |
| if (hs->ssl->server && hs->credential != nullptr && | |
| !hs->credential->sid_ctx.empty()) { | |
| return hs->credential->sid_ctx; | |
| } | |
| return hs->config->cert->sid_ctx; | |
| } | |
| UniquePtr<SSLSession> ssl_session_new(const SSL_X509_METHOD *x509_method) { | |
| return MakeUnique<SSLSession>(x509_method); | |
| } | |
| uint32_t ssl_hash_session_id(Span<const uint8_t> session_id) { | |
| // Take the first four bytes of `session_id`. Session IDs are generated by the | |
| // server randomly, so we can assume even using the first four bytes results | |
| // in a good distribution. | |
| uint8_t tmp_storage[sizeof(uint32_t)]; | |
| if (session_id.size() < sizeof(tmp_storage)) { | |
| OPENSSL_memset(tmp_storage, 0, sizeof(tmp_storage)); | |
| OPENSSL_memcpy(tmp_storage, session_id.data(), session_id.size()); | |
| session_id = tmp_storage; | |
| } | |
| uint32_t hash = ((uint32_t)session_id[0]) | ((uint32_t)session_id[1] << 8) | | |
| ((uint32_t)session_id[2] << 16) | | |
| ((uint32_t)session_id[3] << 24); | |
| return hash; | |
| } | |
| UniquePtr<SSLSession> SSL_SESSION_dup(const SSLSession *session, | |
| int dup_flags) { | |
| UniquePtr<SSLSession> new_session = ssl_session_new(session->x509_method); | |
| if (!new_session) { | |
| return nullptr; | |
| } | |
| new_session->is_server = session->is_server; | |
| new_session->ssl_version = session->ssl_version; | |
| new_session->is_quic = session->is_quic; | |
| new_session->sid_ctx = session->sid_ctx; | |
| // Copy the key material. | |
| new_session->secret = session->secret; | |
| new_session->cipher = session->cipher; | |
| // Copy authentication state. | |
| if (session->psk_identity != nullptr) { | |
| new_session->psk_identity.reset( | |
| OPENSSL_strdup(session->psk_identity.get())); | |
| if (new_session->psk_identity == nullptr) { | |
| return nullptr; | |
| } | |
| } | |
| new_session->peer_cert_type = session->peer_cert_type; | |
| if (session->certs != nullptr) { | |
| new_session->certs.reset(sk_CRYPTO_BUFFER_deep_copy( | |
| session->certs.get(), CRYPTO_BUFFER_dup_ref, CRYPTO_BUFFER_free)); | |
| if (new_session->certs == nullptr) { | |
| return nullptr; | |
| } | |
| } | |
| if (session->peer_raw_public_key != nullptr) { | |
| new_session->peer_raw_public_key = UpRef(session->peer_raw_public_key); | |
| } | |
| if (!session->x509_method->session_dup(new_session.get(), session)) { | |
| return nullptr; | |
| } | |
| new_session->verify_result = session->verify_result; | |
| new_session->ocsp_response = UpRef(session->ocsp_response); | |
| new_session->signed_cert_timestamp_list = | |
| UpRef(session->signed_cert_timestamp_list); | |
| OPENSSL_memcpy(new_session->peer_sha256, session->peer_sha256, | |
| SHA256_DIGEST_LENGTH); | |
| new_session->peer_sha256_valid = session->peer_sha256_valid; | |
| new_session->peer_signature_algorithm = session->peer_signature_algorithm; | |
| new_session->timeout = session->timeout; | |
| new_session->auth_timeout = session->auth_timeout; | |
| new_session->time = session->time; | |
| // Copy non-authentication connection properties. | |
| if (dup_flags & SSL_SESSION_INCLUDE_NONAUTH) { | |
| new_session->session_id = session->session_id; | |
| new_session->group_id = session->group_id; | |
| new_session->original_handshake_hash = session->original_handshake_hash; | |
| new_session->ticket_lifetime_hint = session->ticket_lifetime_hint; | |
| new_session->ticket_age_add = session->ticket_age_add; | |
| new_session->ticket_max_early_data = session->ticket_max_early_data; | |
| new_session->extended_master_secret = session->extended_master_secret; | |
| new_session->has_application_settings = session->has_application_settings; | |
| if (!new_session->early_alpn.CopyFrom(session->early_alpn) || | |
| !new_session->quic_early_data_context.CopyFrom( | |
| session->quic_early_data_context) || | |
| !new_session->local_application_settings.CopyFrom( | |
| session->local_application_settings) || | |
| !new_session->peer_application_settings.CopyFrom( | |
| session->peer_application_settings)) { | |
| return nullptr; | |
| } | |
| } | |
| // Copy the ticket. | |
| if (dup_flags & SSL_SESSION_INCLUDE_TICKET && | |
| !new_session->ticket.CopyFrom(session->ticket)) { | |
| return nullptr; | |
| } | |
| // The new_session does not get a copy of the ex_data. | |
| new_session->not_resumable = true; | |
| return new_session; | |
| } | |
| void ssl_session_rebase_time(SSLImpl *ssl, SSLSession *session) { | |
| OPENSSL_timeval now = ssl_ctx_get_current_time(ssl->ctx.get()); | |
| // To avoid overflows and underflows, if we've gone back in time, update the | |
| // time, but mark the session expired. | |
| if (session->time > now.tv_sec) { | |
| session->time = now.tv_sec; | |
| session->timeout = 0; | |
| session->auth_timeout = 0; | |
| return; | |
| } | |
| // Adjust the session time and timeouts. If the session has already expired, | |
| // clamp the timeouts at zero. | |
| uint64_t delta = now.tv_sec - session->time; | |
| session->time = now.tv_sec; | |
| if (session->timeout < delta) { | |
| session->timeout = 0; | |
| } else { | |
| session->timeout -= delta; | |
| } | |
| if (session->auth_timeout < delta) { | |
| session->auth_timeout = 0; | |
| } else { | |
| session->auth_timeout -= delta; | |
| } | |
| } | |
| void ssl_session_renew_timeout(SSLImpl *ssl, SSLSession *session, | |
| uint32_t timeout) { | |
| // Rebase the timestamp relative to the current time so `timeout` is measured | |
| // correctly. | |
| ssl_session_rebase_time(ssl, session); | |
| if (session->timeout > timeout) { | |
| return; | |
| } | |
| session->timeout = timeout; | |
| if (session->timeout > session->auth_timeout) { | |
| session->timeout = session->auth_timeout; | |
| } | |
| } | |
| uint16_t ssl_session_protocol_version(const SSLSession *session) { | |
| uint16_t ret; | |
| if (!ssl_protocol_version_from_wire(&ret, session->ssl_version)) { | |
| // An `SSL_SESSION` will never have an invalid version. This is enforced by | |
| // the parser. | |
| assert(0); | |
| return 0; | |
| } | |
| return ret; | |
| } | |
| const EVP_MD *ssl_session_get_digest(const SSLSession *session) { | |
| return ssl_get_handshake_digest(ssl_session_protocol_version(session), | |
| session->cipher); | |
| } | |
| bool ssl_get_new_session(SSL_HANDSHAKE *hs) { | |
| SSLImpl *const ssl = hs->ssl; | |
| if (ssl->mode & SSL_MODE_NO_SESSION_CREATION) { | |
| OPENSSL_PUT_ERROR(SSL, SSL_R_SESSION_MAY_NOT_BE_CREATED); | |
| return false; | |
| } | |
| UniquePtr<SSLSession> session = ssl_session_new(ssl->ctx->x509_method); | |
| if (session == nullptr) { | |
| return false; | |
| } | |
| session->is_server = ssl->server; | |
| session->ssl_version = ssl->s3->version; | |
| session->is_quic = SSL_is_quic(ssl); | |
| // Fill in the time from the `SSL_CTX`'s clock. | |
| OPENSSL_timeval now = ssl_ctx_get_current_time(ssl->ctx.get()); | |
| session->time = now.tv_sec; | |
| uint16_t version = ssl_protocol_version(ssl); | |
| if (version >= TLS1_3_VERSION) { | |
| // TLS 1.3 uses tickets as authenticators, so we are willing to use them for | |
| // longer. | |
| session->timeout = ssl->session_ctx->session_psk_dhe_timeout; | |
| session->auth_timeout = SSL_DEFAULT_SESSION_AUTH_TIMEOUT; | |
| } else { | |
| // TLS 1.2 resumption does not incorporate new key material, so we use a | |
| // much shorter timeout. | |
| session->timeout = ssl->session_ctx->session_timeout; | |
| session->auth_timeout = ssl->session_ctx->session_timeout; | |
| } | |
| if (!session->sid_ctx.TryCopyFrom(ssl_handshake_session_id_context(hs))) { | |
| return false; | |
| } | |
| // The session is marked not resumable until it is completely filled in. | |
| session->not_resumable = true; | |
| session->verify_result = X509_V_ERR_INVALID_CALL; | |
| hs->new_session = std::move(session); | |
| ssl_set_session(ssl, nullptr); | |
| return true; | |
| } | |
| bool ssl_ctx_rotate_ticket_encryption_key(SSLContext *ctx) { | |
| OPENSSL_timeval now = ssl_ctx_get_current_time(ctx); | |
| { | |
| // Avoid acquiring a write lock in the common case (i.e. a non-default key | |
| // is used or the default keys have not expired yet). | |
| MutexReadLock lock(&ctx->lock); | |
| if (ctx->ticket_key_current && | |
| (ctx->ticket_key_current->next_rotation_tv_sec == 0 || | |
| ctx->ticket_key_current->next_rotation_tv_sec > now.tv_sec) && | |
| (!ctx->ticket_key_prev || | |
| ctx->ticket_key_prev->next_rotation_tv_sec > now.tv_sec)) { | |
| return true; | |
| } | |
| } | |
| MutexWriteLock lock(&ctx->lock); | |
| if (!ctx->ticket_key_current || | |
| (ctx->ticket_key_current->next_rotation_tv_sec != 0 && | |
| ctx->ticket_key_current->next_rotation_tv_sec <= now.tv_sec)) { | |
| // The current key has not been initialized or it is expired. | |
| auto new_key = bssl::MakeUnique<TicketKey>(); | |
| if (!new_key) { | |
| return false; | |
| } | |
| RAND_bytes(new_key->name, 16); | |
| RAND_bytes(new_key->hmac_key, 16); | |
| RAND_bytes(new_key->aes_key, 16); | |
| new_key->next_rotation_tv_sec = | |
| now.tv_sec + SSL_DEFAULT_TICKET_KEY_ROTATION_INTERVAL; | |
| if (ctx->ticket_key_current) { | |
| // The current key expired. Rotate it to prev and bump up its rotation | |
| // timestamp. Note that even with the new rotation time it may still be | |
| // expired and get dropped below. | |
| ctx->ticket_key_current->next_rotation_tv_sec += | |
| SSL_DEFAULT_TICKET_KEY_ROTATION_INTERVAL; | |
| ctx->ticket_key_prev = std::move(ctx->ticket_key_current); | |
| } | |
| ctx->ticket_key_current = std::move(new_key); | |
| } | |
| // Drop an expired prev key. | |
| if (ctx->ticket_key_prev && | |
| ctx->ticket_key_prev->next_rotation_tv_sec <= now.tv_sec) { | |
| ctx->ticket_key_prev.reset(); | |
| } | |
| return true; | |
| } | |
| static int ssl_encrypt_ticket_with_cipher_ctx(SSL_HANDSHAKE *hs, CBB *out, | |
| const uint8_t *session_buf, | |
| size_t session_len) { | |
| ScopedEVP_CIPHER_CTX ctx; | |
| ScopedHMAC_CTX hctx; | |
| // If the session is too long, decline to send a ticket. | |
| static const size_t kMaxTicketOverhead = | |
| 16 + EVP_MAX_IV_LENGTH + EVP_MAX_BLOCK_LENGTH + EVP_MAX_MD_SIZE; | |
| if (session_len > 0xffff - kMaxTicketOverhead) { | |
| return 1; | |
| } | |
| // Initialize HMAC and cipher contexts. If callback present it does all the | |
| // work otherwise use generated values from parent ctx. | |
| SSLContext *tctx = hs->ssl->session_ctx.get(); | |
| uint8_t iv[EVP_MAX_IV_LENGTH]; | |
| uint8_t key_name[16]; | |
| if (tctx->ticket_key_cb != nullptr) { | |
| int ret = tctx->ticket_key_cb(hs->ssl, key_name, iv, ctx.get(), hctx.get(), | |
| 1 /* encrypt */); | |
| if (ret < 0) { | |
| return 0; | |
| } | |
| if (ret == 0) { | |
| // The caller requested to send no ticket, so write nothing to `out`. | |
| return 1; | |
| } | |
| } else { | |
| // Rotate ticket key if necessary. | |
| if (!ssl_ctx_rotate_ticket_encryption_key(tctx)) { | |
| return 0; | |
| } | |
| MutexReadLock lock(&tctx->lock); | |
| if (!RAND_bytes(iv, 16) || | |
| !EVP_EncryptInit_ex(ctx.get(), EVP_aes_128_cbc(), nullptr, | |
| tctx->ticket_key_current->aes_key, iv) || | |
| !HMAC_Init_ex(hctx.get(), tctx->ticket_key_current->hmac_key, 16, | |
| tlsext_tick_md(), nullptr)) { | |
| return 0; | |
| } | |
| OPENSSL_memcpy(key_name, tctx->ticket_key_current->name, 16); | |
| } | |
| uint8_t *ptr; | |
| if (!CBB_add_bytes(out, key_name, 16) || | |
| !CBB_add_bytes(out, iv, EVP_CIPHER_CTX_iv_length(ctx.get())) || | |
| !CBB_reserve(out, &ptr, session_len + EVP_MAX_BLOCK_LENGTH)) { | |
| return 0; | |
| } | |
| size_t total = 0; | |
| if (CRYPTO_fuzzer_mode_enabled()) { | |
| OPENSSL_memcpy(ptr, session_buf, session_len); | |
| total = session_len; | |
| } else { | |
| size_t len; | |
| if (!EVP_EncryptUpdate_ex(ctx.get(), ptr + total, &len, | |
| session_len + EVP_MAX_BLOCK_LENGTH - total, | |
| session_buf, session_len)) { | |
| return 0; | |
| } | |
| total += len; | |
| if (!EVP_EncryptFinal_ex2(ctx.get(), ptr + total, &len, | |
| session_len + EVP_MAX_BLOCK_LENGTH - total)) { | |
| return 0; | |
| } | |
| total += len; | |
| } | |
| if (!CBB_did_write(out, total)) { | |
| return 0; | |
| } | |
| unsigned hlen; | |
| if (!HMAC_Update(hctx.get(), CBB_data(out), CBB_len(out)) || // | |
| !CBB_reserve(out, &ptr, EVP_MAX_MD_SIZE) || // | |
| !HMAC_Final(hctx.get(), ptr, &hlen) || // | |
| !CBB_did_write(out, hlen)) { | |
| return 0; | |
| } | |
| return 1; | |
| } | |
| static int ssl_encrypt_ticket_with_method(SSL_HANDSHAKE *hs, CBB *out, | |
| const uint8_t *session_buf, | |
| size_t session_len) { | |
| SSLImpl *const ssl = hs->ssl; | |
| const SSL_TICKET_AEAD_METHOD *method = ssl->session_ctx->ticket_aead_method; | |
| const size_t max_overhead = method->max_overhead(ssl); | |
| const size_t max_out = session_len + max_overhead; | |
| if (max_out < max_overhead) { | |
| OPENSSL_PUT_ERROR(SSL, ERR_R_OVERFLOW); | |
| return 0; | |
| } | |
| uint8_t *ptr; | |
| if (!CBB_reserve(out, &ptr, max_out)) { | |
| return 0; | |
| } | |
| size_t out_len; | |
| if (!method->seal(ssl, ptr, &out_len, max_out, session_buf, session_len)) { | |
| OPENSSL_PUT_ERROR(SSL, SSL_R_TICKET_ENCRYPTION_FAILED); | |
| return 0; | |
| } | |
| if (!CBB_did_write(out, out_len)) { | |
| return 0; | |
| } | |
| return 1; | |
| } | |
| bool ssl_encrypt_ticket(SSL_HANDSHAKE *hs, CBB *out, | |
| const SSLSession *session) { | |
| // Serialize the SSL_SESSION to be encoded into the ticket. | |
| uint8_t *session_buf = nullptr; | |
| size_t session_len; | |
| if (!SSL_SESSION_to_bytes_for_ticket(session, &session_buf, &session_len)) { | |
| return false; | |
| } | |
| bssl::UniquePtr<uint8_t> free_session_buf(session_buf); | |
| if (hs->ssl->session_ctx->ticket_aead_method) { | |
| return ssl_encrypt_ticket_with_method(hs, out, session_buf, session_len); | |
| } else { | |
| return ssl_encrypt_ticket_with_cipher_ctx(hs, out, session_buf, | |
| session_len); | |
| } | |
| } | |
| SSLSessionType ssl_session_get_type(const SSLSession *session) { | |
| if (session->not_resumable) { | |
| return SSLSessionType::kNotResumable; | |
| } | |
| if (ssl_session_protocol_version(session) >= TLS1_3_VERSION) { | |
| return session->ticket.empty() ? SSLSessionType::kNotResumable | |
| : SSLSessionType::kPreSharedKey; | |
| } | |
| if (!session->ticket.empty()) { | |
| return SSLSessionType::kTicket; | |
| } | |
| if (!session->session_id.empty()) { | |
| return SSLSessionType::kID; | |
| } | |
| return SSLSessionType::kNotResumable; | |
| } | |
| bool ssl_session_is_context_valid(const SSL_HANDSHAKE *hs, | |
| const SSLSession *session) { | |
| return session != nullptr && | |
| Span(session->sid_ctx) == ssl_handshake_session_id_context(hs); | |
| } | |
| bool ssl_session_is_time_valid(const SSLImpl *ssl, const SSLSession *session) { | |
| if (session == nullptr) { | |
| return false; | |
| } | |
| OPENSSL_timeval now = ssl_ctx_get_current_time(ssl->ctx.get()); | |
| // Reject tickets from the future to avoid underflow. | |
| if (now.tv_sec < session->time) { | |
| return false; | |
| } | |
| return session->timeout > now.tv_sec - session->time; | |
| } | |
| bool ssl_session_is_resumable(const SSL_HANDSHAKE *hs, | |
| const SSLSession *session) { | |
| const SSLImpl *const ssl = hs->ssl; | |
| return ssl_session_is_context_valid(hs, session) && | |
| // The session must have been created by the same type of end point as | |
| // we're now using it with. | |
| ssl->server == session->is_server && | |
| // The session must not be expired. | |
| ssl_session_is_time_valid(ssl, session) && | |
| // Only resume if the session's version matches the negotiated | |
| // version. | |
| ssl->s3->version == session->ssl_version && | |
| // Only resume if the session's cipher matches the negotiated one. This | |
| // is stricter than necessary for TLS 1.3, which allows cross-cipher | |
| // resumption if the PRF hashes match. We require an exact match for | |
| // simplicity. If loosening this, the 0-RTT accept logic must be | |
| // updated to check the cipher. | |
| hs->new_cipher == session->cipher && | |
| // If the session contains a client certificate/RPK (either the full | |
| // certificate/RPK or just the hash) then require that the form of the | |
| // certificate/RPK matches the current configuration. | |
| (!ssl_session_has_peer_cred(session) || | |
| session->peer_sha256_valid == | |
| hs->config->retain_only_sha256_of_client_certs) && | |
| // Only resume if the underlying transport protocol hasn't changed. | |
| // This is to prevent cross-protocol resumption between QUIC and TCP. | |
| SSL_is_quic(ssl) == int{session->is_quic}; | |
| } | |
| // ssl_lookup_session looks up `session_id` in the session cache and sets | |
| // `*out_session` to an `SSL_SESSION` object if found. | |
| static enum ssl_hs_wait_t ssl_lookup_session(SSL_HANDSHAKE *hs, | |
| UniquePtr<SSLSession> *out_session, | |
| Span<const uint8_t> session_id) { | |
| SSLImpl *const ssl = hs->ssl; | |
| out_session->reset(); | |
| if (session_id.empty() || session_id.size() > SSL_MAX_SSL_SESSION_ID_LENGTH) { | |
| return ssl_hs_ok; | |
| } | |
| UniquePtr<SSLSession> session; | |
| // Try the internal cache, if it exists. | |
| if (!(ssl->session_ctx->session_cache_mode & | |
| SSL_SESS_CACHE_NO_INTERNAL_LOOKUP)) { | |
| uint32_t hash = ssl_hash_session_id(session_id); | |
| // TODO(crbug.com/565766495): Use `SSLSession` when the lhash does. | |
| auto cmp = [](const void *key, const SSL_SESSION *sess) -> int { | |
| Span<const uint8_t> key_id = | |
| *reinterpret_cast<const Span<const uint8_t> *>(key); | |
| return key_id == FromOpaque(sess)->session_id ? 0 : 1; | |
| }; | |
| MutexReadLock lock(&ssl->session_ctx->lock); | |
| // `lh_SSL_SESSION_retrieve_key` returns a non-owning pointer. | |
| session = UpRef(FromOpaque(lh_SSL_SESSION_retrieve_key( | |
| ssl->session_ctx->sessions, &session_id, hash, cmp))); | |
| // TODO(davidben): This should probably move it to the front of the list. | |
| } | |
| // Fall back to the external cache, if it exists. | |
| if (!session && ssl->session_ctx->get_session_cb != nullptr) { | |
| int copy = 1; | |
| SSL_SESSION *cb_session = ssl->session_ctx->get_session_cb( | |
| ssl, session_id.data(), session_id.size(), ©); | |
| if (cb_session == nullptr) { | |
| return ssl_hs_ok; | |
| } | |
| if (cb_session == SSL_magic_pending_session_ptr()) { | |
| // This pointer is not actually a session. | |
| return ssl_hs_pending_session; | |
| } | |
| session.reset(FromOpaque(cb_session)); | |
| // Increment reference count now if the session callback asks us to do so | |
| // (note that if the session structures returned by the callback are shared | |
| // between threads, it must handle the reference count itself [i.e. copy == | |
| // 0], or things won't be thread-safe). | |
| if (copy) { | |
| SSL_SESSION_up_ref(session.get()); | |
| } | |
| // Add the externally cached session to the internal cache if necessary. | |
| if (!(ssl->session_ctx->session_cache_mode & | |
| SSL_SESS_CACHE_NO_INTERNAL_STORE)) { | |
| SSL_CTX_add_session(ssl->session_ctx.get(), session.get()); | |
| } | |
| } | |
| if (session && !ssl_session_is_time_valid(ssl, session.get())) { | |
| // The session was from the cache, so remove it. | |
| SSL_CTX_remove_session(ssl->session_ctx.get(), session.get()); | |
| session.reset(); | |
| } | |
| *out_session = std::move(session); | |
| return ssl_hs_ok; | |
| } | |
| enum ssl_hs_wait_t ssl_get_prev_session(SSL_HANDSHAKE *hs, | |
| UniquePtr<SSLSession> *out_session, | |
| bool *out_tickets_supported, | |
| bool *out_renew_ticket, | |
| const SSL_CLIENT_HELLO *client_hello) { | |
| // This is used only by servers. | |
| assert(hs->ssl->server); | |
| UniquePtr<SSLSession> session; | |
| bool renew_ticket = false; | |
| // If tickets are disabled, always behave as if no tickets are present. | |
| CBS ticket; | |
| const bool tickets_supported = | |
| !(SSL_get_options(hs->ssl) & SSL_OP_NO_TICKET) && | |
| ssl_client_hello_get_extension(client_hello, &ticket, | |
| TLSEXT_TYPE_session_ticket); | |
| if (tickets_supported && CBS_len(&ticket) != 0) { | |
| switch (ssl_process_ticket( | |
| hs, &session, &renew_ticket, ticket, | |
| Span(client_hello->session_id, client_hello->session_id_len), | |
| /*save_ticket=*/false)) { | |
| case ssl_ticket_aead_success: | |
| break; | |
| case ssl_ticket_aead_ignore_ticket: | |
| assert(!session); | |
| break; | |
| case ssl_ticket_aead_error: | |
| return ssl_hs_error; | |
| case ssl_ticket_aead_retry: | |
| return ssl_hs_pending_ticket; | |
| } | |
| } else { | |
| // The client didn't send a ticket, so the session ID is a real ID. | |
| enum ssl_hs_wait_t lookup_ret = ssl_lookup_session( | |
| hs, &session, | |
| Span(client_hello->session_id, client_hello->session_id_len)); | |
| if (lookup_ret != ssl_hs_ok) { | |
| return lookup_ret; | |
| } | |
| } | |
| *out_session = std::move(session); | |
| *out_tickets_supported = tickets_supported; | |
| *out_renew_ticket = renew_ticket; | |
| return ssl_hs_ok; | |
| } | |
| static bool remove_session(SSLContext *ctx, SSLSession *session, bool lock) { | |
| if (session == nullptr || session->session_id.empty()) { | |
| return false; | |
| } | |
| if (lock) { | |
| ctx->lock.LockWrite(); | |
| } | |
| // TODO(crbug.com/565766495): Use `SSLSession` when the lhash does. | |
| SSL_SESSION *found_session = lh_SSL_SESSION_retrieve(ctx->sessions, session); | |
| bool found = found_session == session; | |
| if (found) { | |
| found_session = lh_SSL_SESSION_delete(ctx->sessions, session); | |
| SSL_SESSION_list_remove(ctx, session); | |
| } | |
| if (lock) { | |
| ctx->lock.UnlockWrite(); | |
| } | |
| if (found) { | |
| // TODO(https://crbug.com/boringssl/251): Callbacks should not be called | |
| // under a lock. | |
| if (ctx->remove_session_cb != nullptr) { | |
| ctx->remove_session_cb(ctx, found_session); | |
| } | |
| SSL_SESSION_free(found_session); | |
| } | |
| return found; | |
| } | |
| void ssl_set_session(SSLImpl *ssl, SSLSession *session) { | |
| if (ssl->session.get() == session) { | |
| return; | |
| } | |
| ssl->session = UpRef(session); | |
| } | |
| // locked by SSL_CTX in the calling function | |
| static void SSL_SESSION_list_remove(SSLContext *ctx, SSLSession *session) { | |
| if (session->next == nullptr || session->prev == nullptr) { | |
| return; | |
| } | |
| if (session->next == (SSLSession *)&ctx->session_cache_tail) { | |
| // last element in list | |
| if (session->prev == (SSLSession *)&ctx->session_cache_head) { | |
| // only one element in list | |
| ctx->session_cache_head = nullptr; | |
| ctx->session_cache_tail = nullptr; | |
| } else { | |
| ctx->session_cache_tail = session->prev; | |
| session->prev->next = (SSLSession *)&(ctx->session_cache_tail); | |
| } | |
| } else { | |
| if (session->prev == (SSLSession *)&ctx->session_cache_head) { | |
| // first element in list | |
| ctx->session_cache_head = session->next; | |
| session->next->prev = (SSLSession *)&(ctx->session_cache_head); | |
| } else { // middle of list | |
| session->next->prev = session->prev; | |
| session->prev->next = session->next; | |
| } | |
| } | |
| session->prev = session->next = nullptr; | |
| } | |
| static void SSL_SESSION_list_add(SSLContext *ctx, SSLSession *session) { | |
| if (session->next != nullptr && session->prev != nullptr) { | |
| SSL_SESSION_list_remove(ctx, session); | |
| } | |
| if (ctx->session_cache_head == nullptr) { | |
| ctx->session_cache_head = session; | |
| ctx->session_cache_tail = session; | |
| session->prev = (SSLSession *)&(ctx->session_cache_head); | |
| session->next = (SSLSession *)&(ctx->session_cache_tail); | |
| } else { | |
| session->next = ctx->session_cache_head; | |
| session->next->prev = session; | |
| session->prev = (SSLSession *)&(ctx->session_cache_head); | |
| ctx->session_cache_head = session; | |
| } | |
| } | |
| static bool add_session_locked(SSLContext *ctx, UniquePtr<SSLSession> session) { | |
| SSLSession *new_session = session.get(); | |
| // Sessions have intrusive linked lists, so they cannot be stored in two | |
| // SSLContexts at once. | |
| // TODO(crbug.com/527997772): Remove this restriction. | |
| if ((new_session->prev != nullptr || new_session->next != nullptr)) { | |
| return false; | |
| } | |
| // TODO(crbug.com/565766495): Use `SSLSession` when the lhash does. | |
| SSL_SESSION *old_session_opaque; | |
| if (!lh_SSL_SESSION_insert(ctx->sessions, &old_session_opaque, new_session)) { | |
| return false; | |
| } | |
| SSLSession *old_session = FromOpaque(old_session_opaque); | |
| // `ctx->sessions` took ownership of `new_session` and gave us back a | |
| // reference to `old_session`. (`old_session` may be the same as | |
| // `new_session`, in which case we traded identical references with | |
| // `ctx->sessions`.) | |
| session.release(); | |
| session.reset(old_session); | |
| if (old_session != nullptr) { | |
| // The session cannot have been in the session, due to the check above. | |
| // TODO(crbug.com/527997772): Revisit this when we remove the check. | |
| BSSL_CHECK(old_session != new_session); | |
| // There was a session ID collision. `old_session` was replaced with | |
| // `session` in the hash table, so `old_session` must be removed from the | |
| // linked list to match. | |
| SSL_SESSION_list_remove(ctx, old_session); | |
| } | |
| // This does not increment the reference count. Although `session` is inserted | |
| // into two structures (a doubly-linked list and the hash table), `ctx` only | |
| // takes one reference. | |
| SSL_SESSION_list_add(ctx, new_session); | |
| // Enforce any cache size limits. | |
| if (SSL_CTX_sess_get_cache_size(ctx) > 0) { | |
| while (lh_SSL_SESSION_num_items(ctx->sessions) > | |
| SSL_CTX_sess_get_cache_size(ctx)) { | |
| if (!remove_session(ctx, ctx->session_cache_tail, | |
| /*lock=*/false)) { | |
| break; | |
| } | |
| } | |
| } | |
| return true; | |
| } | |
| void ssl_update_cache(SSLImpl *ssl) { | |
| SSLContext *ctx = ssl->session_ctx.get(); | |
| SSLSession *session = ssl->s3->established_session.get(); | |
| int mode = SSL_is_server(ssl) ? SSL_SESS_CACHE_SERVER : SSL_SESS_CACHE_CLIENT; | |
| if (!SSL_SESSION_is_resumable(session) || | |
| (ctx->session_cache_mode & mode) != mode) { | |
| return; | |
| } | |
| // Clients never use the internal session cache. | |
| if (ssl->server && | |
| !(ctx->session_cache_mode & SSL_SESS_CACHE_NO_INTERNAL_STORE)) { | |
| UniquePtr<SSLSession> ref = UpRef(session); | |
| bool remove_expired_sessions = false; | |
| { | |
| MutexWriteLock lock(&ctx->lock); | |
| add_session_locked(ctx, std::move(ref)); | |
| if (!(ctx->session_cache_mode & SSL_SESS_CACHE_NO_AUTO_CLEAR)) { | |
| // Automatically flush the internal session cache every 255 connections. | |
| ctx->handshakes_since_cache_flush++; | |
| if (ctx->handshakes_since_cache_flush >= 255) { | |
| remove_expired_sessions = true; | |
| ctx->handshakes_since_cache_flush = 0; | |
| } | |
| } | |
| } | |
| if (remove_expired_sessions) { | |
| // `SSL_CTX_flush_sessions` takes the lock we just released. We could | |
| // merge the critical sections, but we'd then call user code under a | |
| // lock, or compute `now` earlier, even when not flushing. | |
| OPENSSL_timeval now = ssl_ctx_get_current_time(ssl->ctx.get()); | |
| SSL_CTX_flush_sessions(ctx, now.tv_sec); | |
| } | |
| } | |
| if (ctx->new_session_cb != nullptr) { | |
| UniquePtr<SSLSession> ref = UpRef(session); | |
| if (ctx->new_session_cb(ssl, ref.get())) { | |
| // `new_session_cb`'s return value signals whether it took ownership. | |
| ref.release(); | |
| } | |
| } | |
| } | |
| bool ssl_session_has_peer_cred(const SSLSession *session) { | |
| return sk_CRYPTO_BUFFER_num(SSL_SESSION_get0_peer_certificates(session)) > | |
| 0 || | |
| SSL_SESSION_get0_peer_rpk(session) != nullptr || | |
| session->peer_sha256_valid; | |
| } | |
| SSLSession *ssl_get_session(const SSLImpl *ssl) { | |
| // Once the initial handshake completes, we return the most recently | |
| // established session. In particular, if there is a pending renegotiation, we | |
| // do not return information about it until it completes. | |
| // | |
| // Code in the handshake must either use `hs->new_session` (if updating a | |
| // partial session) or `ssl_handshake_session` (if trying to query properties | |
| // consistently across TLS 1.2 resumption and other handshakes). | |
| if (ssl->s3->established_session != nullptr) { | |
| return ssl->s3->established_session.get(); | |
| } | |
| // Otherwise, we must be in the initial handshake. | |
| SSL_HANDSHAKE *hs = ssl->s3->hs.get(); | |
| assert(hs != nullptr); | |
| assert(!ssl->s3->initial_handshake_complete); | |
| // Return the 0-RTT session, if in the 0-RTT state. While the handshake has | |
| // not actually completed, the public accessors all report properties as if | |
| // it has. | |
| if (hs->early_session) { | |
| return hs->early_session.get(); | |
| } | |
| // Otherwise, return the partial session. | |
| return const_cast<SSLSession *>(ssl_handshake_session(hs)); | |
| } | |
| BSSL_NAMESPACE_END | |
| using namespace bssl; | |
| SSLSession::SSLSession(const SSL_X509_METHOD *method) | |
| : RefCounted(CheckSubClass()), | |
| x509_method(method), | |
| extended_master_secret(false), | |
| peer_sha256_valid(false), | |
| not_resumable(false), | |
| ticket_age_add_valid(false), | |
| is_server(false), | |
| is_quic(false), | |
| has_application_settings(false), | |
| is_resumable_across_names(false) { | |
| CRYPTO_new_ex_data(&ex_data); | |
| time = ::time(nullptr); | |
| } | |
| SSLSession::~SSLSession() { | |
| CRYPTO_free_ex_data(&g_ex_data_class, &ex_data); | |
| x509_method->session_clear(this); | |
| } | |
| SSL_SESSION *SSL_SESSION_new(const SSL_CTX *ctx) { | |
| return ssl_session_new(FromOpaque(ctx)->x509_method).release(); | |
| } | |
| int SSL_SESSION_up_ref(SSL_SESSION *session) { | |
| FromOpaque(session)->UpRefInternal(); | |
| return 1; | |
| } | |
| void SSL_SESSION_free(SSL_SESSION *session) { | |
| if (session == nullptr) { | |
| return; | |
| } | |
| FromOpaque(session)->DecRefInternal(); | |
| } | |
| const uint8_t *SSL_SESSION_get_id(const SSL_SESSION *session, | |
| unsigned *out_len) { | |
| const auto *session_impl = FromOpaque(session); | |
| if (out_len != nullptr) { | |
| *out_len = session_impl->session_id.size(); | |
| } | |
| return session_impl->session_id.data(); | |
| } | |
| int SSL_SESSION_set1_id(SSL_SESSION *session, const uint8_t *sid, | |
| size_t sid_len) { | |
| if (!FromOpaque(session)->session_id.TryCopyFrom(Span(sid, sid_len))) { | |
| OPENSSL_PUT_ERROR(SSL, SSL_R_SSL_SESSION_ID_TOO_LONG); | |
| return 0; | |
| } | |
| return 1; | |
| } | |
| uint32_t SSL_SESSION_get_timeout(const SSL_SESSION *session) { | |
| return FromOpaque(session)->timeout; | |
| } | |
| uint64_t SSL_SESSION_get_time(const SSL_SESSION *session) { | |
| if (session == nullptr) { | |
| // NULL should crash, but silently accept it here for compatibility. | |
| return 0; | |
| } | |
| return FromOpaque(session)->time; | |
| } | |
| X509 *SSL_SESSION_get0_peer(const SSL_SESSION *session) { | |
| return FromOpaque(session)->x509_peer; | |
| } | |
| const STACK_OF(CRYPTO_BUFFER) *SSL_SESSION_get0_peer_certificates( | |
| const SSL_SESSION *session) { | |
| return FromOpaque(session)->certs.get(); | |
| } | |
| EVP_PKEY *SSL_SESSION_get0_peer_rpk(const SSL_SESSION *session) { | |
| return FromOpaque(session)->peer_raw_public_key.get(); | |
| } | |
| void SSL_SESSION_get0_signed_cert_timestamp_list(const SSL_SESSION *session, | |
| const uint8_t **out, | |
| size_t *out_len) { | |
| const auto *session_impl = FromOpaque(session); | |
| if (session_impl->signed_cert_timestamp_list) { | |
| *out = CRYPTO_BUFFER_data(session_impl->signed_cert_timestamp_list.get()); | |
| *out_len = | |
| CRYPTO_BUFFER_len(session_impl->signed_cert_timestamp_list.get()); | |
| } else { | |
| *out = nullptr; | |
| *out_len = 0; | |
| } | |
| } | |
| void SSL_SESSION_get0_ocsp_response(const SSL_SESSION *session, | |
| const uint8_t **out, size_t *out_len) { | |
| const auto *session_impl = FromOpaque(session); | |
| if (session_impl->ocsp_response) { | |
| *out = CRYPTO_BUFFER_data(session_impl->ocsp_response.get()); | |
| *out_len = CRYPTO_BUFFER_len(session_impl->ocsp_response.get()); | |
| } else { | |
| *out = nullptr; | |
| *out_len = 0; | |
| } | |
| } | |
| size_t SSL_SESSION_get_master_key(const SSL_SESSION *session, uint8_t *out, | |
| size_t max_out) { | |
| const auto *session_impl = FromOpaque(session); | |
| if (max_out == 0) { | |
| return session_impl->secret.size(); | |
| } | |
| if (max_out > session_impl->secret.size()) { | |
| max_out = session_impl->secret.size(); | |
| } | |
| OPENSSL_memcpy(out, session_impl->secret.data(), max_out); | |
| return max_out; | |
| } | |
| uint64_t SSL_SESSION_set_time(SSL_SESSION *session, uint64_t time) { | |
| if (session == nullptr) { | |
| return 0; | |
| } | |
| FromOpaque(session)->time = time; | |
| return time; | |
| } | |
| uint32_t SSL_SESSION_set_timeout(SSL_SESSION *session, uint32_t timeout) { | |
| auto *session_impl = FromOpaque(session); | |
| if (session_impl == nullptr) { | |
| return 0; | |
| } | |
| session_impl->timeout = timeout; | |
| session_impl->auth_timeout = timeout; | |
| return 1; | |
| } | |
| const uint8_t *SSL_SESSION_get0_id_context(const SSL_SESSION *session, | |
| unsigned *out_len) { | |
| const auto *session_impl = FromOpaque(session); | |
| if (out_len != nullptr) { | |
| *out_len = session_impl->sid_ctx.size(); | |
| } | |
| return session_impl->sid_ctx.data(); | |
| } | |
| int SSL_SESSION_set1_id_context(SSL_SESSION *session, const uint8_t *sid_ctx, | |
| size_t sid_ctx_len) { | |
| if (!FromOpaque(session)->sid_ctx.TryCopyFrom(Span(sid_ctx, sid_ctx_len))) { | |
| OPENSSL_PUT_ERROR(SSL, SSL_R_SSL_SESSION_ID_CONTEXT_TOO_LONG); | |
| return 0; | |
| } | |
| return 1; | |
| } | |
| int SSL_SESSION_should_be_single_use(const SSL_SESSION *session) { | |
| return ssl_session_protocol_version(FromOpaque(session)) >= TLS1_3_VERSION; | |
| } | |
| int SSL_SESSION_is_resumable(const SSL_SESSION *session) { | |
| return ssl_session_get_type(FromOpaque(session)) != | |
| SSLSessionType::kNotResumable; | |
| } | |
| int SSL_SESSION_has_ticket(const SSL_SESSION *session) { | |
| return !FromOpaque(session)->ticket.empty(); | |
| } | |
| void SSL_SESSION_get0_ticket(const SSL_SESSION *session, | |
| const uint8_t **out_ticket, size_t *out_len) { | |
| const auto *session_impl = FromOpaque(session); | |
| if (out_ticket != nullptr) { | |
| *out_ticket = session_impl->ticket.data(); | |
| } | |
| *out_len = session_impl->ticket.size(); | |
| } | |
| int SSL_SESSION_set_ticket(SSL_SESSION *session, const uint8_t *ticket, | |
| size_t ticket_len) { | |
| return FromOpaque(session)->ticket.CopyFrom(Span(ticket, ticket_len)); | |
| } | |
| uint32_t SSL_SESSION_get_ticket_lifetime_hint(const SSL_SESSION *session) { | |
| return FromOpaque(session)->ticket_lifetime_hint; | |
| } | |
| const SSL_CIPHER *SSL_SESSION_get0_cipher(const SSL_SESSION *session) { | |
| return FromOpaque(session)->cipher; | |
| } | |
| int SSL_SESSION_has_peer_sha256(const SSL_SESSION *session) { | |
| return FromOpaque(session)->peer_sha256_valid; | |
| } | |
| void SSL_SESSION_get0_peer_sha256(const SSL_SESSION *session, | |
| const uint8_t **out_ptr, size_t *out_len) { | |
| const auto *session_impl = FromOpaque(session); | |
| if (session_impl->peer_sha256_valid) { | |
| *out_ptr = session_impl->peer_sha256; | |
| *out_len = sizeof(session_impl->peer_sha256); | |
| } else { | |
| *out_ptr = nullptr; | |
| *out_len = 0; | |
| } | |
| } | |
| int SSL_SESSION_is_resumable_across_names(const SSL_SESSION *session) { | |
| return FromOpaque(session)->is_resumable_across_names; | |
| } | |
| int SSL_SESSION_early_data_capable(const SSL_SESSION *session) { | |
| const auto *session_impl = FromOpaque(session); | |
| return ssl_session_protocol_version(session_impl) >= TLS1_3_VERSION && | |
| session_impl->ticket_max_early_data != 0; | |
| } | |
| SSL_SESSION *SSL_SESSION_copy_without_early_data(SSL_SESSION *session) { | |
| auto *session_impl = FromOpaque(session); | |
| if (!SSL_SESSION_early_data_capable(session)) { | |
| return UpRef(session).release(); | |
| } | |
| UniquePtr<SSLSession> copy = | |
| SSL_SESSION_dup(session_impl, SSL_SESSION_DUP_ALL); | |
| if (!copy) { | |
| return nullptr; | |
| } | |
| copy->ticket_max_early_data = 0; | |
| // Copied sessions are non-resumable until they're completely filled in. | |
| copy->not_resumable = session_impl->not_resumable; | |
| assert(!SSL_SESSION_early_data_capable(copy.get())); | |
| return copy.release(); | |
| } | |
| SSL_SESSION *SSL_magic_pending_session_ptr() { | |
| return (SSL_SESSION *)&g_pending_session_magic; | |
| } | |
| SSL_SESSION *SSL_get_session(const SSL *ssl) { | |
| return ssl_get_session(FromOpaque(ssl)); | |
| } | |
| SSL_SESSION *SSL_get1_session(SSL *ssl) { | |
| SSL_SESSION *ret = SSL_get_session(ssl); | |
| if (ret != nullptr) { | |
| SSL_SESSION_up_ref(ret); | |
| } | |
| return ret; | |
| } | |
| int SSL_SESSION_get_ex_new_index(long argl, void *argp, | |
| CRYPTO_EX_unused *unused, | |
| CRYPTO_EX_dup *dup_unused, | |
| CRYPTO_EX_free *free_func) { | |
| return CRYPTO_get_ex_new_index_ex(&g_ex_data_class, argl, argp, free_func); | |
| } | |
| int SSL_SESSION_set_ex_data(SSL_SESSION *session, int idx, void *arg) { | |
| return CRYPTO_set_ex_data(&FromOpaque(session)->ex_data, idx, arg); | |
| } | |
| void *SSL_SESSION_get_ex_data(const SSL_SESSION *session, int idx) { | |
| return CRYPTO_get_ex_data(&FromOpaque(session)->ex_data, idx); | |
| } | |
| int SSL_CTX_add_session(SSL_CTX *ctx, SSL_SESSION *session) { | |
| auto *ctx_impl = FromOpaque(ctx); | |
| UniquePtr<SSLSession> owned_session = UpRef(FromOpaque(session)); | |
| MutexWriteLock lock(&ctx_impl->lock); | |
| return add_session_locked(ctx_impl, std::move(owned_session)); | |
| } | |
| int SSL_CTX_remove_session(SSL_CTX *ctx, SSL_SESSION *session) { | |
| return remove_session(FromOpaque(ctx), FromOpaque(session), /*lock=*/true); | |
| } | |
| int SSL_set_session(SSL *ssl, SSL_SESSION *session) { | |
| auto *ssl_impl = FromOpaque(ssl); | |
| // SSL_set_session may only be called before the handshake has started. | |
| if (ssl_impl->s3->initial_handshake_complete || // | |
| ssl_impl->s3->hs == nullptr || // | |
| ssl_impl->s3->hs->state != 0) { | |
| abort(); | |
| } | |
| ssl_set_session(ssl_impl, FromOpaque(session)); | |
| return 1; | |
| } | |
| uint32_t SSL_CTX_set_timeout(SSL_CTX *ctx, uint32_t timeout) { | |
| if (ctx == nullptr) { | |
| return 0; | |
| } | |
| // Historically, zero was treated as `SSL_DEFAULT_SESSION_TIMEOUT`. | |
| if (timeout == 0) { | |
| timeout = SSL_DEFAULT_SESSION_TIMEOUT; | |
| } | |
| return std::exchange(FromOpaque(ctx)->session_timeout, timeout); | |
| } | |
| uint32_t SSL_CTX_get_timeout(const SSL_CTX *ctx) { | |
| if (ctx == nullptr) { | |
| return 0; | |
| } | |
| return FromOpaque(ctx)->session_timeout; | |
| } | |
| void SSL_CTX_set_session_psk_dhe_timeout(SSL_CTX *ctx, uint32_t timeout) { | |
| FromOpaque(ctx)->session_psk_dhe_timeout = timeout; | |
| } | |
| typedef struct timeout_param_st { | |
| SSLContext *ctx; | |
| uint64_t time; | |
| LHASH_OF(SSL_SESSION) *cache; | |
| } TIMEOUT_PARAM; | |
| // TODO(crbug.com/565766495): Take `SSLSession` when the lhash does. | |
| static void timeout_doall_arg(SSL_SESSION *session, void *void_param) { | |
| TIMEOUT_PARAM *param = reinterpret_cast<TIMEOUT_PARAM *>(void_param); | |
| auto *session_impl = FromOpaque(session); | |
| if (param->time == 0 || // | |
| session_impl->time + session_impl->timeout < session_impl->time || // | |
| param->time > (session_impl->time + session_impl->timeout)) { | |
| // TODO(davidben): This can probably just call `remove_session`. | |
| (void)lh_SSL_SESSION_delete(param->cache, session); | |
| SSL_SESSION_list_remove(param->ctx, session_impl); | |
| // TODO(https://crbug.com/boringssl/251): Callbacks should not be called | |
| // under a lock. | |
| if (param->ctx->remove_session_cb != nullptr) { | |
| param->ctx->remove_session_cb(param->ctx, session); | |
| } | |
| SSL_SESSION_free(session); | |
| } | |
| } | |
| void SSL_CTX_flush_sessions(SSL_CTX *ctx, uint64_t time) { | |
| auto *ctx_impl = FromOpaque(ctx); | |
| TIMEOUT_PARAM tp; | |
| tp.ctx = ctx_impl; | |
| tp.cache = ctx_impl->sessions; | |
| if (tp.cache == nullptr) { | |
| return; | |
| } | |
| tp.time = time; | |
| MutexWriteLock lock(&ctx_impl->lock); | |
| lh_SSL_SESSION_doall_arg(tp.cache, timeout_doall_arg, &tp); | |
| } | |
| void SSL_CTX_sess_set_new_cb(SSL_CTX *ctx, | |
| int (*cb)(SSL *ssl, SSL_SESSION *session)) { | |
| FromOpaque(ctx)->new_session_cb = cb; | |
| } | |
| int (*SSL_CTX_sess_get_new_cb(SSL_CTX *ctx))(SSL *ssl, SSL_SESSION *session) { | |
| return FromOpaque(ctx)->new_session_cb; | |
| } | |
| void SSL_CTX_sess_set_remove_cb(SSL_CTX *ctx, | |
| void (*cb)(SSL_CTX *ctx, | |
| SSL_SESSION *session)) { | |
| FromOpaque(ctx)->remove_session_cb = cb; | |
| } | |
| void (*SSL_CTX_sess_get_remove_cb(SSL_CTX *ctx))(SSL_CTX *ctx, | |
| SSL_SESSION *session) { | |
| return FromOpaque(ctx)->remove_session_cb; | |
| } | |
| void SSL_CTX_sess_set_get_cb(SSL_CTX *ctx, | |
| SSL_SESSION *(*cb)(SSL *ssl, const uint8_t *id, | |
| int id_len, int *out_copy)) { | |
| FromOpaque(ctx)->get_session_cb = cb; | |
| } | |
| SSL_SESSION *(*SSL_CTX_sess_get_get_cb(SSL_CTX *ctx))(SSL *ssl, | |
| const uint8_t *id, | |
| int id_len, | |
| int *out_copy) { | |
| return FromOpaque(ctx)->get_session_cb; | |
| } | |
| void SSL_CTX_set_resumption_across_names_enabled(SSL_CTX *ctx, int enabled) { | |
| FromOpaque(ctx)->resumption_across_names_enabled = !!enabled; | |
| } | |
| void SSL_set_resumption_across_names_enabled(SSL *ssl, int enabled) { | |
| FromOpaque(ssl)->resumption_across_names_enabled = !!enabled; | |
| } | |
| void SSL_CTX_set_info_callback(SSL_CTX *ctx, void (*cb)(const SSL *ssl, | |
| int type, int value)) { | |
| FromOpaque(ctx)->info_callback = cb; | |
| } | |
| void (*SSL_CTX_get_info_callback(SSL_CTX *ctx))(const SSL *ssl, int type, | |
| int value) { | |
| return FromOpaque(ctx)->info_callback; | |
| } |