Index | Thread | Search

From:
Christian Schulte <cs@schulte.it>
Subject:
Re: httpd: support encrypted tls server keys
To:
Jan Schreiber <jes@posteo.de>, tech@openbsd.org
Date:
Sat, 14 Feb 2026 20:10:38 +0100

Download raw body.

Thread
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
> 
> 
> 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..856f95dbf95 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 },
> 
> 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);
> 
> 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.

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.

-- 
Christian