Crt-list verify not working

Hi.

I’m trying to client certificate authenticate for an specific domain on my proxy.
However, it doesn’t seem to work as expected. I event tried “sni approach” but didn’t work neither. (like: Example HAProxy config which selectively requires client certificates based on SNI "vhost" · GitHub).

Debugging seems certificate verification not being applied at all:

00000000:default-443.accept(0007)=000e from [192.168.1.2:40506] ALPN=h2
00000000:default-443.clireq[000e:ffffffff]: GET https://sub.domain.tld/ HTTP/2.0
00000000:default-443.clihdr[000e:ffffffff]: host: sub.domain.tld
00000000:default-443.clihdr[000e:ffffffff]: user-agent: curl/7.81.0
00000000:default-443.clihdr[000e:ffffffff]: accept: /
00000000:sub.domain.tld.srvcls[000e:0010]
00000000:sub.domain.tld.srvrep[000e:0010]: HTTP/1.1 302 Found
00000000:sub.domain.tld.srvhdr[000e:0010]: date: Thu, 30 May 2024 06:25:48 GMT
00000000:sub.domain.tld.srvhdr[000e:0010]: server: Apache/2.4.52 (Ubuntu)
00000000:sub.domain.tld.srvhdr[000e:0010]: location: http://sub.domain.tld/hello
00000000:sub.domain.tld.srvhdr[000e:0010]: content-length: 0
00000000:sub.domain.tld.srvhdr[000e:0010]: content-type: text/html; charset=UTF-8
00000000:sub.domain.tld.clicls[000e:0010]
00000000:sub.domain.tld.closed[000e:0010]

Relevant configuration:

frontend default-443
mode http
bind *:443 ssl crt /etc/haproxy/certs/ crt-list /etc/haproxy/crt-list.txt
http-response del-header Server
use_backend %[req.hdr(Host),lower]

/etc/haproxy/crt-list.txt

/etc/haproxy/certs/sub.pem [verify required ca-file /etc/haproxy/certs/fakeca.crt] sub.domain.tld

haproxy -vv

HAProxy version 2.8.9-1ppa1~focal 2024/04/06 - https://haproxy.org/
Status: long-term supported branch - will stop receiving fixes around Q2 2028.
Known bugs: http://www.haproxy.org/bugs/bugs-2.8.9.html
Running on: Linux 5.4.0-182-generic #202-Ubuntu SMP Fri Apr 26 12:29:36 UTC 2024 x86_64
Build options :
TARGET = linux-glibc
CPU = generic
CC = cc
CFLAGS = -O2 -g -O2 -fdebug-prefix-map=/build/haproxy-Y40EiX/haproxy-2.8.9=. -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2 -Wall -Wextra -Wundef -Wdeclaration-after-statement -Wfatal-errors -Wtype-limits -Wshift-negative-value -Wshift-overflow=2 -Wduplicated-cond -Wnull-dereference -fwrapv -Wno-address-of-packed-member -Wno-unused-label -Wno-sign-compare -Wno-unused-parameter -Wno-clobbered -Wno-missing-field-initializers -Wno-cast-function-type -Wno-string-plus-int -Wno-atomic-alignment
OPTIONS = USE_OPENSSL=1 USE_LUA=1 USE_SLZ=1 USE_SYSTEMD=1 USE_QUIC=1 USE_PROMEX=1 USE_PCRE2=1 USE_PCRE2_JIT=1 USE_QUIC_OPENSSL_COMPAT=1
DEBUG = -DDEBUG_STRICT -DDEBUG_MEMORY_POOLS

Feature list : -51DEGREES +ACCEPT4 +BACKTRACE -CLOSEFROM +CPU_AFFINITY +CRYPT_H -DEVICEATLAS +DL -ENGINE +EPOLL -EVPORTS +GETADDRINFO -KQUEUE -LIBATOMIC +LIBCRYPT +LINUX_CAP +LINUX_SPLICE +LINUX_TPROXY +LUA +MATH -MEMORY_PROFILING +NETFILTER +NS -OBSOLETE_LINKER +OPENSSL -OPENSSL_WOLFSSL -OT -PCRE +PCRE2 +PCRE2_JIT -PCRE_JIT +POLL +PRCTL -PROCCTL +PROMEX -PTHREAD_EMULATION +QUIC +QUIC_OPENSSL_COMPAT +RT +SHM_OPEN +SLZ +SSL -STATIC_PCRE -STATIC_PCRE2 +SYSTEMD +TFO +THREAD +THREAD_DUMP +TPROXY -WURFL -ZLIB

Default settings :
bufsize = 16384, maxrewrite = 1024, maxpollevents = 200

Built with multi-threading support (MAX_TGROUPS=16, MAX_THREADS=256, default=2).
Built with OpenSSL version : OpenSSL 1.1.1f 31 Mar 2020
Running on OpenSSL version : OpenSSL 1.1.1f 31 Mar 2020
OpenSSL library supports TLS extensions : yes
OpenSSL library supports SNI : yes
OpenSSL library supports : TLSv1.0 TLSv1.1 TLSv1.2 TLSv1.3
Built with Lua version : Lua 5.3.3
Built with the Prometheus exporter as a service
Built with network namespace support.
Built with libslz for stateless compression.
Compression algorithms supported : identity(“identity”), deflate(“deflate”), raw-deflate(“deflate”), gzip(“gzip”)
Built with transparent proxy support using: IP_TRANSPARENT IPV6_TRANSPARENT IP_FREEBIND
Built with PCRE2 version : 10.34 2019-11-21
PCRE2 library supports JIT : yes
Encrypted password support via crypt(3): yes
Built with gcc compiler version 9.4.0

Available polling systems :
epoll : pref=300, test result OK
poll : pref=200, test result OK
select : pref=150, test result OK
Total: 3 (3 usable), will use epoll.

Available multiplexer protocols :
(protocols marked as cannot be specified using ‘proto’ keyword)
quic : mode=HTTP side=FE mux=QUIC flags=HTX|NO_UPG|FRAMED
h2 : mode=HTTP side=FE|BE mux=H2 flags=HTX|HOL_RISK|NO_UPG
fcgi : mode=HTTP side=BE mux=FCGI flags=HTX|HOL_RISK|NO_UPG
: mode=HTTP side=FE|BE mux=H1 flags=HTX
h1 : mode=HTTP side=FE|BE mux=H1 flags=HTX|NO_UPG
: mode=TCP side=FE|BE mux=PASS flags=
none : mode=TCP side=FE|BE mux=PASS flags=NO_UPG

Available services : prometheus-exporter
Available filters :
[BWLIM] bwlim-in
[BWLIM] bwlim-out
[CACHE] cache
[COMP] compression
[FCGI] fcgi-app
[SPOE] spoe
[TRACE] trace

Any ideas?

Hello,

Your configuration seems right, however you didn’t show anything that looks like a verify error from the client side, nor the haproxy side. What makes you think the verify didn’t work ? Everything seems right in the haproxy output.

Regards,

AFAIK the request (I’m using curl) should return something like:

curl: (56) OpenSSL SSL_read: error:0A00045C:SSL routines::tlsv13 alert certificate required, errno 0

but instead it loads home page.

Do you have the same problem with the ca-file and verify keyword on the bind line? Or is it a specific crt-list problem?

Seems its a specific crt-list problem.
Just tested:

bind *:443 ssl crt /etc/haproxy/certs/ verify required ca-file /etc/haproxy/certs/fakeca.crt

and haproxy debug said:

fd[0xe] OpenSSL error[0x1417c0c7] tls_process_client_certificate: peer did not return a certificate

But using:

bind *:443 ssl crt /etc/haproxy/certs/ crt-list /etc/haproxy/crt-list.txt

fails (loads page).

This looks like a bug to me, I’m investigating.

I tried to reproduce your problem but I can’t reproduce at all, this seems to behave correctly with the crt-list and ca-file, but your configuration is wrong in fact.

Your problem is that you are loading all certificates with crt /etc/haproxy/certs without any ca-file options, and then you are loading again your certificate in a crt-list, so haproxy will have twice the certificate with and without the ca-file option and will match the first one declared which does not use ca-file.

Seems changing crt/crt-list order did the trick:

bind *:443 ssl crt /etc/haproxy/certs/ crt-list /etc/haproxy/crt-list.txt

doesn't work, due certificate being loaded without any constraints.

bind *:443 ssl crt-list /etc/haproxy/crt-list.txt crt /etc/haproxy/certs/

works, as constraints are applied first.

but it this " trustworthy"? I mean…is it safe to use it this way or should I try another workaround?

If you have multiple certificates it’s better to separate them than load them twice. So you should probably move the certificate from the directory, or put every certificates in the crt-list instead of loading them from the directory.