Cloudflare turned 13 this year. For Cloudflare Birthday week, they announced support for Post-Quantum key exchange connections (KEX) to Cloudflare origin servers using X25519Kyber768Draft00. For this to work, origin servers need to support Post-Quantum key exchanges. This post outlines how Centmin Mod LEMP stack’s Nginx server can be configured to support Post-Quantum KEX.
Centmin Mod LEMP stack supports various Nginx crypto libraries – OpenSSL 1.1.1 (default), OpenSSL 3.0/3.1/3.2/3.3, and BoringSSL, LibreSSL, QuicTLS OpenSSL 1.1.1/3.0/3.1, Quiche/BoringSSL and Amazon AWS-LC for HTTP/3 QUIC. For Cloudflare Post-Quantum key exchange agreement support and connection to origin server using preferred curve, X25519Kyber768Draft00
, you need to switch Centmin Mod Nginx from default OpenSSL 1.1.1 to BoringSSL or Amazon AWS-LC crypto library as outlined below.
- Update: June 24, 2024: updated Nginx AWS-LC crypto example.
- Update: October 18, 2024: According to Bas Westerbaan from Cloudflare, they have just deployed X25519MLKEM768 to the Cloudflare network which will succeed X25519Kyber768Draft00 once usage drops. Chrome is doing the same in coming months and Firefox is rolling it out now. From what I can tell both BoringSSL and AWS-LC will support this and hence so will Centmin Mod Nginx probably via similar method as outlined below. Tested Centmin Mod Nginx 1.27.2 below with AWS-LC 1.37.0 and confirm
X25519MLKEM768
is supported.
1. Switch from default Centmin Mod Nginx built with OpenSSL 1.1.1 crypto library to BoringSSL crypto library by enabling BoringSSL support, by setting in the persistent config file /etc/centminmod/custom_config.inc
BORINGSSL_SWITCH='y'
Or if you’re using Centmin Mod 130.00beta01 or newer on AlmaLinux/Rocky Linux 8 (EL8) or 9 (EL9) operating systems, you can use Centmin Mod Nginx with Amazon AWS-LC crypto library which is an alternative fork of BoringSSL that combines with OpenSSL 1.1.1 base to re-add OpenSSL features for dual RSA + ECDSA SSL certificate support and re-add OCSP stapling support which BoringSSL stripped out. You can enable Nginx AWS-LC support by setting in the persistent config file /etc/centminmod/custom_config.inc
AWS_LC_SWITCH='y'
2. Run cmupdate and centmin.sh menu option 4 commands to update local Centmin Mod code to latest and then recompile Centmin Mod Nginx with BoringSSL
cmupdate centmin
-------------------------------------------------------- Centmin Mod Menu 130.00beta01 centminmod.com -------------------------------------------------------- 1). Centmin Install 2). Add Nginx vhost domain 3). NSD setup domain name DNS 4). Nginx Upgrade / Downgrade 5). PHP Upgrade / Downgrade 6). Option Being Revised (TBA) 7). Option Being Revised (TBA) 8). Option Being Revised (TBA) 9). Option Being Revised (TBA) 10). Memcached Server Re-install 11). MariaDB MySQL Upgrade & Management 12). Zend OpCache Install/Re-install 13). Install/Reinstall Redis PHP Extension 14). SELinux disable 15). Install/Reinstall ImagicK PHP Extension 16). Change SSHD Port Number 17). Multi-thread compression: zstd,pigz,pbzip2,lbzip2 18). Suhosin PHP Extension install 19). Install FFMPEG and FFMPEG PHP Extension 20). NSD Install/Re-Install 21). Data Transfer (TBA) 22). Add WordPress Nginx vhost + Cache Plugin 23). Update Centmin Mod Code Base 24). Exit -------------------------------------------------------- Enter option [ 1 - 24 ] 4 --------------------------------------------------------
Nginx Upgrade - Would you like to continue? [y/n] y Current Nginx Version: 1.24.0 (260923-074355-almalinux9-332935a-br-659b4b3) Install which version of Nginx? (version i.e. type 1.25.2): 1.25.2 Do you still want to continue? [y/n] y
You can ignore warning for nginx: [warn] "ssl_stapling" ignored, not supported
as BoringSSL doesn’t support OSCP Stapling.
Check that Nginx version output shows built with OpenSSL 1.1.1 (compatible; BoringSSL) (running with BoringSSL)
nginx -V nginx version: nginx/1.25.2 (290923-192121-almalinux9-332935a-br-659b4b3) built by gcc 12.2.1 20221121 (Red Hat 12.2.1-7) (GCC) built with OpenSSL 1.1.1 (compatible; BoringSSL) (running with BoringSSL) TLS SNI support enabled configure arguments: --with-ld-opt='-Wl,-E -L/usr/local/zlib-cf/lib -L/opt/boringssl/.openssl/lib -lcrypto -lssl -L/usr/local/nginx-dep/lib -lrt -ljemalloc -Wl,-z,relro -Wl,-rpath,/usr/local/zlib-cf/lib:/opt/boringssl/.openssl/lib:/usr/local/nginx-d ep/lib -flto=8 -fuse-ld=gold' --with-cc-opt='-I/opt/boringssl/.openssl/include -I/usr/local/zlib-cf/include -I/usr/local/nginx-dep/include -m64 -march=native -g -O3 -fstack-protector-strong -flto=8 -fuse-ld=gold --param=ssp-buffer-size=4 -Wformat -Werror=format-security -Wno-pointer-sign -Wimplicit-fallthrough=0 -Wno-implicit-function-declaration -Wno-int-conversion -Wno-error=unused-result -Wno-unused-result -fcode-hoisting -Wno-cast-function-type -Wno-format-extra-args -Wp,-D_FORTIFY_SOURCE=2' --sbin-path=/usr/local/sbin/nginx --conf-path=/usr/local/nginx/conf/nginx.conf --build=290923-192121-almalinux9-332935a-br-659b4b3 --with-compat --without-pcre2 --with-http_stub_status_module --with-http_secure_link_module --with-libatomic --with-http_gzip_static_module --add-dynamic-module=../ngx_brotli --add-module=../ngx_http_geoip2_module --with-http_sub_module --with-http_addition_module --with-http_image_filter_module=dynamic --with-http_geoip_module --with-stream_geoip_module --with-stream_realip_module --with-stream_ssl_preread_module --with-threads --with-stream --with-stream_ssl_module --with-http_realip_module --add-dynamic-module=../ngx-fancyindex-0.4.2 --add-module=../ngx_cache_purge-2.5.1 --add-dynamic-module=../ngx_devel_kit-0.3.2 --add-dynamic-module=../set-misc-nginx-module-0.33 --add-dynamic-module=../echo-nginx-module-0.63 --add-module=../redis2-nginx-module-0.15 --add-module=../ngx_http_redis-0.4.0-cmm --add-module=../memc-nginx-module-0.19 --add-module=../srcache-nginx-module-0.33 --add-dynamic-module=../headers-more-nginx-module-0.34 --with-pcre-jit --with-zlib=../zlib-cloudflare-1.3.0 --with-http_ssl_module --with-http_v2_module nginx -t nginx: [warn] "ssl_stapling" ignored, not supported nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful ldd /usr/local/sbin/nginx linux-vdso.so.1 (0x00007ffd7494b000) libcrypto.so => /opt/boringssl/.openssl/lib/libcrypto.so (0x00007fa90b0ba000) libssl.so => /opt/boringssl/.openssl/lib/libssl.so (0x00007fa90b04c000) libjemalloc.so.2 => /lib64/libjemalloc.so.2 (0x00007fa90ac00000) libcrypt.so.2 => /lib64/libcrypt.so.2 (0x00007fa90b005000) libmaxminddb.so.0 => /usr/local/nginx-dep/lib/libmaxminddb.so.0 (0x00007fa90affe000) libpcre.so.1 => /usr/local/nginx-dep/lib/libpcre.so.1 (0x00007fa90af85000) libGeoIP.so.1 => /lib64/libGeoIP.so.1 (0x00007fa90af46000) libatomic_ops.so.1 => /lib64/libatomic_ops.so.1 (0x00007fa90af41000) libc.so.6 => /lib64/libc.so.6 (0x00007fa90a800000) libstdc++.so.6 => /lib64/libstdc++.so.6 (0x00007fa90a400000) libm.so.6 => /lib64/libm.so.6 (0x00007fa90ab25000) libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007fa90af24000) /lib64/ld-linux-x86-64.so.2 (0x00007fa90b2b4000)
Notice BoringSSL libraries
libcrypto.so => /opt/boringssl/.openssl/lib/libcrypto.so (0x00007fa90b0ba000) libssl.so => /opt/boringssl/.openssl/lib/libssl.so (0x00007fa90b04c000)
Or for Nginx AWS-LC, Check that Nginx version output shows built with OpenSSL 1.1.1 (compatible; AWS-LC 1.30.1) (running with AWS-LC 1.30.1)
nginx -V nginx version: nginx/1.27.0 (220624-032712-almalinux8-82b794b-br-a71f931) built by gcc 13.1.1 20230614 (Red Hat 13.1.1-4) (GCC) built with OpenSSL 1.1.1 (compatible; AWS-LC 1.30.1) (running with AWS-LC 1.30.1) TLS SNI support enabled configure arguments: --with-ld-opt='-Wl,-E -L/usr/local/zlib-cf/lib -L/opt/aws-lc-install/lib64 -lcrypto -lssl -L/usr/local/nginx-dep/lib -lrt -ljemalloc -Wl,-z,relro,-z,now -Wl,-rpath,/usr/local/zlib-cf/lib:/opt/aws-lc-install/lib64:/usr/local/nginx-dep /lib -pie -flto=2 -flto-compression-level=3 -fuse-ld=gold' --with-cc-opt='-I/opt/aws-lc-install/include -I/usr/local/zlib-cf/include -I/usr/local/nginx-dep/include -m64 -march=native -fPIC -g -O3 -fstack-protector-strong -flto=2 -flto-compression-level=3 -fuse-ld=gold --param=ssp-buffer-size=4 -Wformat -Wno-pointer-sign -Wimplicit-fallthrough=0 -Wno-cast-align -Wno-implicit-function-declaration -Wno-builtin-declaration-mismatch -Wno-deprecated-declarations -Wno-int-conversion -Wno-unused-result -Wno-vla-parameter -Wno-maybe-uninitialized -Wno-return-local-addr -Wno-array-parameter -Wno-alloc-size-larger-than -Wno-address -Wno-array-bounds -Wno-discarded-qualifiers -Wno-stringop-overread -Wno-stringop-truncation -Wno-missing-field-initializers -Wno-unused-variable -Wno-format -Wno-error=unused-result -Wno-missing-profile -Wno-stringop-overflow -Wno-free-nonheap-object -Wno-discarded-qualifiers -Wno-bad-function-cast -Wno-dangling-pointer -Wno-array-parameter -fcode-hoisting -Wno-cast-function-type -Wno-format-extra-args -Wp,-D_FORTIFY_SOURCE=2' --prefix=/usr/local/nginx --sbin-path=/usr/local/sbin/nginx --conf-path=/usr/local/nginx/conf/nginx.conf --build=220624-032712-almalinux8-82b794b-br-a71f931 --with-compat --without-pcre2 --with-http_stub_status_module --with-http_secure_link_module --with-libatomic --with-http_gzip_static_module --add-dynamic-module=../ngx_brotli --add-module=../zstd-nginx-module --add-module=../ngx_http_geoip2_module --with-http_sub_module --with-http_addition_module --with-http_image_filter_module=dynamic --with-http_geoip_module --with-stream_geoip_module --with-stream_realip_module --with-stream_ssl_preread_module --with-threads --with-stream --with-stream_ssl_module --with-http_realip_module --add-dynamic-module=../ngx-fancyindex-0.4.2 --add-module=../ngx_cache_purge-2.5.1 --add-dynamic-module=../ngx_devel_kit-0.3.2 --add-dynamic-module=../set-misc-nginx-module-0.33 --add-dynamic-module=../echo-nginx-module-0.63 --add-module=../redis2-nginx-module-0.15 --add-module=../ngx_http_redis-0.4.0-cmm --add-module=../memc-nginx-module-0.20 --add-module=../srcache-nginx-module-0.33 --add-dynamic-module=../headers-more-nginx-module-0.37 --with-pcre-jit --with-zlib=../zlib-cloudflare-1.3.3 --with-zlib-opt=-fPIC --with-http_ssl_module --with-http_v2_module --with-http_v3_module
Or for Nginx AWS-LC, Nginx version output shows built with OpenSSL 1.1.1 (compatible; AWS-LC 1.37.0) (running with AWS-LC 1.37.0)
with X25519MLKEM768
support.
nginx -V nginx version: nginx/1.27.2 (171024-154416-almalinux8-c90b685-br-a71f931) built by gcc 13.1.1 20230614 (Red Hat 13.1.1-4) (GCC) built with OpenSSL 1.1.1 (compatible; AWS-LC 1.37.0) (running with AWS-LC 1.37.0) TLS SNI support enabled configure arguments: --with-ld-opt='-Wl,-E -L/usr/local/zlib-cf/lib -L/opt/aws-lc-install/lib64 -lcrypto -lssl -L/usr/local/nginx-dep/lib -lrt -ljemalloc -Wl,-z,relro,-z,now -Wl,-rpath,/usr/local/zlib-cf/lib:/opt/aws-lc-install/lib64:/usr/local/nginx-dep /lib -pie -flto=2 -flto-compression-level=3 -fuse-ld=gold -ffat-lto-objects -Wl,-Bsymbolic-functions -Wl,--as-needed' --with-cc-opt='-I/opt/aws-lc-install/include -I/usr/local/zlib-cf/include -I/usr/local/nginx-dep/include -m64 -march=native -fPIC -g -O3 -fstack-protector-strong -flto=2 -flto-compression-level=3 -fuse-ld=gold -ffat-lto-objects --param=ssp-buffer-size=4 -Wformat -Wno-pointer-sign -Wimplicit-fallthrough=0 -Wno-implicit-function-declaration -Wno-cast-align -Wno-builtin-declaration-mismatch -Wno-deprecated-declarations -Wno-int-conversion -Wno-unused-result -Wno-vla-parameter -Wno-maybe-uninitialized -Wno-return-local-addr -Wno-array-parameter -Wno-alloc-size-larger-than -Wno-address -Wno-array-bounds -Wno-discarded-qualifiers -Wno-stringop-overread -Wno-stringop-truncation -Wno-missing-field-initializers -Wno-unused-variable -Wno-format -Wno-error=unused-result -Wno-missing-profile -Wno-stringop-overflow -Wno-free-nonheap-object -Wno-discarded-qualifiers -Wno-bad-function-cast -Wno-dangling-pointer -Wno-array-parameter -fcode-hoisting -Wno-cast-function-type -Wno-format-extra-args -Wp,-D_FORTIFY_SOURCE=2' --prefix=/usr/local/nginx --sbin-path=/usr/local/sbin/nginx --conf-path=/usr/local/nginx/conf/nginx.conf --build=171024-154416-almalinux8-c90b685-br-a71f931 --with-compat --without-pcre2 --with-http_stub_status_module --with-http_secure_link_module --with-libatomic --with-http_gzip_static_module --add-dynamic-module=../ngx_brotli --add-module=../zstd-nginx-module --add-module=../ngx_http_geoip2_module --with-http_sub_module --with-http_addition_module --with-http_image_filter_module=dynamic --with-http_geoip_module --with-stream_geoip_module --with-stream_realip_module --with-stream_ssl_preread_module --with-threads --with-stream --with-stream_ssl_module --with-http_realip_module --add-dynamic-module=../ngx-fancyindex-0.5.2 --add-module=../ngx_cache_purge-2.5.3 --add-dynamic-module=../ngx_devel_kit-0.3.2 --add-dynamic-module=../set-misc-nginx-module-0.33 --add-dynamic-module=../echo-nginx-module-0.63 --add-module=../redis2-nginx-module-0.15 --add-module=../ngx_http_redis-0.4.0-cmm --add-module=../memc-nginx-module-0.20 --add-module=../srcache-nginx-module-0.33 --add-dynamic-module=../headers-more-nginx-module-0.37 --with-pcre-jit --with-zlib=../zlib-cloudflare-1.3.3 --with-zlib-opt=-fPIC --with-http_ssl_module --with-http_v2_module --with-http_v3_module
3. Setup Nginx Post-Quantum Key Exchange Support
In existing Centmin Mod Nginx vhost HTTPS config file i.e. /usr/local/nginx/conf/conf.d/domain.com.ssl.conf
update the following:
The blog mentions 2 additional directives for ssl_protocols
and ssl_prefer_server_ciphers
but they’re already enabled by Centmin Mod Nginx in /usr/local/nginx/conf/ssl_include.conf
include file
In /usr/local/nginx/conf/ssl_include.conf
ssl_session_cache shared:SSL:10m; ssl_session_timeout 60m; ssl_protocols TLSv1.2 TLSv1.3;
and /usr/local/nginx/conf/conf.d/domain.com.ssl.conf
Nginx vhost
ssl_prefer_server_ciphers on;
Then set ssl_ecdh_curve X25519Kyber768Draft00:X25519
. You can do this in two ways:
- First method is to set in your existing Nginx HTTPS vhost where you have manually set
default_server
option in Nginx listen directive. This method takes highest priority when Nginx server reads thessl_ecdh_curve
directive set. - Second method, is to set in /usr/local/nginx/conf/nginx.conf
http{}
context.
Set in only one Nginx HTTPS vhost where listen directive has set default_server
and not in all Nginx HTTPS vhosts. If no existing Nginx HTTPS vhosts have set default_server
in listen directive, choose a single Nginx HTTPS vhost’s listen directive and set it.
For Nginx 1.25
listen 443 ssl default_server; http2 on; ssl_ecdh_curve X25519Kyber768Draft00:X25519;
For Nginx 1.24
listen 443 ssl http2 default_server; ssl_ecdh_curve X25519Kyber768Draft00:X25519;
For Nginx 1.27.2 with X25519MLKEM768
listen 443 ssl http2 default_server; ssl_ecdh_curve X25519MLKEM768:X25519;
Or in /usr/local/nginx/conf/nginx.conf http{}
context
http { ssl_ecdh_curve X25519Kyber768Draft00:X25519; include /usr/local/nginx/conf/brotli_inc.conf; map_hash_bucket_size 128; map_hash_max_size 4096; server_names_hash_bucket_size 128; server_names_hash_max_size 2048; variables_hash_max_size 2048;
Or for X25519MLKEM768
http { ssl_ecdh_curve X25519MLKEM768:X25519; include /usr/local/nginx/conf/brotli_inc.conf; map_hash_bucket_size 128; map_hash_max_size 4096; server_names_hash_bucket_size 128; server_names_hash_max_size 2048; variables_hash_max_size 2048;
If you have ssl_ecdh_curve
set in both, as per https://trac.nginx.org/nginx/ticket/2542, the default_server
set values will take priority.
- In OpenSSL before 1.0.2 (before introduction of curve lists), the specified setting properly applied by OpenSSL to name-based virtual servers (as it is stored in cert->ecdh_tmp, and certificates are properly replaced during the SSL context switch in servername callback).
- In OpenSSL 1.0.2 and above, the list of configured curves is copied from the SSL context when the connection is created (see SSL_new()). As such, configuration from the default server applies to all name-based virtual servers. It is possible to re-apply the setting to the connection in the servername callback though (but see below).
- In OpenSSL 1.1.1 and above (tested up to OpenSSL 3.1.2) when using TLSv1.3, attempts to re-apply the setting results in handshake failure on the client
- In BoringSSL, the list of configured curves is copied from the SSL context when the connection is created (similarly to OpenSSL 1.0.2, see SSL_new()). It does, however, work properly with TLSv1.3 when redefined in the servername callback.
- In LibreSSL (tested with LibreSSL 3.8.0), the list of configured curves is copied from the SSL context when the connection is created (similarly to OpenSSL 1.0.2, see SSL_new()). With TLSv1.2, it does work properly when redefined in the servername callback. With TLSv1.3, attempts to redefine the list of curves in the servername callback are ignored, the configuration from the original SSL context is used instead.
Once updated, run nginx config check
nginx -t nginx: [warn] "ssl_stapling" ignored, not supported nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful
Looks good ignoring OCSP stapling that isn’t supported by BoringSSL.
4. Test Centmin Mod Nginx BoringSSL with Post-Quantum support using bssl
client
Use bssl
client to test Post-Quantum key exchange support in Centmin Mod Nginx built with BoringSSL.
With default_server
set in the Nginx HTTPS vhost config file’s listen directive, I now see the ECDHE group
report X25519Kyber768Draft00
. If Centmin Mod Nginx doesn’t support Post-Quantum key exchange, it will report X25519
curve instead.
Replace domain assigned variable with your domain name.
domain=domain.com echo | bssl client -server-name $domain -connect $domain:443 -curves X25519:X25519Kyber768Draft00 -debug
Example output for X25519Kyber768Draft00
domain=domain.com echo | bssl client -server-name $domain -connect $domain:443 -curves X25519:X25519Kyber768Draft00 -debug Connecting to myserverip:443 Handshake started. Handshake progress: TLS client enter_early_data Handshake progress: TLS client read_server_hello Handshake progress: TLS 1.3 client read_hello_retry_request Handshake progress: TLS 1.3 client read_server_hello Handshake progress: TLS 1.3 client read_encrypted_extensions Handshake progress: TLS 1.3 client read_certificate_request Handshake progress: TLS 1.3 client read_server_certificate Handshake progress: TLS 1.3 client read_server_certificate_verify Handshake progress: TLS 1.3 client read_server_finished Handshake progress: TLS 1.3 client send_end_of_early_data Handshake progress: TLS 1.3 client send_client_encrypted_extensions Handshake progress: TLS 1.3 client send_client_certificate Handshake progress: TLS 1.3 client complete_second_flight Handshake progress: TLS 1.3 client done Handshake progress: TLS client finish_client_handshake Handshake progress: TLS client done Handshake done. Connected. Version: TLSv1.3 Resumed session: no Cipher: TLS_AES_128_GCM_SHA256 ECDHE group: X25519Kyber768Draft00 Signature algorithm: ecdsa_secp256r1_sha256 Secure renegotiation: yes Extended master secret: yes Next protocol negotiated: ALPN protocol: OCSP staple: no SCT list: no Early data: no Encrypted ClientHello: no Cert subject: CN = domain.com Cert issuer: C = US, O = Let's Encrypt, CN = R3
Example for X25519MLKEM768
domain=domain.com echo | bssl client -server-name $domain -connect $domain:443 -curves X25519MLKEM768 -debug Connecting to myserverip:443 Handshake started. Handshake progress: TLS client enter_early_data Handshake progress: TLS client read_server_hello Handshake progress: TLS 1.3 client read_hello_retry_request Handshake progress: TLS 1.3 client read_server_hello Handshake progress: TLS 1.3 client read_encrypted_extensions Handshake progress: TLS 1.3 client read_certificate_request Handshake progress: TLS 1.3 client read_server_certificate Handshake progress: TLS 1.3 client read_server_certificate_verify Handshake progress: TLS 1.3 client read_server_finished Handshake progress: TLS 1.3 client send_end_of_early_data Handshake progress: TLS 1.3 client send_client_encrypted_extensions Handshake progress: TLS 1.3 client send_client_certificate Handshake progress: TLS 1.3 client complete_second_flight Handshake progress: TLS 1.3 client done Handshake progress: TLS client finish_client_handshake Handshake progress: TLS client done Handshake done. Connected. Version: TLSv1.3 Resumed session: no Cipher: TLS_AES_128_GCM_SHA256 ECDHE group: X25519MLKEM768 Signature algorithm: ecdsa_secp256r1_sha256 Secure renegotiation: yes Extended master secret: yes Next protocol negotiated: ALPN protocol: OCSP staple: no SCT list: no Early data: no Encrypted ClientHello: no Cert subject: CN = domain.com Cert issuer: C = US, O = Let's Encrypt, CN = E6
If you do not set ssl_ecdh_curve
in either default_server
listen directive for one Nginx HTTPS vhost or in nginx.conf http{}
context, then bssl
tests may return an error like below despite having set ssl_ecdh_curve
in a regular Nginx HTTPS vhost (not using default_server
listen directive):
Connecting to myserverip:443 Error while connecting: SSLV3_ALERT_HANDSHAKE_FAILURE 25938144:error:10000410:SSL routines:OPENSSL_internal:SSLV3_ALERT_HANDSHAKE_FAILURE:/opt/boringssl/ssl/tls_record.cc:592:SSL alert number 40 25938144:error:1000009a:SSL routines:OPENSSL_internal:HANDSHAKE_FAILURE_ON_CLIENT_HELLO:/opt/boringssl/ssl/handshake.cc:644:
and Nginx error log debug shows
2023/09/30 11:01:22 [info] 1731475#1731475: *1 SSL_do_handshake() failed (SSL: error:1000010a:SSL routines:OPENSSL_internal:NO_SHARED_GROUP) while SSL handshaking, client: myip, server: 0.0.0.0:443
which indicates that during the SSL/TLS handshake, the client and server couldn’t agree on a shared elliptic curve group for key exchange.
Seems Cloudflare already has determined my test Centmin Mod Nginx origin with BoringSSL has Post-Quantum key exchange support and is communicating with my origin via X25519Kyber768Draft00
. Cloudflare is scanning all active origins for support of Post-Quantum key exchange agreement every 24 hours,. Cloudflare will attempt a series of about ten TLS connections to Cloudflare origin server, to test support and preferences for the various key exchange agreements.
From my Centmin Mod Nginx access logs in JSON format, shows ssl_curve
for the request from Cloudflare orange cloud enabled site = X25519Kyber768Draft00
cat access.json | jq -c | tail -1 | jq -r { "msec": "1696111325.536", "connection": "1", "connection_requests": "1", "pid": "1743334", "request_id": "e52516e5a82687420646a8b81c0d8e76", "request_length": "650", "remote_addr": "172.xxx.xxx.xxx", "remote_user": "", "remote_port": "10544", "time_local": "30/Sep/2023:22:02:05 +0000", "time_iso8601": "2023-09-30T22:02:05+00:00", "request": "GET / HTTP/2.0", "request_uri": "/", "args": "", "status": "304", "body_bytes_sent": "0", "bytes_sent": "177", "http_referer": "", "http_user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 OPR/102.0.0.0", "http_x_forwarded_for": "2a09:xxx:xxx:xxx:xxx:xxx", "http_host": "domain.com", "server_name": "domain.com", "request_time": "0.000", "upstream": "", "upstream_connect_time": "", "upstream_header_time": "", "upstream_response_time": "", "upstream_response_length": "", "upstream_cache_status": "", "ssl_protocol": "TLSv1.3", "ssl_session_reused": ".", "ssl_cipher": "TLS_AES_128_GCM_SHA256", "ssl_curve": "X25519Kyber768Draft00", "ssl_curves": "", "scheme": "https", "request_method": "GET", "server_protocol": "HTTP/2.0", "pipe": ".", "gzip_ratio": "", "http_cf_ray": "80efbf881ea0e9bf-BNE", "http_cf_worker": "", "http_cf_request_id": "", "http_cf_railgun": "", "http_accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7" }
Chrome 116+ based also support experimental Post-Quantum X25519 Kyber768 key exchanges. Here’s an example enabled in Opera 102 browser for above test site behind Cloudflare HTTP/3 QUIC based X25519 Kyber768 draft00 key exchange connection.
5. Enabling Cloudflare Outbound Connection Post-Quantum Key Exchange Support
Cloudflare is rolling out support for X25519+Kyber for most outbound connections, including origin servers and Cloudflare Workers fetch() calls as per this schedule.
Plan | Support for post-quantum outbound connections |
---|---|
Free | Started roll-out. Aiming for 100% by the end of the October. |
Pro and Business | Started roll-out. Aiming for 100% by the end of year. |
Enterprise | Start roll-out February 2024. 100% by March 2024 |
You can skip the roll-out and opt-in your Cloudflare domain zone today, or opt-out ahead of time, using an API described below. Before rolling out this support for enterprise customers in February 2024, a toggle on the dashboard to opt out will be added.
To enable a Post-Quantum connection between Cloudflare and your origin server today, opt-in your Cloudflare domain zone to skip the gradual roll-out you can switch from the default value of default to preferred
– though my free Cloudflare plan domain zone seems to default to a value of supported
:
curl --request PUT \ --url https://api.cloudflare.com/client/v4/zones/(zone_id)/cache/origin_post_quantum_encryption \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer (API token)' \ --data '{"value": "preferred"}'
Replace (zone_id)
and (API token)
appropriately. Then, make sure your server supports TLS 1.3; enable and prefer the key agreement X25519Kyber768Draft00;
and ensure it’s configured as outlined in above mentioned Centmin Mod Nginx instructions.
The following values are supported:
Setting | Meaning |
---|---|
supported | Advertise support for post-quantum key agreement, but send a classical keyshare in the first ClientHello.When the origin supports and prefers X25519+Kyber, a post-quantum connection will be established, but it incurs an extra roundtrip.This is the most compatible way to enable post-quantum. |
preferred | Send a post-quantum keyshare in the first ClientHello.When the origin supports X25519+Kyber, a post-quantum connection will be established without an extra roundtrip.This is the most performant way to enable post-quantum. |
off | Do not send or advertise support for post-quantum key agreement to the origin. |
(default) | Allow us to determine the best behavior for your zone. (More about that later.) |
Additional Notes:
Thanks to Bas Westerbaan from Cloudflare for these additional notes.
- If you change your Cloudflare domain zone from default
https://api.cloudflare.com/client/v4/zones/(zone_id)/cache/origin_post_quantum_encryption
setting value of supported to preferred, if an origin server does not support Post-Quantum key exchange viaX25519Kyber768Draft00
, Cloudflare will fallback to using a supported curve likeX25519
for key exchange. An attacker cannot use this to downgrade the connection between Cloudflare and an origin server that does support Post-Quantum KEX – as long as there is not a commonly supported signature scheme that is broken at the time of the handshake. - Cloudflare Tunnels have already been updated to support Post-Quantum KEX for connection between Cloudflare and the Tunnel running on origin server if the Cloudflare Tunnel is configured to support
quic
protocol instead ofhttp2
protocol. FYI, for those interested I did benchmark Cloudflare Tunnels http2 vs quic protocol connections. - Cloudflare intends to update Cloudflare Tunnels with
http2
protocol to support Post-Quantum KEX between Cloudflare and Tunnel installed at origin once Cloudflare upgrades to Go 1.21+. - Currently, for Cloudflare Tunnel to origin connection segment, Post-Quantum KEX is not yet supported.
- To follow the latest developments of Cloudflare’s deployment of Post-Quantum cryptography, and client/server support, check out pq.cloudflareresearch.com and a reminder to share your experiences, or reach out to Cloudflare for any questions: ask-research@cloudflare.com
Optimal Cloudflare To Origin Configuration:
I did some automated Centmin Mod Nginx crypto library benchmarks comparing performance of supported Nginx OpenSSL 1.1.1 vs OpenSSL 3.0 vs QuicTLS OpenSSL 1.1.1 fork vs BoringSSL, and seems Nginx built with BoringSSL is slightly faster. BoringSSL doesn’t support OCSP stapling or dual RSA 2048bit + ECDSA 256bit SSL certificates like OpenSSL does, but if you’re running Centmin Mod Nginx as an origin behind Cloudflare CDN edge servers, then it shouldn’t matter when you’re setup with Cloudflare FULL/FULL Strict SSL modes.
So switching to Cloudflare Post-Quantum key exchange agreement connections to Centmin Mod Nginx origin built with BoringSSL seems like a good combination for both security and performance.
Test done on 2x CPU core Azure Ubuntu 20 instance via Github Workflow automated action tests for Centmin Mod LEMP stack on AlmaLinux 8.8 Docker container using Intel Xeon Platinum 8272CL CPU @2.60GHz using respective openssl
and BoringSSL bssl
binaries’ speed tests in single threaded mode.
Library | RSA 2048 Sign/s | RSA 2048 Verify/s | ECDSA 256-bit Sign/s | ECDSA 256-bit Verify/s |
---|---|---|---|---|
OpenSSL 1.1.1 | 1433.2 | 49688.0 | 40379.6 | 13020.1 |
OpenSSL 3.0 | 1465.3 | 50169.6 | 38818.7 | 13159.2 |
QuicTLS 1.1.1 | 1468.6 | 49766.6 | 40310.4 | 13026.5 |
BoringSSL | 1482.0 | 54519.4 (same key) | 45143.9 | 16039.9 |
OpenSSL 1.1.1
nginx version: nginx/1.25.2 (061023-210846-almalinux8-hyperv-docker-42d655b) built by gcc 11.2.1 20220127 (Red Hat 11.2.1-9) (GCC) built with OpenSSL 1.1.1w 11 Sep 2023 Benchmarking OpenSSL openssl111... rsa 2048 bits signs/s: 1433.2 rsa 2048 bits verify/s: 49688.0 256 bits ecdsa (nistp256) signs/s: 40379.6 256 bits ecdsa (nistp256) verify/s: 13020.1
OpenSSL 3.0.11
nginx version: nginx/1.25.2 (061023-211918-almalinux8-hyperv-docker-42d655b) built by gcc 11.2.1 20220127 (Red Hat 11.2.1-9) (GCC) built with OpenSSL 3.0.11 19 Sep 2023 Benchmarking OpenSSL openssl30... rsa 2048 bits signs/s: 1465.3 rsa 2048 bits verify/s: 50169.6 256 bits ecdsa (nistp256) signs/s: 38818.7 256 bits ecdsa (nistp256) verify/s: 13159.2
QuicTLS OpenSSL 1.1.1 fork
nginx version: nginx/1.25.2 (061023-213612-almalinux8-hyperv-docker-42d655b) built by gcc 11.2.1 20220127 (Red Hat 11.2.1-9) (GCC) built with OpenSSL 1.1.1w+quic 11 Sep 2023 Benchmarking OpenSSL-QUIC... rsa 2048 bits signs/s: 1468.6 rsa 2048 bits verify/s: 49766.6 256 bits ecdsa (nistp256) signs/s: 40310.4 256 bits ecdsa (nistp256) verify/s: 13026.5
BoringSSL
nginx version: nginx/1.25.2 (061023-214630-almalinux8-hyperv-docker-42d655b) built by gcc 11.2.1 20220127 (Red Hat 11.2.1-9) (GCC) built with OpenSSL 1.1.1 (compatible; BoringSSL) (running with BoringSSL) Benchmarking BoringSSL... Did 1530 RSA 2048 signing operations in 1032358us (1482.0 ops/sec) Did 55000 RSA 2048 verify (same key) operations in 1008816us (54519.4 ops/sec) Did 45000 RSA 2048 verify (fresh key) operations in 1001388us (44937.6 ops/sec) Did 8100 RSA 2048 private key parse operations in 1046914us (7737.0 ops/sec) Did 46000 ECDSA P-256 signing operations in 1018964us (45143.9 ops/sec) Did 17000 ECDSA P-256 verify operations in 1059855us (16039.9 ops/sec)
Update: Updated benchmarks on Centmin Mod 140.00beta01 on AlmaLinux 9 testing system OpenSSL 3.0.7 vs OpenSSL 3.3.2 vs OpenSSL 1.1.1w EOL vs BoringSSL vs AWS-LC 1.34.2 for RSA 2048bit and ECDSA 256bit. Ran on libvirtd KVM VPS with 16 CPU cores on AMD Ryzen 5950X with 8GB memory.
Library | RSA 2048 Sign/s | RSA 2048 Verify/s | ECDSA (nistp256) Sign/s | ECDSA (nistp256) Verify/s |
---|---|---|---|---|
OpenSSL 3.0.7 (sys) | 2194.7 | 80739.2 | 63866.6 | 21088.2 |
OpenSSL 3.3.2 | 2154.4 | 75425.2 | 66174.1 | 21575.4 |
OpenSSL 1.1.1w | 2158.5 | 75584.1 | 68769.2 | 21854.4 |
BoringSSL | 2185.4 | 82061.1 | 73360.2 | 25546.7 |
AWS-LC | 2168.0 | 82119.5 | 73805.9 | 25398.3 |