Hi all,
This patch introduces a new directive to mod_ssl that allows
administrators to specify a file from which the passphrase for an
encrypted private key will be read at startup.
This provides a simple, non-interactive mechanism for supplying
key passphrases in automated deployments while preserving existing behavior.
If the configured file is missing, unreadable, or contains an incorrect
passphrase, mod_ssl gracefully falls back to its normal key-loading
mechanism. When the directive is not configured, there is no change in
behavior.
Rationale:
- Enables automation scenarios where interactive passphrase entry is not
feasible.
- Maintains full backward compatibility: no behavior change unless the
directive is explicitly used.
Verified:
* Correct passphrase in file → httpd starts and loads key normally.
* Missing passphrase file → httpd fails to start.
* Invlid passphrase in file → httpd defaults to prompt for the passphrase.
* Both encrypted and unencrypted private keys tested.
Example configuration to use a password file:
SSLEngine on
SSLCertificateFile /etc/pki/tls/certs/cert.pem
SSLCertificateKeyFile /etc/pki/tls/private/key.pem
SSLCertificateKeyPasswordFile /etc/pki/tls/private/key.pem.pass
Patch attached as mod_ssl.patch
Documentation for the new directive will be submitted separately or
included once the directive name and semantics are agreed on.
Feedback welcome.
Regards,
Christian
Index: modules/ssl/mod_ssl.c
===================================================================
--- modules/ssl/mod_ssl.c (revision 1930040)
+++ modules/ssl/mod_ssl.c (working copy)
@@ -114,10 +114,13 @@
"SSL Server Certificate file "
"('/path/to/file' - PEM or DER encoded)")
SSL_CMD_SRV(CertificateKeyFile, TAKE1,
"SSL Server Private Key file "
"('/path/to/file' - PEM or DER encoded)")
+ SSL_CMD_SRV(CertificateKeyPasswordFile, TAKE1,
+ "SSL Server Private Key password file "
+ "('/path/to/file' - File containing the private keys password if any)")
SSL_CMD_SRV(CertificateChainFile, TAKE1,
"SSL Server CA Certificate Chain file "
"('/path/to/file' - PEM encoded)")
#ifdef HAVE_TLS_SESSION_TICKETS
SSL_CMD_SRV(SessionTicketKeyFile, TAKE1,
Index: modules/ssl/ssl_engine_config.c
===================================================================
--- modules/ssl/ssl_engine_config.c (revision 1930040)
+++ modules/ssl/ssl_engine_config.c (working copy)
@@ -200,10 +200,11 @@
mctx->pks = apr_pcalloc(p, sizeof(*mctx->pks));
mctx->pks->cert_files = apr_array_make(p, 3, sizeof(char *));
mctx->pks->key_files = apr_array_make(p, 3, sizeof(char *));
+ mctx->pks->key_password_files = apr_array_make(p, 3, sizeof(char *));
#ifdef HAVE_TLS_SESSION_TICKETS
mctx->ticket_key = apr_pcalloc(p, sizeof(*mctx->ticket_key));
#endif
}
@@ -333,10 +334,11 @@
{
modssl_ctx_cfg_merge(p, base, add, mrg);
cfgMergeArray(pks->cert_files);
cfgMergeArray(pks->key_files);
+ cfgMergeArray(pks->key_password_files);
cfgMergeString(pks->ca_name_path);
cfgMergeString(pks->ca_name_file);
#ifdef HAVE_TLS_SESSION_TICKETS
@@ -1079,13 +1081,34 @@
return err;
}
*(const char **)apr_array_push(sc->server->pks->key_files) = arg;
+ /* add a placeholder null for password; will be owerwritten if
+ * SSLCertificateKeyPasswordFile is set */
+
+ *(const char **)apr_array_push(sc->server->pks->key_password_files) = NULL;
return NULL;
}
+const char *ssl_cmd_SSLCertificateKeyPasswordFile(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;
+ }
+
+ const char **pw_files = (const char **)sc->server->pks->key_password_files->elts;
+ pw_files[sc->server->pks->key_password_files->nelts - 1] = arg;
+
+ return NULL;
+}
+
const char *ssl_cmd_SSLCertificateChainFile(cmd_parms *cmd,
void *dcfg,
const char *arg)
{
SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
@@ -2648,10 +2671,11 @@
if (ctx->pks) {
DMP_STRING("SSLCADNRequestFile", ctx->pks->ca_name_file);
DMP_STRING("SSLCADNRequestPath", ctx->pks->ca_name_path);
DMP_STRARR("SSLCertificateFile", ctx->pks->cert_files);
DMP_STRARR("SSLCertificateKeyFile", ctx->pks->key_files);
+ DMP_STRARR("SSLCertificateKeyPasswordFile", ctx->pks->key_password_files);
}
#ifdef HAVE_OCSP_STAPLING
DMP_ON_OFF("SSLUseStapling", ctx->stapling_enabled);
DMP_LONG( "SSLStaplingResponseTimeSkew", ctx->stapling_resptime_skew);
DMP_LONG( "SSLStaplingResponseMaxAge", ctx->stapling_resp_maxage);
Index: modules/ssl/ssl_engine_init.c
===================================================================
--- modules/ssl/ssl_engine_init.c (revision 1930040)
+++ modules/ssl/ssl_engine_init.c (working copy)
@@ -1628,16 +1628,82 @@
"%s server certificate does NOT include an ID "
"which matches the server name", key_id);
}
}
-/* prevent OpenSSL from showing its "Enter PEM pass phrase:" prompt */
-static int ssl_no_passwd_prompt_cb(char *buf, int size, int rwflag,
- void *userdata) {
- return 0;
+/* prevent OpenSSL from showing its "Enter PEM pass phrase:" prompt.
+ * if *userdata contains anything except nil, we use that as our
+ * SSLKeyPasswordFile path and fetch the contents of the file to
+ * use it as the passphrase for the private key.
+ * This callback is always executed even when the key is unencrypted.
+ * Thus we need to return 0 if the passphrase is not found.
+ */
+
+int ssl_keypass_cb(char *buf, int size, int rwflag, void *userdata)
+{
+ const char *pwfilepath = (const char *)userdata;
+ apr_pool_t *pool = NULL;
+ apr_file_t *pwfile = NULL;
+ apr_status_t rv;
+ char passbuf[4096];
+ int ret = 0;
+
+ if (!pwfilepath || !buf || size <= 0) {
+ return 0;
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL,
+ "Using private key password file: %s", pwfilepath);
+
+ rv = apr_pool_create(&pool, NULL);
+ if (rv != APR_SUCCESS) {
+ return 0;
+ }
+
+ rv = apr_file_open(&pwfile, pwfilepath,
+ APR_READ | APR_BUFFERED,
+ APR_OS_DEFAULT, pool);
+ if (rv != APR_SUCCESS) {
+ apr_pool_destroy(pool);
+ return 0;
+ }
+
+ /* Read up to passbuf-1 bytes */
+ {
+ apr_size_t nread = sizeof(passbuf) - 1;
+ rv = apr_file_read(pwfile, passbuf, &nread);
+
+ if (rv == APR_SUCCESS || rv == APR_EOF) {
+ /* Ensure NUL termination */
+ if (nread > 0 && nread < sizeof(passbuf))
+ passbuf[nread] = '\0';
+ else {
+ passbuf[0] = '\0';
+ nread = 0;
+ }
+
+ /* Strip newline and carriage return */
+ {
+ size_t len = strcspn(passbuf, "\r\n");
+ if (len >= (size_t)size) {
+ len = (size_t)size - 1;
+ }
+ memcpy(buf, passbuf, len);
+ buf[len] = '\0';
+ ret = (int)len;
+ }
+ }
+ }
+
+ apr_file_close(pwfile);
+ apr_pool_destroy(pool);
+
+ return ret;
}
+
+
/* SSL_CTX_use_PrivateKey_file() can fail either because the private
* key was encrypted, or due to a mismatch between an already-loaded
* cert and the key - a common misconfiguration - from calling
* X509_check_private_key(). This macro is passed the last error code
* off the OpenSSL stack and evaluates to true only for the first
@@ -1663,30 +1729,28 @@
apr_pool_t *ptemp,
modssl_ctx_t *mctx,
apr_array_header_t *pphrases)
{
SSLModConfigRec *mc = myModConfig(s);
- const char *vhost_id = mctx->sc->vhost_id, *key_id, *certfile, *keyfile;
+ const char *vhost_id = mctx->sc->vhost_id, *key_id, *certfile, *keyfile, *keypasswordfile;
int i;
EVP_PKEY *pkey;
int custom_dh_done = 0;
#ifdef HAVE_ECC
EC_GROUP *ecgroup = NULL;
int curve_nid = 0;
#endif
-
/* no OpenSSL default prompts for any of the SSL_CTX_use_* calls, please */
- SSL_CTX_set_default_passwd_cb(mctx->ssl_ctx, ssl_no_passwd_prompt_cb);
+ SSL_CTX_set_default_passwd_cb(mctx->ssl_ctx, ssl_keypass_cb);
/* Iterate over the SSLCertificateFile array */
for (i = 0; (i < mctx->pks->cert_files->nelts) &&
(certfile = APR_ARRAY_IDX(mctx->pks->cert_files, i,
const char *));
i++) {
X509 *cert = NULL;
const char *engine_certfile = NULL;
-
key_id = apr_psprintf(ptemp, "%s:%d", vhost_id, i);
ERR_clear_error();
/* first the certificate (public key) */
@@ -1710,12 +1774,20 @@
" check %s", key_id, certfile);
ssl_log_ssl_error(SSLLOG_MARK, APLOG_EMERG, s);
return APR_EGENERAL;
}
}
+
+ /* and second, the private key */
+ keypasswordfile = NULL;
+ if (mctx->pks->key_password_files &&
+ (i < mctx->pks->key_password_files->nelts)) {
+ keypasswordfile = APR_ARRAY_IDX(mctx->pks->key_password_files, i, const char *);
+ }
- /* and second, the private key */
+ SSL_CTX_set_default_passwd_cb_userdata(mctx->ssl_ctx, (void *)keypasswordfile);
+
if (i < mctx->pks->key_files->nelts) {
keyfile = APR_ARRAY_IDX(mctx->pks->key_files, i, const char *);
} else {
keyfile = certfile;
}
@@ -1761,12 +1833,13 @@
ssl_asn1_t *asn1;
const unsigned char *ptr;
ERR_clear_error();
- /* perhaps it's an encrypted private key, so try again */
- ssl_load_encrypted_pkey(s, ptemp, i, keyfile, &pphrases);
+ /* perhaps the private key is encrypted, but the was wrong or no password
+ * was used, default to ask for the passphrase */
+ ssl_load_encrypted_pkey(s, ptemp, i, keyfile, &pphrases);
if (!(asn1 = ssl_asn1_table_get(mc->retained->privkeys, key_id)) ||
!(ptr = asn1->cpData) ||
!(pkey = d2i_AutoPrivateKey(NULL, &ptr, asn1->nData)) ||
(SSL_CTX_use_PrivateKey(mctx->ssl_ctx, pkey) < 1)) {
Index: modules/ssl/ssl_private.h
===================================================================
--- modules/ssl/ssl_private.h (revision 1930040)
+++ modules/ssl/ssl_private.h (working copy)
@@ -744,10 +744,11 @@
* a given vhost */
typedef struct {
/* Lists of configured certs and keys for this server */
apr_array_header_t *cert_files;
apr_array_header_t *key_files;
+ apr_array_header_t *key_password_files;
/** Certificates which specify the set of CA names which should be
* sent in the CertificateRequest message: */
const char *ca_name_path;
const char *ca_name_file;
@@ -952,10 +953,11 @@
const char *ssl_cmd_SSLECHKeyDir(cmd_parms *cmd, void *dcfg, const char *arg);
#endif
const char *ssl_cmd_SSLCipherSuite(cmd_parms *, void *, const char *, const char *);
const char *ssl_cmd_SSLCertificateFile(cmd_parms *, void *, const char *);
const char *ssl_cmd_SSLCertificateKeyFile(cmd_parms *, void *, const char *);
+const char *ssl_cmd_SSLCertificateKeyPasswordFile(cmd_parms *, void *, const char *);
const char *ssl_cmd_SSLCertificateChainFile(cmd_parms *, void *, const char *);
const char *ssl_cmd_SSLCACertificatePath(cmd_parms *, void *, const char *);
const char *ssl_cmd_SSLCACertificateFile(cmd_parms *, void *, const char *);
const char *ssl_cmd_SSLCADNRequestPath(cmd_parms *, void *, const char *);
const char *ssl_cmd_SSLCADNRequestFile(cmd_parms *, void *, const char *);