From: Christian Schulte Subject: Re: httpd: support encrypted tls server keys To: Jan Schreiber , tech@openbsd.org Date: Sat, 14 Feb 2026 20:10:38 +0100 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       STRING >  %token       NUMBER >  %type          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