[Solved] Client Authentication with Certificate not fully working
Added by Roko almost 4 years ago
Hi,
we are planning to use lighttpd in an embedded linux environment on ARMv7 as a proxy.
One requirement we have is the certificate-based client authentication where we tried
the planned functionality on a PC with gentoo linux first.
We discovered a problem where certificate-based client authentication only works partially.
Tested on System:
Linux 5.3.12-gentoo x86_64 Intel(R) Core(TM) i5-6600 CPU @ 3.30GHz GenuineIntel GNU/Linux
Affected versions of lighttpd:
All versions beginning with 1.4.56-rc1 up to current repository state (tested with 1.4.55, 1.4.56-rc1, 1.4.59, latest repository 2021-06-21) NOTE: 1.4.55 doesn't have the problem
Configuration
config { var.CWD = "/home/rko/test/20210618_Reverse_Proxy_lighttpd/lighttpd" var.PID = 32483 var.base_dir = "/home/rko/test/20210618_Reverse_Proxy_lighttpd/lighttpd" var.log_root = "/home/rko/test/20210618_Reverse_Proxy_lighttpd/lighttpd/log" var.server_root = "/home/rko/test/20210618_Reverse_Proxy_lighttpd/lighttpd/srv" var.state_dir = "/home/rko/test/20210618_Reverse_Proxy_lighttpd/lighttpd/run" var.conf_dir = "/home/rko/test/20210618_Reverse_Proxy_lighttpd/lighttpd/conf" var.cache_dir = "/home/rko/test/20210618_Reverse_Proxy_lighttpd/lighttpd/cache" var.socket_dir = "/home/rko/test/20210618_Reverse_Proxy_lighttpd/lighttpd/sockets" var.vhosts_dir = "/home/rko/test/20210618_Reverse_Proxy_lighttpd/lighttpd/srv/vhosts" server.port = 8001 server.document-root = "/home/rko/test/20210618_Reverse_Proxy_lighttpd/lighttpd/srv/htdocs" server.pid-file = "/home/rko/test/20210618_Reverse_Proxy_lighttpd/lighttpd/run/lighttpd.pid" server.errorlog = "/home/rko/test/20210618_Reverse_Proxy_lighttpd/lighttpd/log/error.log" server.network-backend = "sendfile" server.max-fds = 2048 server.stat-cache-engine = "simple" server.max-connections = 1024 server.feature-flags = ( "server.h2proto" => "disable", "server.h2c" => "disable", # 2 ) server.modules = ("mod_proxy", "mod_openssl") debug.log-request-handling = "enable" debug.log-request-header = "enable" debug.log-request-header-on-error = "enable" debug.log-response-header = "enable" debug.log-file-not-found = "enable" debug.log-state-handling = "enable" debug.log-condition-handling = "enable" ssl.engine = "enable" ssl.pemfile = "/home/rko/test/20210618_Reverse_Proxy_lighttpd/lighttpd/conf/https.pem" ssl.privkey = "/home/rko/test/20210618_Reverse_Proxy_lighttpd/lighttpd/conf/https.key" ssl.ca-file = "/home/rko/test/20210618_Reverse_Proxy_lighttpd/lighttpd/conf/ca-root.pem" ssl.verifyclient.activate = "enable" ssl.verifyclient.enforce = "disable" ssl.verifyclient.depth = 2 $HTTP["url"] =~ "(^/http)" { # block 1 proxy.forwarded = ( "for" => 1, "proto" => 1, "remote_user" => 1, # 3 ) proxy.server = ( "" => ( "HTTP" => ( "host" => "192.168.1.5", "port" => 8888, # 2 ), ), ) } # end of $HTTP["url"] =~ "(^/http)" $HTTP["url"] =~ "(^/api)" { # block 2 proxy.server = ( "" => ( "API" => ( "host" => "192.168.1.5", "port" => 8889, # 2 ), ), ) } # end of $HTTP["url"] =~ "(^/api)" $HTTP["url"] =~ "(^/ws)" { # block 3 proxy.server = ( "" => ( "Websocket" => ( "host" => "192.168.1.5", "port" => 8887, # 2 ), ), ) proxy.header = ( "upgrade" => "enable", ) } # end of $HTTP["url"] =~ "(^/ws)" }
Clients used
Firefox 88.0.1 (64-Bit) Chrome 91.0.4472.101 (Official Build, ungoogled-chromium) (64-bit)
Explanation of problem:
Given is a server certificate and private key (https.pem + https.key - see config above) for server authentication by the client - certificate is signed by an internal CA (not configured or used on server side).
For certificate-based client authentication an appropriate CA is configured on server side (ca-root.pem - see config above).
It is quite common to use different CAs for these two purposes (although one could use a single one).
If we try this usual use case that the client's certificate is signed by a different CA than the server certificate, we get the following error in the error.log on the server side:
2021-06-21 11:06:11: connections.c.1414) state at enter 7 read 2021-06-21 11:06:11: connections.c.1060) state for fd:7 id:0 read 2021-06-21 11:06:11: mod_openssl.c.1095) SSL: building cert chain for TLS server name localhost: error:00000000:lib(0):func(0):reason(0) 2021-06-21 11:06:11: mod_openssl.c.3095) SSL: 1 error:1417A179:SSL routines:tls_post_process_client_hello:cert cb error 2021-06-21 11:06:11: connections.c.1060) state for fd:7 id:0 error 2021-06-21 11:06:11: connections.c.191) shutdown for fd 7 2021-06-21 11:06:11: connections.c.1060) state for fd:7 id:0 close 2021-06-21 11:06:11: connections.c.1421) state at exit: 7 close
In this case both browsers show SSL_ERROR_INTERNAL_ERROR_ALERT.
If we use a single CA for both authentication sides then it works!
We think that this might be a bug.
Many greetings
Roman
Replies (4)
RE: Client Authentication with Certificate not fully working - Added by gstrauss almost 4 years ago
We think that this might be a bug.
It does not sound like you read the lighttpd TLS docs. Instead, it sounds like you should have been using ssl.ca-dn-file
since lighttpd 1.4.46, and your misconfiguration may be sending larger-than-necessary ServerHello responses.
Please carefully read the lighttpd TLS docs. Specifically, it is strongly recommended that the certificate chain of intermediates be included in ssl.pemfile
, ordered from the leaf cert to the last cert prior to the root cert. This has been supported in ssl.pemfile
in $SERVER["socket"]
for a very long time, and is supported in ssl.pemfile
in $HTTP["host"] (and everywhere else that is valid) since lighttpd 1.4.56.
In your case, I think that ssl.ca-file
should contain only the root cert for the CA which signs the client certificates, and the CA cert for the client certificate signer. If there is an intermediate which signs the certificate you expect from the client, include that cert in both ssl.ca-file
and ssl.ca-dn-file
so that lighttpd will send the DN for the intermediate in the ServerHello when the server sends the certificate request to the client.
BTW, in lighttpd 1.4.60 (not yet released), ssl.ca-file
has been renamed ssl.verifyclient.ca-file
, and ssl.ca-dn-file
has been renamed to ssl.verifyclient.ca-dn-file
to better indicate when these directives are intended to be used. (ssl.ca-file
and ssl.ca-dn-file
are still supported, and are remapped to the new names)
The full certificate chain for ssl.pemfile
should be in ssl.pemfile
and should not rely on ssl.ca-file
at all, as the server cert can be issued by an entirely separate chain of CAs than what is used for the client certificates, and you do not want someone to generate a certificate on the (external) CA, different from your internal CA used to sign client certs, and then accidentally allow access to your server via a certificate issued from the external CA. If you have not been using ssl.ca-dn-file
, then you might be exposed due to your misconfiguration. It would be much better to separate these, putting the full chain in ssl.pemfile
, and leaving ssl.ca-file
(ssl.verifyclient.ca-file
) for your client certificate trusted CA signers and root.
RE: Client Authentication with Certificate not fully working - Added by Roko almost 4 years ago
Thank you for your prompt answer. Well we read the docs, not only once.
We have a rather simple setup here:
Server CA signs https.pem
Clientauth CA signs Client cert(s)
No intermediate certificates at all here.
Nevertheless the trigger in the right direction was
...it is strongly recommended that the certificate chain of intermediates be included in ssl.pemfile, ordered from the leaf cert to the last cert prior to the root cert...
The lighttpd TLS docs state for
ssl.pemfile
: path to the PEM file certificate chain
So the Server CA was missing...
After creation of a new file with https.pem + https.key + ca.pem for ssl.pemfile
and deletion of ssl.privkey
from config it works now.
- Until 1.4.55 it works without the CA so it was not clear to us that there is a change in the configuration needed. We used a solution with libmicrohttpd in the past where we also didn't need to use the Server CA in the configuration.
- :
If there is an intermediate which signs the certificate you expect from the client, include that cert in both
ssl.ca-file
andssl.ca-dn-file
so that lighttpd will send the DN for the intermediate in the ServerHello when the server sends the certificate request to the client.
Hm sounds strange.
If you have not been using
ssl.ca-dn-file
, then you might be exposed due to your misconfiguration. It would be much better to separate these, putting the full chain inssl.pemfile
, and leavingssl.ca-file
(ssl.verifyclient.ca-file
) for your client certificate trusted CA signers and root.
For what is ssl.ca-dn-file
needed/useful? The explanation path to file for certificate authorities (CA) from which client should select client certs (if needed)
rather confuses us. Even after reading your answer it's not clear to us.
Do we need ssl.ca-dn-file
even without any intermediate certs? What about larger-than-necessary ServerHello responses?
Thanks very much
Roman
RE: Client Authentication with Certificate not fully working - Added by gstrauss almost 4 years ago
The lighttpd TLS docs state for ssl.pemfile: path to the PEM file certificate chain
Yes, there is an important difference between "certificate" and "certificate chain"
There is also (potentially) a difference between ssl.pemfile
certificate chain and the client certificate chains.
For what is ssl.ca-dn-file needed/useful?
ssl.ca-dn-file
is needed when the CA which signs the client certificate certs is not a root CA, e.g. if a distinct root CA signs the CA cert which signs the client certs.
Do we need ssl.ca-dn-file even without any intermediate certs? What about larger-than-necessary ServerHello responses?
No, not needed if there are no intermediate certs used to sign client certificates. The ssl.ca-dn-file
is to specify to the client that the client certificate selection should use a certificate signed by (issued by) the specified DN (distinguished name of the subject in the cert in ssl.ca-dn-file
), and that any other issuer (including trusted parent chain of the cert of the client certificate signing CA in ssl.ca-dn-file
) will not be accepted as a client cert. I think I misunderstood what you had written earlier, and thought that you might have an intermediate CA signing client certificates. If that is not the case, then you do not need ssl.ca-dn-file
.
Larger than necessary ServerHello might occur if ssl.ca-dn-file
is not specified and ssl.ca-file
contains many trusted certs, e.g. including different CAs for ssl.pemfile
, which are not what you desire for client certificate. If you have excess trusted CA certs in ssl.ca-file
and you do not specify ssl.ca-dn-file
, then you might be vulnerable to accepting client certificates signed by those other, different CAs. After all, ssl.ca-file
contains certs for CAs that you are telling lighttpd to trust.
If there is an intermediate which signs the certificate you expect from the client, include that cert in both ssl.ca-file and ssl.ca-dn-file so that lighttpd will send the DN for the intermediate in the ServerHello when the server sends the certificate request to the client.
Hm sounds strange.
ssl.ca-file
(ssl.verifyclient.ca-file
) should contain the certificate chain (including root CA) of the CA used to sign (issue) client certificates.ssl.ca-dn-file
(ssl.verifyclient.ca-dn-file
) should contain only the certificate of the CA used to sign (issue) client certificates.
ssl.ca-dn-file
(ssl.verifyclient.ca-dn-file
) is used in ServerHello when sending a client certificate request, telling the client to select a certificate signed by (issued by) the DN of the subject of the cert in ssl.ca-dn-file
ssl.ca-file
(ssl.verifyclient.ca-file
) is used to validate the client certificate presented by the client in response to ServerHello containing a client certificate request.
After creation of a new file with https.pem + https.key + ca.pem for ssl.pemfile and deletion of ssl.privkey from config it works now.
As an aside, ssl.privkey
is separate from the discussion above, and since lighttpd 1.4.53 may be separate from ssl.pemfile
. ssl.privkey
is not relevant to your issue with client certificate verification.
RE: Client Authentication with Certificate not fully working - Added by Roko almost 4 years ago
Thank you for this great explanation, this clarifies it!