Index | Thread | Search

From:
Jan Schreiber <jes@posteo.de>
Subject:
Re: httpd: support encrypted tls server keys
To:
Christian Schulte <cs@schulte.it>, tech@openbsd.org
Date:
Sun, 15 Feb 2026 14:45:22 +0000

Download raw body.

Thread

On 2/14/26 20:10, Christian Schulte wrote:
> Am 14.02.2026 um 15:34 schrieb Jan Schreiber:
>> Hello,
>>
>> this diff enables using encrypted tls server keys with httpd.
>> The heavy lifting is already done in tls_load_file(3). I'm only passing the
>> needed password and added the config settings plus a regression test.
>>
>> The additions to the man page are, with the exception of the additional
>> word 'optional', the same as in the relayd man page.
>>
>> Jan
snip
> That password would reside in cleartext in /etc/httpd.conf, which may be
> world readable.
>
> $ ls -lah /etc/httpd.conf
> -rw-r--r--  1 root  wheel   542B Feb 11 03:30 /etc/httpd.conf
>
> Seems check_file_secrecy in parse.y is never called with secret set so
> does never perform any checks. You end up with a cleartext password in a
> config file nothing prevents from being world readable. Enabling
> check_file_secrecy in parse.y is required in addition, at least.
>
Thank you for taking a look, new diff blow.
While there I notices relayd also never calls check_file_secrecy.
So the ca key password will also be visible in the relayd.conf

If it's the right way I'll send an additional diff for relayd in another 
thread.


diff --git regress/usr.sbin/httpd/tests/Httpd.pm 
regress/usr.sbin/httpd/tests/Httpd.pm
index 53fed72040c..66f5cb7ba73 100644
--- regress/usr.sbin/httpd/tests/Httpd.pm
+++ regress/usr.sbin/httpd/tests/Httpd.pm
@@ -71,7 +71,12 @@ sub new {
      if ($self->{listentls}) {
          print $fh "\n";
          print $fh "\ttls certificate \"".$args{chroot}."/server.crt\"\n";
-        print $fh "\ttls key \"".$args{chroot}."/server.key\"";
+        my $tlskey = "\ttls key \"".$args{chroot};
+        if ($self->{tlskeypw}) {
+        print $fh ${tlskey}."/server.enc.key\" password \"password1\"";
+        } else {
+        print $fh ${tlskey}."/server.key\"";
+        }
          $self->{verifytls}
          and print $fh "\n\ttls client ca \"".$args{chroot}."/ca.crt\"";
      }
diff --git regress/usr.sbin/httpd/tests/Makefile 
regress/usr.sbin/httpd/tests/Makefile
index 3afb588ce93..f1981257cdf 100644
--- regress/usr.sbin/httpd/tests/Makefile
+++ regress/usr.sbin/httpd/tests/Makefile
@@ -82,7 +82,11 @@ server.crt: ca.crt server.req
  client.crt: ca.crt client.req
      openssl x509 -CAcreateserial -CAkey ca.key -CA ca.crt -req -in 
client.req -out $@

+server_enc.key:
+    openssl pkcs8 -topk8 -in server.key -out server.enc.key -passout 
pass:password1
+
  ${REGRESS_TARGETS:M*tls*} ${REGRESS_TARGETS:M*https*}: server.crt 
client.crt
+${REGRESS_TARGETS:M*tls-pw*} ${REGRESS_TARGETS:M*https*}: server.crt 
client.crt server_enc.key

  # make perl syntax check for all args files

diff --git regress/usr.sbin/httpd/tests/args-tls-pw.pl 
regress/usr.sbin/httpd/tests/args-tls-pw.pl
new file mode 100644
index 00000000000..f5d512d726e
--- /dev/null
+++ regress/usr.sbin/httpd/tests/args-tls-pw.pl
@@ -0,0 +1,19 @@
+# test https connection
+
+use strict;
+use warnings;
+
+our %args = (
+    client => {
+    tls => 1,
+    loggrep => 'Issuer.*/OU=ca/',
+    },
+    httpd => {
+    listentls => 1,
+    tlskeypw => 1,
+    },
+    len => 512,
+    md5 => path_md5("512")
+);
+
+1;
diff --git usr.sbin/httpd/httpd.conf.5 usr.sbin/httpd/httpd.conf.5
index 3053709a359..745f7573bf6 100644
--- usr.sbin/httpd/httpd.conf.5
+++ usr.sbin/httpd/httpd.conf.5
@@ -709,7 +709,7 @@ in order of preference.
  The special value of "default" will use the default curves; see
  .Xr tls_config_set_ecdhecurves 3
  for further details.
-.It Ic key Ar file
+.It Ic key Ar file Ic password Ar password
  Specify the private key to use for this server.
  The
  .Ar file
@@ -719,6 +719,9 @@ root directory of
  .Nm httpd .
  The default is
  .Pa /etc/ssl/private/server.key .
+The optional
+.Ar password
+argument will specify the password to decrypt the key.
  .It Ic ocsp Ar file
  Specify an OCSP response to be stapled during TLS handshakes
  with this server.
diff --git usr.sbin/httpd/httpd.h usr.sbin/httpd/httpd.h
index baf11d1b523..71fd829be6b 100644
--- usr.sbin/httpd/httpd.h
+++ usr.sbin/httpd/httpd.h
@@ -61,6 +61,7 @@
  #define HTTPD_LOGVIS        VIS_NL|VIS_TAB|VIS_CSTYLE
  #define HTTPD_TLS_CERT        "/etc/ssl/server.crt"
  #define HTTPD_TLS_KEY        "/etc/ssl/private/server.key"
+#define HTTPD_TLS_KEY_PASS    NULL
  #define HTTPD_TLS_CONFIG_MAX    511
  #define HTTPD_TLS_CIPHERS    "compat"
  #define HTTPD_TLS_DHE_PARAMS    "none"
@@ -508,6 +509,7 @@ struct server_config {
      uint8_t            *tls_key;
      size_t             tls_key_len;
      char            *tls_key_file;
+    char            *tls_key_pass;
      uint32_t         tls_protocols;
      uint8_t            *tls_ocsp_staple;
      size_t             tls_ocsp_staple_len;
diff --git usr.sbin/httpd/parse.y usr.sbin/httpd/parse.y
index 326b249662f..5b55dcb289d 100644
--- usr.sbin/httpd/parse.y
+++ usr.sbin/httpd/parse.y
@@ -141,7 +141,7 @@ typedef struct {
  %token    TIMEOUT TLS TYPE TYPES HSTS MAXAGE SUBDOMAINS DEFAULT 
PRELOAD REQUEST
  %token    ERROR INCLUDE AUTHENTICATE WITH BLOCK DROP RETURN PASS REWRITE
  %token    CA CLIENT CRL OPTIONAL PARAM FORWARDED FOUND NOT
-%token    ERRDOCS GZIPSTATIC BANNER
+%token    ERRDOCS GZIPSTATIC BANNER PASSWORD
  %token    <v.string>    STRING
  %token  <v.number>    NUMBER
  %type    <v.port>    port
@@ -870,7 +870,7 @@ tlsopts        : CERTIFICATE STRING        {
                  fatal("out of memory");
              free($2);
          }
-        | KEY STRING            {
+        | KEY STRING tlskeyopt        {
              free(srv_conf->tls_key_file);
              if ((srv_conf->tls_key_file = strdup($2)) == NULL)
                  fatal("out of memory");
@@ -947,7 +947,14 @@ tlsopts        : CERTIFICATE STRING        {
              srv_conf->tls_ticket_lifetime = 0;
          }
          ;
-
+tlskeyopt    :
+        | tlskeyopt PASSWORD STRING    {
+            free(srv_conf->tls_key_pass);
+            if ((srv_conf->tls_key_pass = strdup($3)) == NULL)
+                fatal("out of memory");
+            free($3);
+        }
+        ;
  tlsclientopt    : /* empty */
          | tlsclientopt CRL STRING    {
              srv_conf->tls_flags = TLSFLAG_CRL;
@@ -1496,6 +1503,7 @@ lookup(char *s)
          { "optional",        OPTIONAL },
          { "param",        PARAM },
          { "pass",        PASS },
+        { "password",        PASSWORD },
          { "port",        PORT },
          { "prefork",        PREFORK },
          { "preload",        PRELOAD },
@@ -1885,7 +1893,7 @@ parse_config(const char *filename, struct httpd 
*x_conf)

      errors = 0;

-    if ((file = pushfile(filename, 0)) == NULL)
+    if ((file = pushfile(filename, 1)) == NULL)
          return (-1);

      topfile = file;
diff --git usr.sbin/httpd/server.c usr.sbin/httpd/server.c
index a38cf018d81..3bc22a62f6a 100644
--- usr.sbin/httpd/server.c
+++ usr.sbin/httpd/server.c
@@ -162,9 +162,8 @@ server_tls_load_keypair(struct server *srv)
      log_debug("%s: using certificate %s", __func__,
          srv->srv_conf.tls_cert_file);

-    /* XXX allow to specify password for encrypted key */
      if ((srv->srv_conf.tls_key = tls_load_file(srv->srv_conf.tls_key_file,
-        &srv->srv_conf.tls_key_len, NULL)) == NULL)
+        &srv->srv_conf.tls_key_len, srv->srv_conf.tls_key_pass)) == NULL)
          return (-1);
      log_debug("%s: using private key %s", __func__,
          srv->srv_conf.tls_key_file);