diff --git a/doc/reference/configuration/ssl_multicert.config.en.rst b/doc/reference/configuration/ssl_multicert.config.en.rst index 331e47e862a..17a859e9731 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 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 + +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'" \ No newline at end of file diff --git a/iocore/net/P_SSLUtils.h b/iocore/net/P_SSLUtils.h index 5ed2d826ec7..e5178242de5 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); diff --git a/iocore/net/SSLUtils.cc b/iocore/net/SSLUtils.cc index 474f4563e9b..a1fa1083b7a 100644 --- a/iocore/net/SSLUtils.cc +++ b/iocore/net/SSLUtils.cc @@ -31,6 +31,8 @@ #include #include #include +#include +#include #if HAVE_OPENSSL_EVP_H #include @@ -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 addr; // dest_ip - IPv[64] address to match + xptr cert; // ssl_cert_name - certificate + xptr ca; // ssl_ca_name - CA public certificate + xptr key; // ssl_key_name - Private key + int session_ticket_enabled; // ssl_ticket_enabled - session ticket enabled + xptr ticket_key_filename; // ticket_key_name - session key file. [key_name (16Byte) + HMAC_secret (16Byte) + AES_key (16Byte)] + xptr 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,160 @@ ssl_context_enable_tickets(SSL_CTX * ctx, const char * ticket_key_path) #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 (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 (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 +589,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 +617,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 +659,22 @@ SSLInitServerContext( } // Now, load any additional certificate chains specified in this entry. - if (serverCaCertPtr) { - xptr completeServerCertChainPath(Layout::relative_to(params->serverCertPathOnly, serverCaCertPtr)); + if (sslMultCertSettings.ca) { + xptr 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 == NULL) { // 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 completeServerKeyPath(Layout::get()->relative_to(params->serverKeyPathOnly, serverKeyPtr)); + xptr 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 +725,16 @@ SSLInitServerContext( goto fail; } } - +#define SSL_CLEAR_PW_REFERENCES(UD,CTX) { \ + memset(static_cast(&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; } @@ -696,18 +897,13 @@ static bool ssl_store_ssl_context( const SSLConfigParams * params, SSLCertLookup * lookup, - xptr& addr, - xptr& cert, - xptr& ca, - xptr& key, - const int session_ticket_enabled, - xptr& ticket_key_filename) + const ssl_user_config & sslMultCertSettings) { SSL_CTX * ctx; xptr certpath; xptr 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; } @@ -722,36 +918,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 ticket_key_path(Layout::relative_to(params->serverCertPathOnly, ticket_key_filename)); + if (sslMultCertSettings.session_ticket_enabled != 0 && sslMultCertSettings.ticket_key_filename) { + xptr ticket_key_path(Layout::relative_to(params->serverCertPathOnly, sslMultCertSettings.ticket_key_filename)); ssl_context_enable_tickets(ctx, ticket_key_path); } @@ -767,12 +963,8 @@ ssl_store_ssl_context( static bool ssl_extract_certificate( const matcher_line * line_info, - xptr& addr, // IPv[64] address to match - xptr& cert, // certificate - xptr& ca, // CA public certificate - xptr& key, // Private key - int& session_ticket_enabled, // session ticket enabled - xptr& 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; @@ -786,31 +978,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; } @@ -863,12 +1058,7 @@ SSLParseCertificateConfiguration( } if (*line != '\0' && *line != '#') { - xptr addr; - xptr cert; - xptr ca; - xptr key; - int session_ticket_enabled = -1; - xptr ticket_key_filename; + ssl_user_config sslMultiCertSettings; const char * errPtr; errPtr = parseConfigLine(line, &line_info, &sslCertTags); @@ -878,8 +1068,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); } diff --git a/proxy/config/ssl_multicert.config.default b/proxy/config/ssl_multicert.config.default index be6099459b7..b6dff3c4f7d 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'"