Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion doc/reference/configuration/ssl_multicert.config.en.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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'"
3 changes: 2 additions & 1 deletion iocore/net/P_SSLUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
212 changes: 206 additions & 6 deletions iocore/net/SSLUtils.cc
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,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>
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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<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()
{
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -520,10 +706,17 @@ 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;
}
Expand Down Expand Up @@ -671,13 +864,14 @@ ssl_store_ssl_context(
xptr<char>& ca,
xptr<char>& key,
const int session_ticket_enabled,
xptr<char>& ticket_key_filename)
xptr<char>& ticket_key_filename,
xptr<char>& dialog)
{
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, cert, ca, key, dialog), lookup);
if (!ctx) {
return false;
}
Expand Down Expand Up @@ -736,7 +930,8 @@ ssl_extract_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)]
xptr<char>& ticket_key_filename, // session key file. [key_name (16Byte) + HMAC_secret (16Byte) + AES_key (16Byte)]
xptr<char>& dialog) // Private key dialog
{
for (int i = 0; i < MATCHER_MAX_TOKENS; ++i) {
const char * label;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -828,6 +1027,7 @@ SSLParseCertificateConfiguration(
xptr<char> key;
int session_ticket_enabled = -1;
xptr<char> ticket_key_filename;
xptr<char> dialog;
const char * errPtr;

errPtr = parseConfigLine(line, &line_info, &sslCertTags);
Expand All @@ -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);
}
Expand Down
10 changes: 9 additions & 1 deletion proxy/config/ssl_multicert.config.default
Original file line number Diff line number Diff line change
Expand Up @@ -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.
#
Expand All @@ -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'"