Download raw body.
httpd: support encrypted tls server keys
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
httpd: support encrypted tls server keys