diff --git a/doc/reference/configuration/ssl_multicert.config.en.rst b/doc/reference/configuration/ssl_multicert.config.en.rst index 331e47e862a..53ae61ebe82 100644 --- a/doc/reference/configuration/ssl_multicert.config.en.rst +++ b/doc/reference/configuration/ssl_multicert.config.en.rst @@ -75,6 +75,22 @@ ssl_ca_name=FILENAME the certificate chain. `FILENAME` is resolved relative to the :ts:cv:`proxy.config.ssl.CA.cert.path` configuration variable. +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. + 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 @@ -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 key. + +:: + + ssl_cert_name=server.pem ssl_key_dialog="exec:/usr/bin/mypass foo" + ssl_cert_name=server.pem ssl_key_dialog="exec:/usr/bin/mypass foo 'ba r'" diff --git a/iocore/net/P_SSLUtils.h b/iocore/net/P_SSLUtils.h index 5ed2d826ec7..a08a5655046 100644 --- a/iocore/net/P_SSLUtils.h +++ b/iocore/net/P_SSLUtils.h @@ -54,7 +54,8 @@ SSLInitServerContext( const SSLConfigParams * param, const char * serverCertPtr, const char * serverCaCertPtr, - const char * serverKeyPtr); + const char * serverKeyPtr, + const char * serverDialog); // 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 833ac7de759..5d8e7c345f1 100644 --- a/iocore/net/SSLUtils.cc +++ b/iocore/net/SSLUtils.cc @@ -30,6 +30,8 @@ #include #include #include +#include +#include #if HAVE_OPENSSL_EVP_H #include @@ -54,6 +56,7 @@ #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" #ifndef evp_md_func #ifdef OPENSSL_NO_SHA256 @@ -63,6 +66,11 @@ #endif #endif +// 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 + #if (OPENSSL_VERSION_NUMBER >= 0x10000000L) // openssl returns a const SSL_METHOD typedef const SSL_METHOD * ink_ssl_method_t; #else @@ -293,6 +301,160 @@ ssl_context_enable_tickets(SSL_CTX * ctx, const char * ticket_key_path) #endif /* TS_USE_TLS_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() { @@ -407,7 +569,8 @@ SSLInitServerContext( const SSLConfigParams * params, const char * serverCertPtr, const char * serverCaCertPtr, - const char * serverKeyPtr) + const char * serverKeyPtr, + const char * serverDialog) { int session_id_context; int server_verify_client; @@ -435,6 +598,29 @@ SSLInitServerContext( #endif SSL_CTX_set_quiet_shutdown(ctx, 1); + // pass phrase dialog configuration + passphrase_cb_userdata ud(params, serverDialog, serverCertPtr, serverKeyPtr); + + if (serverDialog) { + pem_password_cb * passwd_cb = NULL; + if (strncmp(serverDialog, "exec:", 5) == 0) { + ud._serverDialog = &serverDialog[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(serverDialog, "builtin") == 0) { + passwd_cb = ssl_private_key_passphrase_callback_builtin; + } else { // unknown config + SSLError("unknown "SSL_KEY_DIALOG" configuration value '%s'", serverDialog); + 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. @@ -520,10 +706,17 @@ 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; } @@ -671,13 +864,14 @@ ssl_store_ssl_context( xptr& ca, xptr& key, const int session_ticket_enabled, - xptr& ticket_key_filename) + xptr& ticket_key_filename, + xptr& dialog) { 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, cert, ca, key, dialog), lookup); if (!ctx) { return false; } @@ -736,7 +930,8 @@ ssl_extract_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)] + xptr& ticket_key_filename, // session key file. [key_name (16Byte) + HMAC_secret (16Byte) + AES_key (16Byte)] + xptr& dialog) // Private key dialog { for (int i = 0; i < MATCHER_MAX_TOKENS; ++i) { const char * label; @@ -772,6 +967,10 @@ ssl_extract_certificate( if (strcasecmp(label, SSL_SESSION_TICKET_KEY_FILE_TAG) == 0) { ticket_key_filename = ats_strdup(value); } + + if (strcasecmp(label, SSL_KEY_DIALOG) == 0) { + dialog = ats_strdup(value); + } } if (!cert) { @@ -828,6 +1027,7 @@ SSLParseCertificateConfiguration( xptr key; int session_ticket_enabled = -1; xptr ticket_key_filename; + xptr dialog; const char * errPtr; errPtr = parseConfigLine(line, &line_info, &sslCertTags); @@ -837,8 +1037,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, addr, cert, ca, key, session_ticket_enabled, ticket_key_filename,dialog)) { + if (!ssl_store_ssl_context(params, lookup, addr, cert, ca, key, session_ticket_enabled, ticket_key_filename,dialog)) { 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..69a90ee049e 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,17 @@ # 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'"