*) end-generation hook: Fix false notification of end-of-generation for
temporary intervals with no active MPM children. [Jeff Trawick]
- *) mod_ssl: Add support for RFC 5077 TLS Session tickets.
- [Paul Querna]
+ *) mod_ssl: Add support for configuring persistent TLS session ticket
+ encryption/decryption keys (useful for clustered environments).
+ [Paul Querna, Kaspar Brand]
*) mod_usertrack: Use random value instead of remote IP address.
[Stefan Fritsch]
</usage>
</directivesynopsis>
+<directivesynopsis>
+<name>SSLSessionTicketKeyFile</name>
+<description>Persistent encryption/decryption key for TLS session tickets</description>
+<syntax>SSLSessionTicketKeyFile <em>file-path</em></syntax>
+<contextlist><context>server config</context>
+<context>virtual host</context></contextlist>
+<compatibility>Available in httpd 2.4.0 and later, if using OpenSSL 0.9.8h or later</compatibility>
+
+<usage>
+<p>Optionally configures a secret key for encrypting and decrypting
+TLS session tickets, as defined in
+<a href="http://www.ietf.org/rfc/rfc5077.txt">RFC 5077</a>.
+Primarily suitable for clustered environments where TLS sessions information
+should be shared between multiple nodes. For single-instance httpd setups,
+it is recommended to <em>not</em> configure a ticket key file, but to
+rely on (random) keys generated by mod_ssl at startup, instead.</p>
+<p>The ticket key file must contain 48 bytes of random data,
+preferrably created from a high-entropy source. On a Unix-based system,
+a ticket key file can be created as follows:</p>
+
+<example>
+dd if=/dev/random of=/path/to/file.tkey bs=1 count=48
+</example>
+
+<p>Ticket keys should be rotated (replaced) on a frequent basis,
+as this is the only way to invalidate an existing session ticket -
+OpenSSL currently doesn't allow to specify a limit for ticket lifetimes.</p>
+
+<note type="warning">
+<p>The ticket key file contains sensitive keying material and should
+be protected with file permissions similar to those used for
+<directive module="mod_ssl">SSLCertificateKeyFile</directive>.</p>
+</note>
+</usage>
+</directivesynopsis>
+
</modulesynopsis>
SSL_CMD_SRV(FIPS, FLAG,
"Enable FIPS-140 mode "
"(`on', `off')")
-#ifdef HAVE_TLSEXT_TICKETS
- SSL_CMD_SRV(TicketKeyFile, TAKE2,
- "Key file to use for encrypting and decrypting the client ticket (RFC 5077) "
- "(keyname '/path/to/file')")
- SSL_CMD_SRV(TicketKeyDefault, TAKE1,
- "Set the key name used by default for new sessions "
- "(keyname)")
-#endif
SSL_CMD_ALL(CipherSuite, TAKE1,
"Colon-delimited list of permitted SSL Ciphers "
"('XXX:...:XXX' - see manual)")
SSL_CMD_SRV(PKCS7CertificateFile, TAKE1,
"PKCS#7 file containing server certificate and chain"
" certificates ('/path/to/file' - PEM encoded)")
+#ifdef HAVE_TLS_SESSION_TICKETS
+ SSL_CMD_SRV(SessionTicketKeyFile, TAKE1,
+ "TLS session ticket encryption/decryption key file (RFC 5077) "
+ "('/path/to/file' - file with 48 bytes of random data)")
+#endif
SSL_CMD_ALL(CACertificatePath, TAKE1,
"SSL CA Certificate path "
"('/path/to/dir' - contains PEM encoded files)")
mctx->pks = NULL;
mctx->pkp = NULL;
+#ifdef HAVE_TLS_SESSION_TICKETS
+ mctx->ticket_key = NULL;
+#endif
+
mctx->protocol = SSL_PROTOCOL_ALL;
mctx->pphrase_dialog_type = SSL_PPTYPE_UNSET;
mctx->pks = apr_pcalloc(p, sizeof(*mctx->pks));
/* mctx->pks->... certs/keys are set during module init */
+
+#ifdef HAVE_TLS_SESSION_TICKETS
+ mctx->ticket_key = apr_pcalloc(p, sizeof(*mctx->ticket_key));
+#endif
}
static SSLSrvConfigRec *ssl_config_server_new(apr_pool_t *p)
sc->fips = UNSET;
#endif
-#ifdef HAVE_TLSEXT_TICKETS
- sc->default_ticket_name = NULL;
- sc->default_ticket = NULL;
- sc->tickets = apr_array_make(p, 4, sizeof(modssl_ticket_t*));
-#endif
-
modssl_ctx_init_proxy(sc, p);
modssl_ctx_init_server(sc, p);
cfgMergeString(pks->ca_name_path);
cfgMergeString(pks->ca_name_file);
+
+#ifdef HAVE_TLS_SESSION_TICKETS
+ cfgMergeString(ticket_key->file_path);
+#endif
}
/*
cfgMerge(mc, NULL);
cfgMerge(enabled, SSL_ENABLED_UNSET);
-#ifdef HAVE_TLSEXT_TICKETS
- cfgMergeString(default_ticket_name);
- apr_array_cat(mrg->tickets, base->tickets);
- apr_array_cat(mrg->tickets, add->tickets);
-#endif
cfgMergeBool(proxy_enabled);
cfgMergeInt(session_cache_timeout);
cfgMergeBool(cipher_server_pref);
return "Argument must be On, Off, or Optional";
}
-const char *ssl_cmd_SSLTicketKeyDefault(cmd_parms *cmd, void *dcfg, const char *name)
-{
-#ifdef HAVE_TLSEXT_TICKETS
- SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
-
- sc->default_ticket_name = name;
-
- return NULL;
-#else
- return "TLS Ticket keys are not supported.";
-#endif
-}
-
-const char *ssl_cmd_SSLTicketKeyFile(cmd_parms *cmd, void *dcfg, const char *name, const char *path)
-{
-#ifdef HAVE_TLSEXT_TICKETS
- apr_status_t rv;
- apr_file_t *fp;
- apr_size_t len;
- char buf[TLSEXT_TICKET_KEYLEN];
- modssl_ticket_t* ticket = NULL;
- SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
-
- path = ap_server_root_relative(cmd->pool, path);
-
- rv = apr_file_open(&fp, path, APR_READ|APR_BINARY,
- APR_OS_DEFAULT, cmd->temp_pool);
-
- if (rv != APR_SUCCESS) {
- return apr_psprintf(cmd->pool,
- "Failed to open %s: (%d) %pm",
- path, rv, &rv);
- }
-
- rv = apr_file_read_full(fp, &buf[0], TLSEXT_TICKET_KEYLEN, &len);
-
- if (rv != APR_SUCCESS) {
- return apr_psprintf(cmd->pool,
- "Failed to read at least 48 bytes from %s: (%d) %pm",
- path, rv, &rv);
- }
-
- ticket = apr_palloc(cmd->pool, sizeof(modssl_ticket_t));
-
- ticket->conf_name = name;
-
- memcpy(ticket->key_name, buf, 16);
- memcpy(ticket->hmac_secret, buf + 16, 16);
- memcpy(ticket->aes_key, buf + 32, 16);
-
- APR_ARRAY_PUSH(sc->tickets, modssl_ticket_t*) = ticket;
-
- return NULL;
-#else
- return "TLS Ticket keys are not supported.";
-#endif
-}
-
const char *ssl_cmd_SSLFIPS(cmd_parms *cmd, void *dcfg, int flag)
{
#ifdef HAVE_FIPS
return NULL;
}
+#ifdef HAVE_TLS_SESSION_TICKETS
+const char *ssl_cmd_SSLSessionTicketKeyFile(cmd_parms *cmd,
+ void *dcfg,
+ const char *arg)
+{
+ SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
+ const char *err;
+
+ if ((err = ssl_cmd_check_file(cmd, &arg))) {
+ return err;
+ }
+
+ sc->server->ticket_key->file_path = arg;
+
+ return NULL;
+}
+#endif
+
#define NO_PER_DIR_SSL_CA \
"Your SSL library does not have support for per-directory CA"
#endif
ssl_die();
}
+}
-#ifdef HAVE_TLSEXT_TICKETS
- if (mctx->sc->tickets->nelts > 0) {
+#ifdef HAVE_TLS_SESSION_TICKETS
+static void ssl_init_ticket_key(server_rec *s,
+ apr_pool_t *p,
+ apr_pool_t *ptemp,
+ modssl_ctx_t *mctx)
+{
+ apr_status_t rv;
+ apr_file_t *fp;
+ apr_size_t len;
+ char buf[TLSEXT_TICKET_KEY_LEN];
+ char *path;
+ modssl_ticket_key_t *ticket_key = mctx->ticket_key;
+
+ if (!ticket_key->file_path) {
+ return;
+ }
- if (mctx->sc->default_ticket_name != NULL) {
- int i;
- modssl_ticket_t* ticket = NULL;
- mctx->sc->default_ticket = NULL;
+ path = ap_server_root_relative(p, ticket_key->file_path);
- for (i = 0; i < mctx->sc->tickets->nelts; i++) {
- ticket = APR_ARRAY_IDX(mctx->sc->tickets, i, modssl_ticket_t*);
- if (strcmp(ticket->conf_name, mctx->sc->default_ticket_name) == 0) {
- mctx->sc->default_ticket = ticket;
- }
- }
+ rv = apr_file_open(&fp, path, APR_READ|APR_BINARY,
+ APR_OS_DEFAULT, ptemp);
- if (mctx->sc->default_ticket == NULL) {
- ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(01912)
- "Misconfigured TLS Tickets. Couldn't find key named '%s'",
- mctx->sc->default_ticket_name);
- ssl_die();
- }
- }
- else {
- mctx->sc->default_ticket = APR_ARRAY_IDX(mctx->sc->tickets, 0, modssl_ticket_t*);
- }
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(02286)
+ "Failed to open ticket key file %s: (%d) %pm",
+ path, rv, &rv);
+ ssl_die();
+ }
- if (!SSL_CTX_set_tlsext_ticket_key_cb(mctx->ssl_ctx, ssl_callback_tlsext_tickets)) {
- ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(01913)
- "Unable to initialize TLS session ticket extension "
- "(incompatible OpenSSL version?)");
- ssl_log_ssl_error(SSLLOG_MARK, APLOG_EMERG, s);
- ssl_die();
- }
+ rv = apr_file_read_full(fp, &buf[0], TLSEXT_TICKET_KEY_LEN, &len);
+
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(02287)
+ "Failed to read %d bytes from %s: (%d) %pm",
+ TLSEXT_TICKET_KEY_LEN, path, rv, &rv);
+ ssl_die();
}
-#endif
+ memcpy(ticket_key->key_name, buf, 16);
+ memcpy(ticket_key->hmac_secret, buf + 16, 16);
+ memcpy(ticket_key->aes_key, buf + 32, 16);
+
+ if (!SSL_CTX_set_tlsext_ticket_key_cb(mctx->ssl_ctx,
+ ssl_callback_SessionTicket)) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(01913)
+ "Unable to initialize TLS session ticket key callback "
+ "(incompatible OpenSSL version?)");
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_EMERG, s);
+ ssl_die();
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(02288)
+ "TLS session ticket key for %s successfully loaded from %s",
+ (mySrvConfig(s))->vhost_id, path);
}
+#endif
static void ssl_init_proxy_certs(server_rec *s,
apr_pool_t *p,
ssl_init_ctx(s, p, ptemp, sc->server);
ssl_init_server_certs(s, p, ptemp, sc->server);
+
+#ifdef HAVE_TLS_SESSION_TICKETS
+ ssl_init_ticket_key(s, p, ptemp, sc->server);
+#endif
}
/*
}
#endif
-#ifdef HAVE_TLSEXT_TICKETS
-
-#ifndef tlsext_tick_md
-#ifdef OPENSSL_NO_SHA256
-#define tlsext_tick_md EVP_sha1
-#else
-#define tlsext_tick_md EVP_sha256
-#endif
-#endif
-
-int ssl_callback_tlsext_tickets(SSL *ssl,
- unsigned char *keyname,
- unsigned char *iv,
- EVP_CIPHER_CTX *cipher_ctx,
- HMAC_CTX *hctx,
- int mode)
+#ifdef HAVE_TLS_SESSION_TICKETS
+/*
+ * This callback function is executed when OpenSSL needs a key for encrypting/
+ * decrypting a TLS session ticket (RFC 5077) and a ticket key file has been
+ * configured through SSLSessionTicketKeyFile.
+ */
+int ssl_callback_SessionTicket(SSL *ssl,
+ unsigned char *keyname,
+ unsigned char *iv,
+ EVP_CIPHER_CTX *cipher_ctx,
+ HMAC_CTX *hctx,
+ int mode)
{
- conn_rec *conn = (conn_rec *)SSL_get_app_data(ssl);
- server_rec *s = mySrvFromConn(conn);
+ conn_rec *c = (conn_rec *)SSL_get_app_data(ssl);
+ server_rec *s = mySrvFromConn(c);
SSLSrvConfigRec *sc = mySrvConfig(s);
+ SSLConnRec *sslconn = myConnConfig(c);
+ modssl_ctx_t *mctx = myCtxConfig(sslconn, sc);
+ modssl_ticket_key_t *ticket_key = mctx->ticket_key;
if (mode == 1) {
- modssl_ticket_t* ticket = sc->default_ticket;
-
- /* Setting up the stuff for encrypting:
- * - keyname contains at least 16 bytes we can write to.
- * - iv contains at least EVP_MAX_IV_LENGTH (16) bytes we can write to.
- * - hctx is already allocated, we just need to set the
- * secret key via HMAC_Init_ex.
- * - cipher_ctx is also allocated, and we need to configure
- * the cipher and private key.
+ /*
+ * OpenSSL is asking for a key for encrypting a ticket,
+ * see s3_srvr.c:ssl3_send_newsession_ticket()
*/
- if (ticket == NULL) {
- /* this should not happen, we always set the default
- * ticket.
- */
+ if (ticket_key == NULL) {
+ /* should never happen, but better safe than sorry */
return -1;
}
- memcpy(keyname, ticket->key_name, 16);
-
+ memcpy(keyname, ticket_key->key_name, 16);
RAND_pseudo_bytes(iv, EVP_MAX_IV_LENGTH);
-
EVP_EncryptInit_ex(cipher_ctx, EVP_aes_128_cbc(), NULL,
- ticket->aes_key, iv);
+ ticket_key->aes_key, iv);
+ HMAC_Init_ex(hctx, ticket_key->hmac_secret, 16, tlsext_tick_md(), NULL);
- HMAC_Init_ex(hctx, ticket->hmac_secret, 16, tlsext_tick_md(), NULL);
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(02289)
+ "TLS session ticket key for %s successfully set, "
+ "creating new session ticket", sc->vhost_id);
return 0;
}
else if (mode == 0) {
- /* Setup contextes for decryption, based on the keyname input */
- int i;
- modssl_ticket_t* ticket = NULL;
-
- for (i = 0; i < sc->tickets->nelts; i++) {
- modssl_ticket_t* itticket = APR_ARRAY_IDX(sc->tickets, i, modssl_ticket_t*);
- if (memcmp(keyname, itticket->key_name, 16) == 0) {
- ticket = itticket;
- break;
- }
- }
+ /*
+ * OpenSSL is asking for the decryption key,
+ * see t1_lib.c:tls_decrypt_ticket()
+ */
- if (ticket == NULL) {
- /* Ticket key not found, but no error */
+ /* check key name */
+ if (ticket_key == NULL || memcmp(keyname, ticket_key->key_name, 16)) {
return 0;
}
- EVP_DecryptInit_ex(cipher_ctx, EVP_aes_128_cbc(), NULL, ticket->aes_key, iv);
+ EVP_DecryptInit_ex(cipher_ctx, EVP_aes_128_cbc(), NULL,
+ ticket_key->aes_key, iv);
+ HMAC_Init_ex(hctx, ticket_key->hmac_secret, 16, tlsext_tick_md(), NULL);
- HMAC_Init_ex(hctx, ticket->hmac_secret, 16, tlsext_tick_md(), NULL);
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(02290)
+ "TLS session ticket key for %s successfully set, "
+ "decrypting existing session ticket", sc->vhost_id);
- if (ticket != sc->default_ticket) {
- /* Ticket key found, we did our stuff, but didn't use the default,
- * re-issue a ticket with the default ticket */
- return 2;
- }
- else {
- return 1;
- }
+ return 1;
}
- /* TODO: log invalid use */
+ /* OpenSSL is not expected to call us with modes other than 1 or 0 */
return -1;
}
-
#endif
#endif
#ifndef OPENSSL_NO_TLSEXT
-#ifdef SSL_CTX_set_tlsext_ticket_key_cb
-#define HAVE_TLSEXT_TICKETS
+#ifdef SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB
+#define HAVE_TLS_SESSION_TICKETS
+#define TLSEXT_TICKET_KEY_LEN 48
+#ifndef tlsext_tick_md
+#ifdef OPENSSL_NO_SHA256
+#define tlsext_tick_md EVP_sha1
+#else
+#define tlsext_tick_md EVP_sha256
+#endif
+#endif
#endif
#endif
ssl_verify_t verify_mode;
} modssl_auth_ctx_t;
-
-#ifdef HAVE_TLSEXT_TICKETS
-
-/* 48 bytes: 16 for keyname, 16 for HMAC secret, 16 for AES private key */
-#define TLSEXT_TICKET_KEYLEN (48)
-
+#ifdef HAVE_TLS_SESSION_TICKETS
typedef struct {
- /* Human readable name, used in the configuration */
- const char *conf_name;
- char key_name[16];
+ const char *file_path;
+ unsigned char key_name[16];
unsigned char hmac_secret[16];
unsigned char aes_key[16];
-} modssl_ticket_t;
+} modssl_ticket_key_t;
#endif
typedef struct SSLSrvConfigRec SSLSrvConfigRec;
modssl_pk_server_t *pks;
modssl_pk_proxy_t *pkp;
+#ifdef HAVE_TLS_SESSION_TICKETS
+ modssl_ticket_key_t *ticket_key;
+#endif
+
ssl_proto_t protocol;
/** config for handling encrypted keys */
#ifdef HAVE_FIPS
BOOL fips;
#endif
-#ifdef HAVE_TLSEXT_TICKETS
- const char *default_ticket_name;
- modssl_ticket_t* default_ticket;
- apr_array_header_t* tickets;
-#endif
};
/**
const char *ssl_cmd_SSLProxyMachineCertificatePath(cmd_parms *, void *, const char *);
const char *ssl_cmd_SSLProxyMachineCertificateFile(cmd_parms *, void *, const char *);
const char *ssl_cmd_SSLProxyMachineCertificateChainFile(cmd_parms *, void *, const char *);
+#ifdef HAVE_TLS_SESSION_TICKETS
+const char *ssl_cmd_SSLSessionTicketKeyFile(cmd_parms *cmd, void *dcfg, const char *arg);
+#endif
const char *ssl_cmd_SSLProxyCheckPeerExpire(cmd_parms *cmd, void *dcfg, int flag);
const char *ssl_cmd_SSLProxyCheckPeerCN(cmd_parms *cmd, void *dcfg, int flag);
const char *ssl_cmd_SSLOCSPEnable(cmd_parms *cmd, void *dcfg, int flag);
const char *ssl_cmd_SSLFIPS(cmd_parms *cmd, void *dcfg, int flag);
-const char *ssl_cmd_SSLTicketKeyDefault(cmd_parms *cmd, void *dcfg, const char *name);
-const char *ssl_cmd_SSLTicketKeyFile(cmd_parms *cmd, void *dcfg, const char *name, const char *path);
/** module initialization */
int ssl_init_Module(apr_pool_t *, apr_pool_t *, apr_pool_t *, server_rec *);
#ifndef OPENSSL_NO_TLSEXT
int ssl_callback_ServerNameIndication(SSL *, int *, modssl_ctx_t *);
#endif
-
-#ifdef HAVE_TLSEXT_TICKETS
-int ssl_callback_tlsext_tickets(SSL *ssl,
- unsigned char *keyname,
- unsigned char *iv,
- EVP_CIPHER_CTX *cipher_ctx,
- HMAC_CTX *hctx,
- int mode);
+#ifdef HAVE_TLS_SESSION_TICKETS
+int ssl_callback_SessionTicket(SSL *, unsigned char *, unsigned char *,
+ EVP_CIPHER_CTX *, HMAC_CTX *, int);
#endif
/** Session Cache Support */