Repository: trafficserver Updated Branches: refs/heads/master 61e6dcd51 -> e0c35aa8a
TS-612: add support for SSL keys with passphrases Add support for dialog settings in ssl_multicert.config to allow for passphrase protected private keys. Project: http://git-wip-us.apache.org/repos/asf/trafficserver/repo Commit: http://git-wip-us.apache.org/repos/asf/trafficserver/commit/e0c35aa8 Tree: http://git-wip-us.apache.org/repos/asf/trafficserver/tree/e0c35aa8 Diff: http://git-wip-us.apache.org/repos/asf/trafficserver/diff/e0c35aa8 Branch: refs/heads/master Commit: e0c35aa8aad57ae473d02528c67f96da5b549b8c Parents: 61e6dcd Author: Ron Barber <[email protected]> Authored: Tue Feb 18 13:36:40 2014 -0600 Committer: James Peach <[email protected]> Committed: Tue Feb 25 15:14:12 2014 -0800 ---------------------------------------------------------------------- CHANGES | 3 + .../configuration/ssl_multicert.config.en.rst | 30 +- iocore/net/P_SSLUtils.h | 8 - iocore/net/SSLUtils.cc | 294 ++++++++++++++++--- proxy/config/ssl_multicert.config.default | 11 +- 5 files changed, 287 insertions(+), 59 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/trafficserver/blob/e0c35aa8/CHANGES ---------------------------------------------------------------------- diff --git a/CHANGES b/CHANGES index 3fc0e38..a4b08be 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,9 @@ -*- coding: utf-8 -*- Changes with Apache Traffic Server 5.0.0 + *) [TS-612] Add support for SSL keys with passphrases. + Author: Ron Barber <[email protected]> + *) [TS-2319] Change default behavior for the various ignore-mismatch configurations. http://git-wip-us.apache.org/repos/asf/trafficserver/blob/e0c35aa8/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 331e47e..f3ff295 100644 --- a/doc/reference/configuration/ssl_multicert.config.en.rst +++ b/doc/reference/configuration/ssl_multicert.config.en.rst @@ -94,6 +94,22 @@ ticket_key_name=FILENAME specified, and internal session ticket key is generated. This key will be different each time Traffic Server is started. +ssl_key_dialog=builtin|"exec:/path/to/program [args]" + Method used to provide a pass phrase for encrypted private keys. If the + pass phrase is incorrect, SSL negotiation for this dest_ip will fail for + clients who attempt to connect. + Two options are supported: builtin and exec + ``builtin`` - Requests pass phrase via stdin/stdout. User will be + provided the ssl_cert_name and be prompted for the pass phrase. + Useful for debugging. + ``exec:`` - Executes program /path/to/program and passes args, if + specified, to the program and reads the output from stdout for + the pass phrase. If args are provided then the entire exec: string + must be quoted with "" (see examples). Arguments with white space + are supported by single quoting ('). The intent is that this + program runs a security check to ensure that the system is not + compromised by an attacker before providing the pass phrase. + Certificate Selection ===================== @@ -153,9 +169,21 @@ 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 +certificate ``server.pem`` and disable session tickets 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 + +The following examples configure Traffic Server to use the SSL +certificate ``server.pem`` which includes an encrypted private key. +The external program /usr/bin/mypass will be called on startup with one +parameter (foo) in the first example, and with two parameters (foo) +and (ba r) in the second example, the program (mypass) will return the +pass phrase to decrypt the keys. + +:: + + ssl_cert_name=server1.pem ssl_key_dialog="exec:/usr/bin/mypass foo" + ssl_cert_name=server2.pem ssl_key_dialog="exec:/usr/bin/mypass foo 'ba r'" http://git-wip-us.apache.org/repos/asf/trafficserver/blob/e0c35aa8/iocore/net/P_SSLUtils.h ---------------------------------------------------------------------- diff --git a/iocore/net/P_SSLUtils.h b/iocore/net/P_SSLUtils.h index 5ed2d82..e517824 100644 --- a/iocore/net/P_SSLUtils.h +++ b/iocore/net/P_SSLUtils.h @@ -48,14 +48,6 @@ struct SSLCertLookup; // Create a default SSL server context. SSL_CTX * SSLDefaultServerContext(); -// Create and initialize a SSL server context. -SSL_CTX * -SSLInitServerContext( - const SSLConfigParams * param, - const char * serverCertPtr, - const char * serverCaCertPtr, - const char * serverKeyPtr); - // Create and initialize a SSL client context. SSL_CTX * SSLInitClientContext(const SSLConfigParams * param); http://git-wip-us.apache.org/repos/asf/trafficserver/blob/e0c35aa8/iocore/net/SSLUtils.cc ---------------------------------------------------------------------- diff --git a/iocore/net/SSLUtils.cc b/iocore/net/SSLUtils.cc index 6ec3208..d066416 100644 --- a/iocore/net/SSLUtils.cc +++ b/iocore/net/SSLUtils.cc @@ -31,6 +31,8 @@ #include <openssl/x509.h> #include <openssl/asn1.h> #include <openssl/rand.h> +#include <unistd.h> +#include <termios.h> #if HAVE_OPENSSL_EVP_H #include <openssl/evp.h> @@ -55,6 +57,12 @@ #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" +#define SSL_KEY_DIALOG "ssl_key_dialog" + +// openssl version must be 0.9.4 or greater +#if (OPENSSL_VERSION_NUMBER < 0x00090400L) +# error Traffic Server requires an OpenSSL library version 0.9.4 or greater +#endif #ifndef evp_md_func #ifdef OPENSSL_NO_SHA256 @@ -70,6 +78,18 @@ typedef const SSL_METHOD * ink_ssl_method_t; typedef SSL_METHOD * ink_ssl_method_t; #endif +// gather user provided settings from ssl_multicert.config in to a single struct +struct ssl_user_config +{ + xptr<char> addr; // dest_ip - IPv[64] address to match + xptr<char> cert; // ssl_cert_name - certificate + xptr<char> ca; // ssl_ca_name - CA public certificate + xptr<char> key; // ssl_key_name - Private key + int session_ticket_enabled; // ssl_ticket_enabled - session ticket enabled + xptr<char> ticket_key_filename; // ticket_key_name - session key file. [key_name (16Byte) + HMAC_secret (16Byte) + AES_key (16Byte)] + xptr<char> dialog; // ssl_key_dialog - Private key dialog +}; + // Check if the ticket_key callback #define is available, and if so, enable session tickets. #ifdef SSL_CTX_set_tlsext_ticket_key_cb # define HAVE_OPENSSL_SESSION_TICKETS 1 @@ -303,6 +323,168 @@ fail: #endif /* HAVE_OPENSSL_SESSION_TICKETS */ } +struct passphrase_cb_userdata +{ + const SSLConfigParams * _configParams; + const char * _serverDialog; + const char * _serverCert; + const char * _serverKey; + + passphrase_cb_userdata(const SSLConfigParams *params,const char *dialog, const char *cert, const char *key) : + _configParams(params), _serverDialog(dialog), _serverCert(cert), _serverKey(key) {} +}; + +// RAII implementation for struct termios +struct ssl_termios : public termios +{ + ssl_termios(int fd) { + _fd = -1; + // populate base class data + if (tcgetattr(fd, this) == 0) { // success + _fd = fd; + } + // save our copy + _initialAttr = *this; + } + + ~ssl_termios() { + if (_fd != -1) { + tcsetattr(_fd, 0, &_initialAttr); + } + } + + bool ok() { + return (_fd != -1); + } + +private: + int _fd; + struct termios _initialAttr; +}; + +static int +ssl_getpassword(const char* prompt, char* buffer, int size) +{ + fprintf(stdout, "%s", prompt); + + // disable echo and line buffering + ssl_termios tty_attr(STDIN_FILENO); + + if (!tty_attr.ok()) { + return -1; + } + + tty_attr.c_lflag &= ~ICANON; // no buffer, no backspace + tty_attr.c_lflag &= ~ECHO; // no echo + tty_attr.c_lflag &= ~ISIG; // no signal for ctrl-c + + if (tcsetattr(STDIN_FILENO, 0, &tty_attr) < 0) { + return -1; + } + + int i = 0; + int ch = 0; + *buffer = 0; + while ((ch = getchar()) != '\n' && ch != EOF) { + // make sure room in buffer + if (i >= size - 1) { + return -1; + } + + buffer[i] = ch; + buffer[++i] = 0; + } + + return i; +} + +static int +ssl_private_key_passphrase_callback_exec(char *buf, int size, int rwflag, void *userdata) +{ + if (0 == size) { + return 0; + } + + *buf = 0; + passphrase_cb_userdata *ud = static_cast<passphrase_cb_userdata *> (userdata); + + Debug("ssl", "ssl_private_key_passphrase_callback_exec rwflag=%d serverDialog=%s", rwflag, ud->_serverDialog); + + // only respond to reading private keys, not writing them (does ats even do that?) + if (0 == rwflag) { + // execute the dialog program and use the first line output as the passphrase + FILE *f = popen(ud->_serverDialog, "r"); + if (f) { + if (fgets(buf, size, f)) { + // remove any ending CR or LF + for (char *pass = buf; *pass; pass++) { + if (*pass == '\n' || *pass == '\r') { + *pass = 0; + break; + } + } + } + pclose(f); + } else {// popen failed + Error("could not open dialog '%s' - %s", ud->_serverDialog, strerror(errno)); + } + } + return strlen(buf); +} + +static int +ssl_private_key_passphrase_callback_builtin(char *buf, int size, int rwflag, void *userdata) +{ + if (0 == size) { + return 0; + } + + *buf = 0; + passphrase_cb_userdata *ud = static_cast<passphrase_cb_userdata *> (userdata); + + Debug("ssl", "ssl_private_key_passphrase_callback rwflag=%d serverDialog=%s", rwflag, ud->_serverDialog); + + // only respond to reading private keys, not writing them (does ats even do that?) + if (0 == rwflag) { + // output request + fprintf(stdout, "Some of your private key files are encrypted for security reasons.\n"); + fprintf(stdout, "In order to read them you have to provide the pass phrases.\n"); + fprintf(stdout, "ssl_cert_name=%s", ud->_serverCert); + if (ud->_serverKey) { // output ssl_key_name if provided + fprintf(stdout, " ssl_key_name=%s", ud->_serverKey); + } + fprintf(stdout, "\n"); + // get passphrase + // if error, then no passphrase + if (ssl_getpassword("Enter passphrase:", buf, size) <= 0) { + *buf = 0; + } + fprintf(stdout, "\n"); + } + return strlen(buf); +} + +static bool +ssl_private_key_validate_exec(const char *cmdLine) +{ + if (NULL == cmdLine) { + errno = EINVAL; + return false; + } + + bool bReturn = false; + char *cmdLineCopy = ats_strdup(cmdLine); + char *ptr = cmdLineCopy; + + while(*ptr && !isspace(*ptr)) ++ptr; + *ptr = 0; + if (access(cmdLineCopy, X_OK) != -1) { + bReturn = true; + } + ats_free(cmdLineCopy); + return bReturn; +} + void SSLInitializeLibrary() { @@ -415,9 +597,7 @@ SSLDefaultServerContext() SSL_CTX * SSLInitServerContext( const SSLConfigParams * params, - const char * serverCertPtr, - const char * serverCaCertPtr, - const char * serverKeyPtr) + const ssl_user_config & sslMultCertSettings) { int session_id_context; int server_verify_client; @@ -445,10 +625,33 @@ SSLInitServerContext( #endif SSL_CTX_set_quiet_shutdown(ctx, 1); + // pass phrase dialog configuration + passphrase_cb_userdata ud(params, sslMultCertSettings.dialog, sslMultCertSettings.cert, sslMultCertSettings.key); + + if (sslMultCertSettings.dialog) { + pem_password_cb * passwd_cb = NULL; + if (strncmp(sslMultCertSettings.dialog, "exec:", 5) == 0) { + ud._serverDialog = &sslMultCertSettings.dialog[5]; + // validate the exec program + if (!ssl_private_key_validate_exec(ud._serverDialog)) { + SSLError("failed to access '%s' pass phrase program: %s", (const char *) ud._serverDialog, strerror(errno)); + goto fail; + } + passwd_cb = ssl_private_key_passphrase_callback_exec; + } else if (strcmp(sslMultCertSettings.dialog, "builtin") == 0) { + passwd_cb = ssl_private_key_passphrase_callback_builtin; + } else { // unknown config + SSLError("unknown " SSL_KEY_DIALOG " configuration value '%s'", (const char *)sslMultCertSettings.dialog); + goto fail; + } + SSL_CTX_set_default_passwd_cb(ctx, passwd_cb); + SSL_CTX_set_default_passwd_cb_userdata(ctx, &ud); + } + // XXX OpenSSL recommends that we should use SSL_CTX_use_certificate_chain_file() here. That API // also loads only the first certificate, but it allows the intermediate CA certificate chain to // be in the same file. SSL_CTX_use_certificate_chain_file() was added in OpenSSL 0.9.3. - completeServerCertPath = Layout::relative_to(params->serverCertPathOnly, serverCertPtr); + completeServerCertPath = Layout::relative_to(params->serverCertPathOnly, sslMultCertSettings.cert); if (!SSL_CTX_use_certificate_file(ctx, completeServerCertPath, SSL_FILETYPE_PEM)) { SSLError("failed to load certificate from %s", (const char *)completeServerCertPath); goto fail; @@ -464,22 +667,22 @@ SSLInitServerContext( } // Now, load any additional certificate chains specified in this entry. - if (serverCaCertPtr) { - xptr<char> completeServerCertChainPath(Layout::relative_to(params->serverCertPathOnly, serverCaCertPtr)); + if (sslMultCertSettings.ca) { + xptr<char> completeServerCertChainPath(Layout::relative_to(params->serverCertPathOnly, sslMultCertSettings.ca)); if (!SSL_CTX_add_extra_chain_cert_file(ctx, completeServerCertChainPath)) { SSLError("failed to load certificate chain from %s", (const char *)completeServerCertChainPath); goto fail; } } - if (serverKeyPtr == NULL) { + if (!sslMultCertSettings.key) { // assume private key is contained in cert obtained from multicert file. if (!SSL_CTX_use_PrivateKey_file(ctx, completeServerCertPath, SSL_FILETYPE_PEM)) { SSLError("failed to load server private key from %s", (const char *)completeServerCertPath); goto fail; } } else if (params->serverKeyPathOnly != NULL) { - xptr<char> completeServerKeyPath(Layout::get()->relative_to(params->serverKeyPathOnly, serverKeyPtr)); + xptr<char> completeServerKeyPath(Layout::get()->relative_to(params->serverKeyPathOnly, sslMultCertSettings.key)); if (!SSL_CTX_use_PrivateKey_file(ctx, completeServerKeyPath, SSL_FILETYPE_PEM)) { SSLError("failed to load server private key from %s", (const char *)completeServerKeyPath); goto fail; @@ -530,10 +733,16 @@ SSLInitServerContext( goto fail; } } - +#define SSL_CLEAR_PW_REFERENCES(UD,CTX) { \ + memset(static_cast<void *>(&UD),0,sizeof(UD));\ + SSL_CTX_set_default_passwd_cb(CTX, NULL);\ + SSL_CTX_set_default_passwd_cb_userdata(CTX, NULL);\ + } + SSL_CLEAR_PW_REFERENCES(ud,ctx) return ssl_context_enable_ecdh(ctx); fail: + SSL_CLEAR_PW_REFERENCES(ud,ctx) SSL_CTX_free(ctx); return NULL; } @@ -704,18 +913,13 @@ static bool ssl_store_ssl_context( const SSLConfigParams * params, SSLCertLookup * lookup, - xptr<char>& addr, - xptr<char>& cert, - xptr<char>& ca, - xptr<char>& key, - const int session_ticket_enabled, - xptr<char>& ticket_key_filename) + const ssl_user_config & sslMultCertSettings) { SSL_CTX * ctx; xptr<char> certpath; xptr<char> session_key_path; - ctx = ssl_context_enable_sni(SSLInitServerContext(params, cert, ca, key), lookup); + ctx = ssl_context_enable_sni(SSLInitServerContext(params, sslMultCertSettings), lookup); if (!ctx) { return false; } @@ -730,36 +934,36 @@ ssl_store_ssl_context( SSL_CTX_set_alpn_select_cb(ctx, SSLNetVConnection::select_next_protocol, NULL); #endif /* TS_USE_TLS_ALPN */ - certpath = Layout::relative_to(params->serverCertPathOnly, cert); + certpath = Layout::relative_to(params->serverCertPathOnly, sslMultCertSettings.cert); // Index this certificate by the specified IP(v6) address. If the address is "*", make it the default context. - if (addr) { - if (strcmp(addr, "*") == 0) { + if (sslMultCertSettings.addr) { + if (strcmp(sslMultCertSettings.addr, "*") == 0) { lookup->ssl_default = ctx; - lookup->insert(ctx, addr); + lookup->insert(ctx, sslMultCertSettings.addr); } else { IpEndpoint ep; - if (ats_ip_pton(addr, &ep) == 0) { - Debug("ssl", "mapping '%s' to certificate %s", (const char *)addr, (const char *)certpath); + if (ats_ip_pton(sslMultCertSettings.addr, &ep) == 0) { + Debug("ssl", "mapping '%s' to certificate %s", (const char *)sslMultCertSettings.addr, (const char *)certpath); lookup->insert(ctx, ep); } else { - Error("'%s' is not a valid IPv4 or IPv6 address", (const char *)addr); + Error("'%s' is not a valid IPv4 or IPv6 address", (const char *)sslMultCertSettings.addr); } } } #if defined(SSL_OP_NO_TICKET) // Session tickets are enabled by default. Disable if explicitly requested. - if (session_ticket_enabled == 0) { + if (sslMultCertSettings.session_ticket_enabled == 0) { SSL_CTX_set_options(ctx, SSL_OP_NO_TICKET); Debug("ssl", "ssl session ticket is disabled"); } #endif // 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)); + if (sslMultCertSettings.session_ticket_enabled != 0 && sslMultCertSettings.ticket_key_filename) { + xptr<char> ticket_key_path(Layout::relative_to(params->serverCertPathOnly, sslMultCertSettings.ticket_key_filename)); ssl_context_enable_tickets(ctx, ticket_key_path); } @@ -779,12 +983,8 @@ ssl_store_ssl_context( static bool ssl_extract_certificate( const matcher_line * line_info, - xptr<char>& addr, // IPv[64] address to match - xptr<char>& cert, // certificate - xptr<char>& ca, // CA public certificate - 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)] + ssl_user_config & sslMultCertSettings) + { for (int i = 0; i < MATCHER_MAX_TOKENS; ++i) { const char * label; @@ -798,31 +998,34 @@ ssl_extract_certificate( } if (strcasecmp(label, SSL_IP_TAG) == 0) { - addr = ats_strdup(value); + sslMultCertSettings.addr = ats_strdup(value); } if (strcasecmp(label, SSL_CERT_TAG) == 0) { - cert = ats_strdup(value); + sslMultCertSettings.cert = ats_strdup(value); } if (strcasecmp(label, SSL_CA_TAG) == 0) { - ca = ats_strdup(value); + sslMultCertSettings.ca = ats_strdup(value); } if (strcasecmp(label, SSL_PRIVATE_KEY_TAG) == 0) { - key = ats_strdup(value); + sslMultCertSettings.key = ats_strdup(value); } if (strcasecmp(label, SSL_SESSION_TICKET_ENABLED) == 0) { - session_ticket_enabled = atoi(value); + sslMultCertSettings.session_ticket_enabled = atoi(value); } if (strcasecmp(label, SSL_SESSION_TICKET_KEY_FILE_TAG) == 0) { - ticket_key_filename = ats_strdup(value); + sslMultCertSettings.ticket_key_filename = ats_strdup(value); } - } - if (!cert) { + if (strcasecmp(label, SSL_KEY_DIALOG) == 0) { + sslMultCertSettings.dialog = ats_strdup(value); + } + } + if (!sslMultCertSettings.cert) { Error("missing %s tag", SSL_CERT_TAG); return false; } @@ -875,12 +1078,7 @@ SSLParseCertificateConfiguration( } if (*line != '\0' && *line != '#') { - xptr<char> addr; - xptr<char> cert; - xptr<char> ca; - xptr<char> key; - int session_ticket_enabled = -1; - xptr<char> ticket_key_filename; + ssl_user_config sslMultiCertSettings; const char * errPtr; errPtr = parseConfigLine(line, &line_info, &sslCertTags); @@ -890,8 +1088,8 @@ SSLParseCertificateConfiguration( __func__, params->configFilePath, line_num, errPtr); REC_SignalError(errBuf, alarmAlready); } else { - 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)) { + if (ssl_extract_certificate(&line_info, sslMultiCertSettings)) { + if (!ssl_store_ssl_context(params, lookup, sslMultiCertSettings)) { Error("failed to load SSL certificate specification from %s line %u", params->configFilePath, line_num); } http://git-wip-us.apache.org/repos/asf/trafficserver/blob/e0c35aa8/proxy/config/ssl_multicert.config.default ---------------------------------------------------------------------- diff --git a/proxy/config/ssl_multicert.config.default b/proxy/config/ssl_multicert.config.default index be60994..b6dff3c 100644 --- a/proxy/config/ssl_multicert.config.default +++ b/proxy/config/ssl_multicert.config.default @@ -28,7 +28,7 @@ # square brackets if they have a port, eg, [::1]:80. # # ssl_key_name=FILENAME -# The name of the file containg the private key for this certificate. +# 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. # @@ -40,9 +40,16 @@ # The name of the file containing the TLS certificate. This is the # only field that is required to be present. # +# ssl_key_dialog=[builtin|exec:/path/to/program] +# Method used to provide a pass phrase for encrypted private keys. +# Two options are supported: builtin and exec +# builtin - Requests passphrase via stdin/stdout. Useful for debugging. +# exec: - Executes a program and uses the stdout output for the pass +# phrase. +# # Examples: # ssl_cert_name=foo.pem # dest_ip=* ssl_cert_name=bar.pem ssl_key_name=barKey.pem # dest_ip=209.131.48.79 ssl_cert_name=server.pem ssl_key_name=serverKey.pem # dest_ip=10.0.0.1:99 ssl_cert_name=port99.pem - +# ssl_cert_name=foo.pem ssl_key_dialog="exec:/usr/bin/mypass foo 'ba r'"
