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 *);

Reply via email to