From: Jan Schreiber Subject: Re: httpd: support encrypted tls server keys To: Christian Schulte , tech@openbsd.org Date: Sun, 15 Feb 2026 14:45:22 +0000 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        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 }, @@ -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);