Updated Branches: refs/heads/master 16882d822 -> 8dbf06bf3
TS-1146: RFC 5077 TLS session tickets For supporting RFC 5077 TLS Session tickets across a ATS cluster, all the machines need to have the same server ticket. Add ssl_multicert.config support for specifying a common session ticket key. Project: http://git-wip-us.apache.org/repos/asf/trafficserver/repo Commit: http://git-wip-us.apache.org/repos/asf/trafficserver/commit/8dbf06bf Tree: http://git-wip-us.apache.org/repos/asf/trafficserver/tree/8dbf06bf Diff: http://git-wip-us.apache.org/repos/asf/trafficserver/diff/8dbf06bf Branch: refs/heads/master Commit: 8dbf06bf30f618aac4c1fc5c87afe7aa38569d33 Parents: 16882d8 Author: Wei Sun <[email protected]> Authored: Fri Dec 6 10:18:39 2013 -0800 Committer: James Peach <[email protected]> Committed: Fri Dec 6 10:20:35 2013 -0800 ---------------------------------------------------------------------- CHANGES | 3 + .../configuration/ssl_multicert.config.en.rst | 57 +++++-- iocore/net/P_SSLUtils.h | 3 + iocore/net/SSLCertLookup.cc | 2 +- iocore/net/SSLUtils.cc | 164 ++++++++++++++++++- iocore/net/test_certlookup.cc | 9 + 6 files changed, 221 insertions(+), 17 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/trafficserver/blob/8dbf06bf/CHANGES ---------------------------------------------------------------------- diff --git a/CHANGES b/CHANGES index 309edcf..429ac04 100644 --- a/CHANGES +++ b/CHANGES @@ -2,6 +2,9 @@ Changes with Apache Traffic Server 4.2.0 + *) [TS-1146] Add RFC 5077 TLS session ticket support. + Author: Wei Sun <[email protected]> + *) [TS-2401] Use Layout instead of global install path directories. *) [TS-2420] Remove STAT_SYNC, CONF_SYNC, and REM_SYNC threads and schedule those http://git-wip-us.apache.org/repos/asf/trafficserver/blob/8dbf06bf/doc/reference/configuration/ssl_multicert.config.en.rst ---------------------------------------------------------------------- diff --git a/doc/reference/configuration/ssl_multicert.config.en.rst b/doc/reference/configuration/ssl_multicert.config.en.rst index 9098fc7..39fda6f 100644 --- a/doc/reference/configuration/ssl_multicert.config.en.rst +++ b/doc/reference/configuration/ssl_multicert.config.en.rst @@ -5,9 +5,9 @@ to you 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 - + http://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 @@ -46,8 +46,8 @@ Each :file:`ssl_multicert.config` line consists of a sequence of `key=value` fields that specify how Traffic Server should use a particular SSL certificate. -ssl_cert_name=PATH - The name of the file containing the TLS certificate. `PATH` is +ssl_cert_name=FILENAME + The name of the file containing the TLS certificate. `FILENAME` is located relative to the directory specified by the :ts:cv:`proxy.config.ssl.server.cert.path` configuration variable. This is the only field that is required to be present. @@ -55,26 +55,45 @@ ssl_cert_name=PATH dest_ip=ADDRESS The IP (v4 or v6) address that the certificate should be presented on. This is now only used as a fallback in the case that the TLS - SubjectNameIndication extension is not supported. If `ADDRESS` - is `*`, the corresponding certificate will be used as the global + SubjectNameIndication extension is not supported. If `ADDRESS` is + `*`, the corresponding certificate will be used as the global default fallback if no other match can be made. The address may contain a port specifier, in which case the corresponding certificate will only match for connections accepted on the specified port. IPv6 addresses must be enclosed by square brackets if they have a port, eg, [::1]:80. -ssl_key_name=PATH +ssl_key_name=FILENAME The name of the file containing the private key for this certificate. If the key is contained in the certificate file, this field can - be omitted, otherwise `PATH` is resolved relative to the + be omitted, otherwise `FILENAME` is resolved relative to the :ts:cv:`proxy.config.ssl.server.private_key.path` configuration variable. ssl_ca_name=FILENAME If the certificate is issued by an authority that is not in the system CA bundle, additional certificates may be needed to validate - the certificate chain. `PATH` is resolved relative to the + the certificate chain. `FILENAME` is resolved relative to the :ts:cv:`proxy.config.ssl.CA.cert.path` configuration variable. +ssl_ticket_enabled=1|0 + Enable :rfc:`5077` stateless TLS session tickets. To support this, + OpenSSL should be upgraded to version 0.9.8f or higher. This + option must be set to `0` to disable session ticket support. + +ticket_key_name=FILENAME + The name of session ticket key file which contains a secret for + encrypting and decrypting TLS session tickets. If `FILENAME` is + not an absolute path, it is resolved relative to the + :ts:cv:`proxy.config.ssl.server.cert.path` configuration variable. + This option has no effect if session tickets are disabled by the + ``ssl_ticket_enabled`` option. The contents of the key file should + be 48 random bytes. + + Session ticket support is enabled by default. If neither of the + ``ssl_ticket_enabled`` and ``ticket_key_name`` options are + specified, and internal session tiucket key is generated. This + key will be different each time Traffic Server is started. + Certificate Selection ===================== @@ -98,7 +117,6 @@ take precedence over a specification that does not contain a port number. A specific certificate subject will take precedence over a wildcard certificate. - Examples ======== @@ -123,6 +141,21 @@ for all requests to port 8443 on IP address 111.11.11.1. The :: - dest_ip=111.11.11.1:8443 ssl_cert_name=server.pem ssl_key_name=serverKey.pem - ssl_cert_name=general.pem + dest_ip=111.11.11.1:8443 ssl_cert_name=server.pem ssl_key_name=serverKey.pem ssl_cert_name=general.pem + +The following example configures Traffic Server to use the SSL +certificate ``server.pem`` for all requests to the IP address +111.11.11.1. Session tickets are enabled with a persistent ticket +key. + +:: + + dest_ip=111.11.11.1 ssl_cert_name=server.pem ssl_ticket_enabled=1 ticket_key_name=ticket.key + +The following example configures Traffic Server to use the SSL +certificate ``server.pem`` and disable sessiont ticket for all +requests to the IP address 111.11.11.1. + +:: + dest_ip=111.11.11.1 ssl_cert_name=server.pem ssl_ticket_enabled=0 http://git-wip-us.apache.org/repos/asf/trafficserver/blob/8dbf06bf/iocore/net/P_SSLUtils.h ---------------------------------------------------------------------- diff --git a/iocore/net/P_SSLUtils.h b/iocore/net/P_SSLUtils.h index 4f1ec96..289ff99 100644 --- a/iocore/net/P_SSLUtils.h +++ b/iocore/net/P_SSLUtils.h @@ -53,6 +53,9 @@ SSL_CTX * SSLInitClientContext(const SSLConfigParams * param); // Initialize the SSL library. void SSLInitializeLibrary(); +// Release SSL_CTX and the associated data +void SSLReleaseContext(SSL_CTX* ctx); + // Log an SSL error. #define SSLError(fmt, ...) SSLDiagnostic(DiagsMakeLocation(), false, fmt, ##__VA_ARGS__) // Log a SSL diagnostic using the "ssl" diagnostic tag. http://git-wip-us.apache.org/repos/asf/trafficserver/blob/8dbf06bf/iocore/net/SSLCertLookup.cc ---------------------------------------------------------------------- diff --git a/iocore/net/SSLCertLookup.cc b/iocore/net/SSLCertLookup.cc index 5715cdf..fe14369 100644 --- a/iocore/net/SSLCertLookup.cc +++ b/iocore/net/SSLCertLookup.cc @@ -206,7 +206,7 @@ SSLContextStorage::SSLContextStorage() SSLContextStorage::~SSLContextStorage() { for (unsigned i = 0; i < this->references.count(); ++i) { - SSL_CTX_free(this->references[i]); + SSLReleaseContext(this->references[i]); } ink_hash_table_destroy(this->hostnames); http://git-wip-us.apache.org/repos/asf/trafficserver/blob/8dbf06bf/iocore/net/SSLUtils.cc ---------------------------------------------------------------------- diff --git a/iocore/net/SSLUtils.cc b/iocore/net/SSLUtils.cc index 020c16b..a958b4a 100644 --- a/iocore/net/SSLUtils.cc +++ b/iocore/net/SSLUtils.cc @@ -29,6 +29,8 @@ #include <openssl/pem.h> #include <openssl/x509.h> #include <openssl/asn1.h> +#include <openssl/rand.h> +#include <openssl/evp.h> #if HAVE_OPENSSL_TS_H #include <openssl/ts.h> @@ -43,6 +45,16 @@ #define SSL_CERT_TAG "ssl_cert_name" #define SSL_PRIVATE_KEY_TAG "ssl_key_name" #define SSL_CA_TAG "ssl_ca_name" +#define SSL_SESSION_TICKET_ENABLED "ssl_ticket_enabled" +#define SSL_SESSION_TICKET_KEY_FILE_TAG "ticket_key_name" + +#ifndef evp_md_func +#ifdef OPENSSL_NO_SHA256 + #define evp_md_func EVP_sha1() +#else + #define evp_md_func EVP_sha256() +#endif +#endif #if (OPENSSL_VERSION_NUMBER >= 0x10000000L) // openssl returns a const SSL_METHOD typedef const SSL_METHOD * ink_ssl_method_t; @@ -50,8 +62,17 @@ typedef const SSL_METHOD * ink_ssl_method_t; typedef SSL_METHOD * ink_ssl_method_t; #endif +struct ssl_ticket_key_t +{ + unsigned char key_name[16]; + unsigned char hmac_secret[16]; + unsigned char aes_key[16]; +}; + static ProxyMutex ** sslMutexArray; static bool open_ssl_initialized = false; +static int ssl_callback_session_ticket(SSL *, unsigned char *, unsigned char *, EVP_CIPHER_CTX *, HMAC_CTX *, int); +static int ssl_session_ticket_index = 0; struct ats_file_bio { @@ -208,6 +229,50 @@ ssl_context_enable_ecdh(SSL_CTX * ctx) return ctx; } +static SSL_CTX * +ssl_context_enable_tickets(SSL_CTX * ctx, const char * ticket_key_path) +{ + xptr<char> ticket_key_data; + int ticket_key_len; + ssl_ticket_key_t * ticket_key = NULL; + + ticket_key_data = readIntoBuffer(ticket_key_path, __func__, &ticket_key_len); + if (!ticket_key_data) { + Error("failed to read SSL session ticket key from %s", (const char *)ticket_key_path); + goto fail; + } + + if (ticket_key_len < 48) { + Error("SSL session ticket key from %s is too short (48 bytes are required)", (const char *)ticket_key_path); + goto fail; + } + + ticket_key = NEW(new ssl_ticket_key_t()); + memcpy(ticket_key->key_name, (const char *)ticket_key_data, 16); + memcpy(ticket_key->hmac_secret, (const char *)ticket_key_data + 16, 16); + memcpy(ticket_key->aes_key, (const char *)ticket_key_data + 32, 16); + + // Setting the callback can only fail if OpenSSL does not recognize the + // SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB constant. we set the callback first + // so that we don't leave a ticket_key pointer attached if it fails. + if (SSL_CTX_set_tlsext_ticket_key_cb(ctx, ssl_callback_session_ticket) == 0) { + Error("failed to set session ticket callback"); + goto fail; + } + + if (SSL_CTX_set_ex_data(ctx, ssl_session_ticket_index, ticket_key) == 0) { + Error ("failed to set session ticket data to ctx"); + goto fail; + } + + SSL_CTX_clear_options(ctx, SSL_OP_NO_TICKET); + return ctx; + +fail: + delete ticket_key; + return ctx; +} + void SSLInitializeLibrary() { @@ -227,6 +292,12 @@ SSLInitializeLibrary() CRYPTO_set_id_callback(SSL_pthreads_thread_id); } + int iRet = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL); + if (iRet == -1) { + SSLError("failed to create session ticket index"); + } + ssl_session_ticket_index = (iRet == -1 ? 0 : iRet); + open_ssl_initialized = true; } @@ -577,10 +648,13 @@ ssl_store_ssl_context( xptr<char>& addr, xptr<char>& cert, xptr<char>& ca, - xptr<char>& key) + xptr<char>& key, + const int session_ticket_enabled, + xptr<char>& ticket_key_filename) { SSL_CTX * ctx; xptr<char> certpath; + xptr<char> session_key_path; ctx = ssl_context_enable_sni(SSLInitServerContext(params, cert, ca, key), lookup); if (!ctx) { @@ -610,10 +684,23 @@ ssl_store_ssl_context( } } + // Session tickets are enabled by default. Disable if explicitly requested. + if (session_ticket_enabled == 0) { + SSL_CTX_set_options(ctx, SSL_OP_NO_TICKET); + Debug("ssl", "ssl session ticket is disabled"); + } + + // Load the session ticket key if session tickets are not disabled and we have key name. + if (session_ticket_enabled != 0 && ticket_key_filename) { + xptr<char> ticket_key_path(Layout::relative_to(params->serverCertPathOnly, ticket_key_filename)); + ssl_context_enable_tickets(ctx, ticket_key_path); + } + // Insert additional mappings. Note that this maps multiple keys to the same value, so when // this code is updated to reconfigure the SSL certificates, it will need some sort of // refcounting or alternate way of avoiding double frees. ssl_index_certificate(lookup, ctx, certpath); + return true; } @@ -623,7 +710,9 @@ ssl_extract_certificate( xptr<char>& addr, // IPv[64] address to match xptr<char>& cert, // certificate xptr<char>& ca, // CA public certificate - xptr<char>& key) // Private key + xptr<char>& key, // Private key + int& session_ticket_enabled, // session ticket enabled + xptr<char>& ticket_key_filename) // session key file. [key_name (16Byte) + HMAC_secret (16Byte) + AES_key (16Byte)] { for (int i = 0; i < MATCHER_MAX_TOKENS; ++i) { const char * label; @@ -651,6 +740,14 @@ ssl_extract_certificate( if (strcasecmp(label, SSL_PRIVATE_KEY_TAG) == 0) { key = ats_strdup(value); } + + if (strcasecmp(label, SSL_SESSION_TICKET_ENABLED) == 0) { + session_ticket_enabled = atoi(value); + } + + if (strcasecmp(label, SSL_SESSION_TICKET_KEY_FILE_TAG) == 0) { + ticket_key_filename = ats_strdup(value); + } } if (!cert) { @@ -705,6 +802,8 @@ SSLParseCertificateConfiguration( xptr<char> cert; xptr<char> ca; xptr<char> key; + int session_ticket_enabled = -1; + xptr<char> ticket_key_filename; const char * errPtr; errPtr = parseConfigLine(line, &line_info, &sslCertTags); @@ -714,8 +813,8 @@ SSLParseCertificateConfiguration( __func__, params->configFilePath, line_num, errPtr); REC_SignalError(errBuf, alarmAlready); } else { - if (ssl_extract_certificate(&line_info, addr, cert, ca, key)) { - if (!ssl_store_ssl_context(params, lookup, addr, cert, ca, key)) { + if (ssl_extract_certificate(&line_info, addr, cert, ca, key, session_ticket_enabled, ticket_key_filename)) { + if (!ssl_store_ssl_context(params, lookup, addr, cert, ca, key, session_ticket_enabled, ticket_key_filename)) { Error("failed to load SSL certificate specification from %s line %u", params->configFilePath, line_num); } @@ -741,3 +840,60 @@ SSLParseCertificateConfiguration( return true; } + +/* + * RFC 5077. Create session ticket to resume SSL session without requiring session-specific state at the TLS server. + * Specifically, it distributes the encrypted session-state information to the client in the form of a ticket and + * a mechanism to present the ticket back to the server. + * */ +int ssl_callback_session_ticket(SSL *ssl, + unsigned char *keyname, + unsigned char *iv, + EVP_CIPHER_CTX *cipher_ctx, + HMAC_CTX *hctx, + int enc) +{ + ssl_ticket_key_t* ssl_ticket_key = (ssl_ticket_key_t*) SSL_CTX_get_ex_data(SSL_get_SSL_CTX(ssl), ssl_session_ticket_index); + if (NULL == ssl_ticket_key) { + Error("ssl ticket key is null."); + return -1; + } + + if (enc == 1) { + memcpy(keyname, ssl_ticket_key->key_name, 16); + RAND_pseudo_bytes(iv, EVP_MAX_IV_LENGTH); + EVP_EncryptInit_ex(cipher_ctx, EVP_aes_128_cbc(), NULL, + ssl_ticket_key->aes_key, iv); + HMAC_Init_ex(hctx, ssl_ticket_key->hmac_secret, 16, evp_md_func, NULL); + Note("create ticket for a new session"); + + return 0; + } else if (enc == 0) { + if (memcmp(keyname, ssl_ticket_key->key_name, 16)) { + Error("keyname is not consistent."); + return 0; + } + + EVP_DecryptInit_ex(cipher_ctx, EVP_aes_128_cbc(), NULL, + ssl_ticket_key->aes_key, iv); + HMAC_Init_ex(hctx, ssl_ticket_key->hmac_secret, 16, evp_md_func, NULL); + + Note("verify the ticket for an existing session." ); + return 1; + } + + return -1; +} + +void +SSLReleaseContext(SSL_CTX * ctx) +{ + ssl_ticket_key_t * ssl_ticket_key = (ssl_ticket_key_t*)SSL_CTX_get_ex_data(ctx, ssl_session_ticket_index); + + // Free the ticket if this is the last reference. + if (ctx->references == 1 && ssl_ticket_key) { + delete ssl_ticket_key; + } + + SSL_CTX_free(ctx); +} http://git-wip-us.apache.org/repos/asf/trafficserver/blob/8dbf06bf/iocore/net/test_certlookup.cc ---------------------------------------------------------------------- diff --git a/iocore/net/test_certlookup.cc b/iocore/net/test_certlookup.cc index 9327258..e0ba053 100644 --- a/iocore/net/test_certlookup.cc +++ b/iocore/net/test_certlookup.cc @@ -175,6 +175,15 @@ load_hostnames_csv(const char * fname, SSLCertLookup& lookup) return count; } +// This stub version of SSLReleaseContext saves us from having to drag in a lot +// of binary dependencies. We don't have session tickets in this test environment +// so it's safe to do this; just a bit ugly. +void +SSLReleaseContext(SSL_CTX * ctx) +{ + SSL_CTX_free(ctx); +} + int main(int argc, const char ** argv) { res_track_memory = 1;
