Nginx, OpenSSL, and Quantum-Safe Cryptography
Secure Web Service with Nginx, OpenSSL, and Quantum-Safe Cryptography
Step-by-step notes for setting up a web server running
the latest protocols and cryptography for the best
performance and security.
We will use Nginx
for the HTTP/HTTPS service,
OpenSSL for the
cryptographic infrastructure,
and the Open Quantum Safe or OQS
liboqs library to "future-proof" your server
against quantum computing breakthroughs.
A current operating system should provide most of
what you need.
If you need to build OQS from source,
I'll show you how to do that.
Like many of my technical pages,
this serves as an aide-mémoire for me.
I figured all this out once,
and this helps when I need to return to it.
You will see that I have simply included blocks of my
heavily commented nginx.conf
file
with some added explanations.
I find this useful,
and I hope that you will, too.
You Will Need Certificates
You might want to start by getting your certificates lined up. See my pages for how to get, and automatically renew, certificates from Let's Encrypt.
I wouldn't advise anyone to start a project like this on GoDaddy, but if you're already locked in to that platform, at least there's a way to automate certificate renewal.
OpenSSL
OpenSSL provides the cryptographic framework.
It includes the
openssl
and
s_client
commands (along with many others),
plus a large shared library of cryptographic functions
that other programs can use.
You need OpenSSL 3.* to support Open Quantum Safe. Version 3.0.0 released on 7 September 2021, so your operating system should include it. If not, check your package management system to see if it includes extra packages with newer versions of some utilities, or if it can can reference an added package repository as with the EPEL collection for Red Hat, Oracle Linux, etc.
Wait, "Post-Quantum"? "Quantum-Safe"?
We usually use hybrid cryptography to protect information — symmetric cryptography to protect the data itself, and asymmetric cryptography to protect the authentication and the session keys.
When establishing a connection, the end points use asymmetric cryptography, sometimes called public-key cryptography, first to authenticate, and then to establish a shared session key.
The shared session key is for the symmetric cipher that will protect the data. It will be an ephemeral session key, meaning that it will be randomly generated and then used only for this one connection session. To re-connect and send the same data, a new unique ephemeral key would be generated.
The symmetric cipher will typically be AES, or possibly the ChaCha20/Poly1305 stream cipher which can be used with TLS.
An asymmetric cipher is based on a "trapdoor" function. That's a problem that is enormously, impractically more difficult to do in one direction than in the other. We can safely conclude that there is no practical solution, but it would be quite easy to test whether or not a proposed solution is correct.
How RSA WorksThe factoring problem has been a traditional trapdoor function used for session key agreement. The security of the RSA algorithm is based on the difficulty of factoring the product of two large prime numbers. How large? For a 4096-bit RSA key, two prime numbers of about 640 digits each, multiplied together to form a product of 1280 digits. The 4096-bit product is the public key, and the two roughly 2048-bit prime numbers are the private key.
How Elliptic-CurveCryptography Works
The classic Diffie-Hellman scheme from the 1970s uses the discrete logarithm problem as its trapdoor. For elliptic-curve cryptography or ECC, the trapdoor function is the related elliptic-curve discrete logarithm problem.
Unfortunately, the factoring and discrete logarithm problems could be quickly solved by a quantum computer with enough stable qubits running Shor's algorithm. That would break either of those trapdoor functions, exposing the session keys and allowing an attacker to decrypt the ciphertext.
Richard Feynman, "Simulating Physics with Computers", Intl. J. of Theoretical Physics, 21:467-488, 1981Quantum computing had been suggested in 1981, and when Peter Shor published his algorithm in 1994, the cryptographic world realized that quantum computing was be a threat.
Researchers began working on what has been variously called post-quantum, quantum-safe, and quantum-resistant cryptography. The PQCrypto conference series began running in 2006. The goal is the development of asymmetric algorithms unbreakable with Shor's algorithm. The Open Quantum Safe project (GitHub page here) provides open-source software implementing the quantum-resistant algorithms we have so far. The U.S. National Institute of Standards and Technology has led research projects, workshops, conferences, and competitions for PQC, selecting algorithms to become standards.
Grover's algorithm was published just two years after Shor's. It is useful in an attack against a symmetric cipher. But it is not nearly the threat that Shor's algorithm poses to our traditional asymmetric ciphers. 256-bit AES would be reduced to 128-bit strength against a large, stable quantum computer running Grover's algorithm. That's not as strong as the pre-quantum case, but it's still pretty strong.
U.S. NIST Finalized Post-Quantum Algorithms in August 2024
"NIST finalizes trio of post-quantum
encryption standards"
The Register
FIPS 203: Module-Lattice-Based Key-Encapsulation
Mechanism Standard
FIPS 204: Module-Lattice-Based
Digital Signature Standard
FIPS 205: Stateless Hash-Based
Digital Signature Standard
liboqs — Open Quantum Safe
liboqs is the Open Quantum Safe
shared library.
You need this library.
On FreeBSD, which this server runs,
liboqs is available as a package.
That package includes the shared library file
/usr/local/lib/liboqs.so
.
If you need to compile your own liboqs library, see the project's GitHub page for full details.
oqsprovider — Open Quantum Safe
With major version 3, OpenSSL supports "providers",
modules for adding capabilities like FIPS compliance
and post-quantum algorithms.
The shared library file
oqsprovider.so
adds post-quantum algorithms to OpenSSL.
However, you don't need oqsprovider to support the
quantum-resistant algorithms in Nginx.
It accomplishes that with the liboqs.so
shared library.
I have some notes on building oqsprovider on FreeBSD. For Linux, see the Open Quantum Safe oqsprovider page.
Nginx and HTTP
HTTP/1 was finalized in 1996, and updated as 1.1 in 1997. HTTP/2 came out in 2015, and HTTP/3 in 2022. Of course we prefer the latest and greatest as they have better performance and capabilities. But we also want to support the older versions still used by web crawlers. And, unfortunately, also used by bots looking for vulnerable servers...
To do this, the nginx.conf
file has
the following overall structure.
My server is hosting
cromwell-intl.com
,
toilet-guru.com
,
and two other sites.
The following works on Nginx 1.25.0 and later,
that was the first release of Nginx to support QUIC and HTTP/3.
The listen 443
directives would have
to be changed for 1.24.0 and earlier.
See the following sections for content and discussion
of the server { ... }
blocks.
# Let the server start one worker process per CPU core, # allow many connections per worker. worker_processes auto; events { worker_connections 1024; } http { # Define everything that can be shared by all sites. server { listen 80; server_name cromwell-intl.com www.cromwell-intl.com; # Where and what to log, # Redirect to HTTPS on port 443 } server { listen 80; server_name toilet-guru.com www.toilet-guru.com; # Where and what to log, # Redirect to HTTPS on port 443 } # Two other server { listen 80 } blocks... server { listen 443 quic; listen 443 ssl; http2 on; server_name _; # This is an intentionally bogus definition. Modern browsers # will ignore it because "_" doesn't match a hostname. See # the below explanation. } server { listen 443 quic; listen 443 ssl; http2 on; server_name cromwell-intl.com www.cromwell-intl.com # HTTPS details for that site... } server { listen 443 quic; listen 443 ssl; http2 on; server_name toilet-guru.com toilet-guru-intl.com # HTTPS details for that site... } # Two other server HTTPS blocks... }
Why the Bogus "_" Block?
A web server can host multiple web sites, each with a unique "root" location. All of the DNS names for the various sites can resolve to the same IP address. That's happening on this server.
A modern browser supporting the SNI or Server Name Indication
extension to TLS specifies the server name in its request,
and the server uses the corresponding
server { ... }
block.
However, requests from extremely old browsers without SNI
(IE on Windows XP, for example)
would simply use
the first server
block,
getting the first certificate listed there.
If the request is for any site other than the first one defined,
that would imply information about relationships
between sites and organizations.
Those ancient browser clients couldn't make a TLS connection
because they require outdated SSL/TLS versions
that this server doesn't support.
However, tools like the the
Qualys scanner
simulate non-SNI browsers.
So, it also retrieves the first server
block.
Its certificate will be indicated with "No SNI" and and no details
shown by default.
With or without this block, the Qualys report will show "This site works only in browsers with SNI support" in the summary at the top. That's good, as browsers without SNI support are horribly outdated.
The server
block for the bogus "_" site
looks like the following.
The RSA private key and certificate are for
example.com
,
generated and self-signed on my server
because the whole point of this
is to be syntactically valid but entirely meaningless.
[... earlier blocks deleted ...] server { listen 443 quic; listen 443 ssl; http2 on; server_name _; ssl_certificate /usr/local/etc/letsencrypt/rsa-live/example.com/FakeSite_RSA.crt; ssl_certificate_key /usr/local/etc/letsencrypt/rsa-live/example.com/FakeSite_RSA.key; # Return a status code indicating that the server will not respond # to an unwanted request from a non-SNI browser. # https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/418 return 418; } [... following blocks deleted ...]
The "Shared by all sites" Section
As I mentioned at the beginning,
I am simply copying in that block of the
nginx.conf
file with all the detailed
comments that contains.
The result is that the bulk of this is explanatory comments.
I have made the non-comment content bold to make this
a little easier to interpret.
This section includes the cryptography.
To establish the connection,
ssl_ecdh_curve
for the Key Exchange or KEX
as it's called with ECDHE,
and the Key Encapsulation Mechanism or KEM
as it's called with quantum-safe algorithms.
And then ssl_ciphers
for the
symmetric cipher suite protecting the data.
As for which quantum-safe KEMs, you would normally select them based on a combination of strength and browser support. But there really isn't any browser support yet. As for strength, my understanding is that the algorithms with 512, 768, and 1024 in their names provide 128-bit, 192-bit, and 256-bit security, as those terms are applied to symmetric cryptography. So, I leaned toward them for my interim configuration.
It also limits the TLS protocol versions with
ssl_protocols
.
My server runs TLS 1.3 and 1.2 only, preferring 1.3.
Analysis of my server logs back in 2021 showed that
almost no clients ran TLSv1.1,
and while some ran TLSv1.0, they were mostly
hostile automated bots looking for WordPress
and other attack targets.
[...9 lines deleted...] http { ########################################################################### # Start by defining everything that can be shared by all sites. # Look for "Context: http" for that directive in: # https://nginx.org/en/docs/dirindex.html ########################################################################### include mime.types; default_type application/octet-stream; index Index.html index.html; error_page 404 /ssi/404page.html; # For general log variables see: # http://nginx.org/en/docs/http/ngx_http_log_module.html#log_format # For SSL-specific log variables see: # http://nginx.org/en/docs/http/ngx_http_ssl_module.html#variables # This is basically the default plus the last three, which I added # to capture statistics on client use of protocols and crypto. log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '$ssl_protocol $ssl_curve $ssl_cipher'; ########################################################################### ## Cryptography and TLS ########################################################################### # TLS versions 1.2 and 1.3 only, preferring 1.3. ssl_protocols TLSv1.3 TLSv1.2; # ssl_early_data is susceptible to replay attacks, although I'm not # doing anything on this server for which that could matter. ssl_early_data on; # Set aside 1 MB of storage to cache TLS connection details so a # subsequent page load can start immediately. That's enough for # about 4,000 sessions. ssl_session_cache shared:SSL:1m; # SSL session cache timeout defaults to 5 minutes, 1 minute should # be plenty. This is abused by advertisers like Google and Facebook, # long timeouts like theirs will look suspicious. See, for example: # https://www.zdnet.com/article/advertisers-can-track-users-across-the-internet-via-tls-session-resumption/ ssl_session_timeout 1m; # Specify the ciphers. Guidance is available here: # https://mozilla.github.io/server-side-tls/ssl-config-generator/ # https://wiki.mozilla.org/Security/Server_Side_TLS # # Using 4.5 months of log data in 2021, the following supports all but # 157 out of 1,955,087 clients, or all but 0.00803%. These are the # ciphers used by wikipedia.org as of May 2021, plus two more ChaCha20 # ciphers used by just 0.0087% and 0.0049% of clients in 2021. # # See "man ciphers" for a mapping between OpenSSL cipher names and the # IETF names. Or run: # $ openssl -v 'ALL:eNULL' | less # # My preference: # ECDHE-ECDSA first, then # ECDHE-RSA, then # DHE-RSA # and 256-bit before 128-bit in each block. # # HOWEVER: OpenSSL's API does not let you specify the TLS 1.3 ciphers # in the same way as the earlier ones. TLS 1.3 ciphers are set by: # SSL_CTX_set_ciphersuites() and SSL_set_ciphersuites() # while TLS 1.2 and earlier are set by # SSL_CTX_set_ciphers() and SSL_set_ciphers() # So, at least with OpenSSL 1.1.1, Nginx (and Apache) teams weren't # initially sure if the API would be stable over the long run. # They don't use the *ciphersuites() functions, so you can't change # cipher preference order for TLS 1.3. You get the default: # TLS_AES_256_GCM_SHA384 # TLS_CHACHA20_POLY1305_SHA256 # TLS_AES_128_GCM_SHA256 # See https://wiki.openssl.org/index.php/TLS1.3 for background. # Also see: # https://github.com/ssllabs/ssllabs-scan/issues/636 # https://trac.nginx.org/nginx/ticket/1529 # # My resulting list: # Cipher: Used by: # ECDHE-ECDSA-AES256-GCM-SHA384 TLSv1.3 # ECDHE-ECDSA-CHACHA20-POLY1305 TLSv1.3 # ECDHE-ECDSA-AES128-GCM-SHA256 TLSv1.3 # ECDHE-RSA-AES256-GCM-SHA384 TLSv1.2 # ECDHE-RSA-CHACHA20-POLY1305 TLSv1.2 # ECDHE-RSA-AES128-GCM-SHA256 TLSv1.2 # DHE-RSA-AES256-GCM-SHA384 TLSv1.2 # DHE-RSA-CHACHA20-POLY1305 TLSv1.2 # # Unfortunately, unlike Apache, the ssl_ciphers list has to be # one enormously long line. # ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305; # Have the server prefer them in that order: ssl_prefer_server_ciphers on; # However, only some clients have processors with the AES-NI extension. # AES is faster for clients with those processors, but ChaCha20 is # 3 times faster for processors without the special instructions. # Tell the server to prioritize ChaCha20, which means that if the # client reports ChaCha20 as its most-preferred cipher, use it; # otherwise the server preference wins. ssl_conf_command Options PrioritizeChaCha; # Elliptic curves for traditional ECDHE: prefer x448 and Ed25519 curves # first, NIST curves later. Speculation remains about NSA backdoors in # NIST curves. Chrome and derivatives dropped secp521r1 when it wasn't # listed in NSA's Suite B list. Indexing bots *.search.msn.com will be # about the only clients using secp384r1. Few clients support X448. # # As for the quantum-safe algorithms in the Open Quantum Safe suite, # these are just a few of them because nginx has a limit on the parameter # length. These are also supported in the BoringSSL variant, important to # enable Chromium support. Those starting "x25519_", "x448_", "p256_", # etc., are hybrid, meaning that a traditional key exchange algorithm # is also used along with a post-quantum ones So, if the new quantum # algorithm is broken, we're still reasonably safe. # # We can test this with, e.g.: # $ curl https://cromwell-intl.com --curves kyber1024 | grep Crypto # A list is available here: # https://github.com/open-quantum-safe/oqs-provider/blob/main/ALGORITHMS.md # However, it lists, e.g., x25519_kyber512 and NOT x25519_kyber90s512, no # "90s". As of late November and nginx 1.24.0_12,3 on FreeBSD 14.0, # these names and their default identifiers are not yet standardized. # To see what liboqs and oqsprovider offer: # $ openssl list -key-exchange-algorithms # $ openssl list -kem-algorithms # # As of late November 2023 and nginx 1.24.0_12,3, these worked for me # as an interim solution: # name TLS identifier # kyber1024 0x023d # p521_kyber1024 0x2f3d # x448_kyber768 0x2f90 # x25519_kyber90s512 0x2fa9 # x25519_frodo640aes 0x2f80 # x25519_frodo640shake 0x2f81 # p256_kyber90s512 0x2f3e # p256_frodo640aes 0x2f00 # p256_frodo640shake 0x2f01 # Plus, of course, the non-PQC curves: X448:X25519:secp521r1:secp384r1 # # Note that case matters! "x488_kyber768" but "X448". # # https://en.wikipedia.org/wiki/NIST_Post-Quantum_Cryptography_Standardization # lists CRYSTALS-Kyber, NTRU, SABER, and Classic McEliece as finalists for # PKE/KEM, with FrodoKEM, NTRU Prime, BIKE, HQC, and SIKE as alternates. # # You can find the hex TLS identifier within the Client Hello packet, # within "Extension: supported groups", within "Supported Groups", # although Wireshark won't know what the identifier means, nor will # most servers. ssl_ecdh_curve kyber1024:p521_kyber1024:x448_kyber768:x25519_kyber90s512:p256_kyber90s512:X448:X25519:secp521r1:secp384r1; # This file contains the predefined DH group ffdhe4096 recommended # by IETF in RFC 7919. Those have been audited, and may be more # resistant to attacks than randomly generated ones. See: # https://wiki.mozilla.org/Security/Server_Side_TLS # As opposed to generating my own with: # openssl dhparam 4096 -out /etc/ssl/dhparam.pem ssl_dhparam /usr/local/nginx/ssl_dhparam; ########################################################################### # Compression suggestions from: # https://www.digitalocean.com/community/tutorials/how-to-increase-pagespeed-score-by-changing-your-nginx-configuration-on-ubuntu-16-04 # - Level 5 (of 1-9) is almost as good as 9, and is much less work. # - Compressing very small things may make them larger. # - Compress even for clients connecting via proxies like Cloudfront. # - If client says it can handle compression, but it asks # for uncompressed, send it the compressed version. # - Nginx compresses HTML by default. Tell it about others data types # that could benefit. ########################################################################### gzip on; gzip_comp_level 5; gzip_min_length 256; gzip_proxied any; gzip_vary on; gzip_types text/plain text/css application/x-font-ttf image/x-icon application/javascript application/x-javascript text/javascript; ########################################################################### # TCP Tuning # # Nagle's algorithm (potentially) adds a 0.2 second delay to every # TCP connection. It made sense in the days of remote keyboard # interaction, but it gets in the way of transferring many files. # Turn on tcp_nodelay to disable Nagle's algorithm. # # FreeBSD man page for tcp(4) says: # TCP_NODELAY Under most circumstances, TCP sends data when it is # presented; when outstanding data has not yet been # acknowledged, it gathers small amounts of output to # be sent in a single packet once an acknowledgement # is received. For a small number of clients, such # as window systems that send a stream of mouse # events which receive no replies, this packetization # may cause significant delays. The boolean option # TCP_NODELAY defeats this algorithm. # # It's on by default, but why not make it explicit: ########################################################################### tcp_nodelay on; ########################################################################### # tcp_nopush blocks data until either it's done or the packet reaches the # MSS, so you more efficiently stream data in larger segments. You can # send a response header and the beginning of a file in one packet, and # generally send a file with full packets. # # FreeBSD man page for tcp(4) says: # TCP_NOPUSH By convention, the sender-TCP will set the "push" # bit, and begin transmission immediately (if # permitted) at the end of every user call to # write(2) or writev(2). When this option is set to # a non-zero value, TCP will delay sending any data # at all until either the socket is closed, or the # internal send buffer is filled. # # This is like the TCP_CORK socket option on Linux. It's only # effective when sendfile is used. ########################################################################### tcp_nopush on; sendfile on; ########################################################################### # Limit how long a keep-alive client connection will stay open at the # server end. ########################################################################### keepalive_timeout 65; ########################################################################### # Enable the use of underscores in client request header fields. ########################################################################### underscores_in_headers on; [... server { ... } blocks deleted ...]
How Strict Regarding TLS Versions and Ciphers?
My server runs TLS 1.3 because it's the state of the art. It also runs TLS 1.2 because a significant number of clients still run that. For the three-month period of August through October, 2023, I saw that about 7% of the clients ran TLS 1.2.
$ awk '{print $12}' /var/www/log/cromwell-intl-access.log | sort | uniq -c | sort -nr 2743473 TLSv1.3 208603 TLSv1.2
I wouldn't want to give up 7% of ad revenue, so I continued to prefer TLSv1.3, tolerate TLSv1.2, and accept nothing else. Make your own decision carefully, based on the clients you want, and need, to support. I have a story that illustrates that.
In 2016 I went to Nashville to teach an introductory cybersecurity class to Tennessee state government employees. It was a very introductory course, one in which the more qualified students were often frustrated by the lack of meaningful hands-on exercises. So I always added what I could. One thing I always did was tell them about the Qualys scanner at ssllabs.com and have them run a scan of some system that they thought needed to be secure. "For example, your bank or some other place that you do financial transactions."
As usual, I strolled to the back of the room and chatted with some of the people sitting back there while we waited for people to get to the correct site and then run a scan that can take two or three minutes.
Looking toward the front of the room, I had never seen this before — bright red F scores were appearing on everyone's screens.
Usually almost everyone scans their bank, and things aren't perfect but they aren't too bad overall. This week, however, everyone had scanned a public-facing state government server in the department where they worked. And, the state-wide web server template had serious problems.
Most of the room looked terrified and asked me what they should do. I told them that they should tell someone about this. I had no idea as to exactly who they should inform, but someone needs to know about this.
I put my scan of my server up on the projector. We walked through the major differences, we me telling them that they could follow the links in the Qualys scan result for more details.
I went back to Nashville two months later. The classes ran in a large state office building which had a cafeteria on the ground floor. I would get breakfast there each day, grits and sweet tea.
A guy who had been in the earlier class saw me the first morning and came over to talk. He was complementing me on the class, he had really enjoyed it and gotten a lot out of it. I remembered that he had particularly enjoyed the Qualys scan and been interested in discussing the results. So, I told him that I planned to have them do it again, and hopefully things would work out better. He said that the state IT department had made changes since my last visit. "And, um, let me tell you what else."
The next Monday morning after the course, he set out get to the servers he ran to an A+ score. Within a couple of day, he had. And a few days after that, the state IT department jumped on him.
Their automated scans had detected that his department's servers were no longer adequately backward compatible with old browsers. A state government is obligated to provide services to all residents, at least within reason. But it can't require that all residents immediately upgrade their operating systems to something within the past year or two.
Both of us commented on the absurdity of a state IT department that automatically detected systems that were too secure, but ignored those with many obvious problems for which we've had solutions for several years. And so, later that week we did the Qualys scan and everyone found an A- score. Things had improved! I could tell that group about how much things had changed for the better in two months. And, warn them to not get too enthusiastic about locking down their department's servers.
Here are the other statistics I saw in late 2023.
HTTP version (I have no idea what's going on with the bots claiming to run HTTP/1.0)
$ awk '{print $8}' /var/www/log/cromwell-intl-access.log | sort | uniq -c | sort -nr 2710679 HTTP/1.1 223717 HTTP/2.0 12901 HTTP/3.0 4783 HTTP/1.0
Key Exchange
$ awk '{print $13}' /var/www/log/cromwell-intl-access.log | sort | uniq -c | sort -nr 2744018 X25519 155687 secp521r1 43443 secp384r1 8910 X448
Cipher
$ awk '{print $14}' /var/www/log/cromwell-intl-access.log | sort | uniq -c | sort -nr 2739818 TLS_AES_256_GCM_SHA384 191856 ECDHE-ECDSA-AES256-GCM-SHA384 11676 ECDHE-ECDSA-CHACHA20-POLY1305 4877 ECDHE-ECDSA-AES128-GCM-SHA256 3640 TLS_CHACHA20_POLY1305_SHA256 184 ECDHE-RSA-AES256-GCM-SHA384 4 ECDHE-RSA-CHACHA20-POLY1305 3 ECDHE-RSA-AES128-GCM-SHA256 3 DHE-RSA-AES256-GCM-SHA384
Where Are The Quantum-Safe Clients?
So far there's curl
and,
well, that's pretty much it.
You can go to chrome://flags
in Chrome
and search for the experimental Kyber
support
and enable it.
It will list 0x6399 as a support group for key exchange,
indicating a hybrid of x25519 and Kyber1024.
But that will only be recognized by some of the
quantum-safe libraries.
To test with curl
do something like the
following.
Every page on my site includes a small block at the bottom
listing the HTTP and TLS protocols plus the cryptography used.
See 0x023d
in the output,
the TLS identifier used by liboqs for Kyber1024.
$ curl https://cromwell-intl.com --curves kyber1024 | grep Crypto
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
1<a href="/open-source/google-freebsd-tls/apache-http2-php.html">Protocols</a>: HTTP/2.0 / TLSv1.3<br><a href="/open-source/nginx-openssl-quantum-safe/">Crypto</a>: 0x023d / TLS_AES_256_GCM_SHA384</p>
00 26580 0 26580 0 0 102k 0 --:--:-- --:--:-- --:--:-- 102k
At the time of writing this (December 2023), quantum-safe cryptography across web sites is just getting started. US NIST had published draft federal standards for post-quantum cryptography, and the public comment period had closed near the end of November.
Different implementations were using different hexadecimal identifiers, there was no common naming scheme for the PQC algorithms. Chrome's experimental hybrid x25519-Kyber768Draft00 support describes it in the TLS Client Hello message as 0x6399. Cloudflare's PQC test server recognizes that and runs that specific hybrid and draft algorithm. Mine doesn't.
It will be a while before we get a really final standard, including a standard set of identifiers, and liboqs and other shared libraries support the algorithm and identifier suite, and those libraries start to be used by browsers.
Meanwhile people are enthusiastically suggesting that you simply recompile your own version of Chrome from source, linking it with all the appropriate shared libraries. Um, no. That would probably take a full day of compiling for each round of trying to figure out how to specify all the include files, shared libraries, and more.
Nginx and liboqs lets my server be ahead of the trend, but numerous course corrections will be needed before all this becomes common.
Two server { ... } Blocks for a Site
Now for the things that must be defined for each site,
one server
block for HTTP,
mostly to log and redirect the client to HTTPS,
then a server
block for HTTPs.
In the second block, for HTTPS, you will see a line
http2 on
for HTTP/2,
along with two lines about listening on port 443:
listen 443 quic; listen 443 ssl; https on;
HTTP/3 runs on top of QUIC, "Quick UDP Internet Connection". That uses UDP to roughly simulate TCP, with much faster connection setup and a solution to the TCP "head-of-line blocking" problem.
QUIC runs on UDP port 443, so you will need to also open that on a firewall between your server and the rest of the Internet.
You also need an HTTPS type DNS record.
$ dig cromwell-intl.com HTTPS
; <<>> DiG 9.18.20 <<>> cromwell-intl.com HTTPS
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 58569
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;cromwell-intl.com. IN HTTPS
;; ANSWER SECTION:
cromwell-intl.com. 3600 IN HTTPS 1 . alpn="h3,h2" ipv4hint=35.203.182.32
;; Query time: 55 msec
;; SERVER: 169.254.169.254#53(169.254.169.254) (UDP)
;; WHEN: Sat Dec 02 20:47:22 UTC 2023
;; MSG SIZE rcvd: 79
If the client assumed that the hostname started "www.", it gets back both the CNAME record correcting that assumption and the HTTPS record info it was looking for.
$ dig www.cromwell-intl.com HTTPS
; <<>> DiG 9.18.20 <<>> www.cromwell-intl.com https
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 20171
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;www.cromwell-intl.com. IN HTTPS
;; ANSWER SECTION:
www.cromwell-intl.com. 3600 IN CNAME cromwell-intl.com.
cromwell-intl.com. 3600 IN HTTPS 1 . alpn="h3,h2" ipv4hint=35.203.182.32
;; Query time: 77 msec
;; SERVER: 169.254.169.254#53(169.254.169.254) (UDP)
;; WHEN: Sat Dec 02 20:56:48 UTC 2023
;; MSG SIZE rcvd: 97
A potential QUIC client can request an HTTPS DNS record for the server and find that it runs HTTP/2 and HTTP/3 or QUIC, along with a "hint" of the IPv4 address so it doesn't have to do a second DNS lookup. Yes, you can also include an IPv6 hint, but to my surprise, Google Cloud platform still doesn't support IPv6 (unless you're paying for a load-balancer).
You also need to add an Alt-Svc
header
to the response, see the details below.
You can test your HTTP/3 / QUIC service at http3check.net.
As for experimenting with QUIC and HTTP/3 from the client end,
on Chrome go to chrome://flags/
and search for QUIC.
Change the setting to Enabled and restart Chrome.
SmashingMagazine.com on HTTP/3:
Part 1: Core Concepts
Part 2: Performance Improvements
Part 3: Practical Deployment Options
You will see that each site has dual certificates, one for a 384-bit elliptic curve key pair, and the other for a 4096-bit RSA key pair, both signed by Let's Encrypt.
The HTTPS server
blocks also set HTTPS
security headers.
See the comments for further details.
You can check yours at:
cspvalidator.org
report-uri.io
securityheaders.io
Here are the HTTP and HTTPS server
blocks for cromwell-intl.com
,
again with non-comment content in bold.
[... early blocks deleted ...] ########################################################################### # HTTP Server: Log and redirect to HTTPS. ########################################################################### server { listen 80; server_name cromwell-intl.com www.cromwell-intl.com; access_log /var/www/logs/cromwell-intl-access.log main; error_log /var/www/logs/cromwell-intl-error.log; # Don't log bulky data, even HTTP requests that will be redirected. location ~* \.(jpg|jpeg|png|gif|ico|css|js|ttf)$ { access_log off; } root /usr/local/nginx/html; # Redirect to HTTPS with same hostname, don't cause a # DNS lookup *and* a new protocol at the same time. return 301 https://$http_host$request_uri; } ########################################################################### # Now the first of the real HTTPS servers. ########################################################################### server { # FreeBSD 14.0 comes with: # nginx 1.24.0_12,3 # nginx-devel 1.25.2_7 # As stated here: # https://nginx.org/en/docs/quic.html # "Support for QUIC and HTTP/3 protocols is available since 1.25.0." listen 443 quic; listen 443 ssl; http2 on; server_name cromwell-intl.com www.cromwell-intl.com; # Redirect if hostname starts "www.". if ($http_host = www.cromwell-intl.com) { return 301 https://cromwell-intl.com$request_uri; } root /usr/local/nginx/html; ####################################################################### ## Logging and cache control for images, CSS, JavaScript, fonts ####################################################################### access_log /var/www/logs/cromwell-intl-access.log main; error_log /var/www/logs/cromwell-intl-error.log; location ~* \.(jpg|jpeg|png|gif|ico|css|js|ttf)$ { ############################################################### # Logging ############################################################### # Don't log bulky data. # HOWEVER, it seems we get a log entry no matter what # if the request was referred in by something else. For # example, fetching an image because of Google search. access_log off; ############################################################### # Caching ############################################################### # Tell client to cache bulky data for 7 days, which is # the Google Pagespeed recommendation / requirement. expires 7d; # "Pragma public" = Now rather outdated, skip it. # "public" = Cache in browser and any intermediate caches. # "no-transform" = Caches may not modify my data formats. add_header Cache-Control "public, no-transform"; # Tell client and intermediate caches to understand that # compressed and uncompressed versions are equivalent. # Goes with gzip_vary below. More details here: # https://blog.stackpath.com/accept-encoding-vary-important add_header Vary "Accept-Encoding"; ############################################################### # Block image hotlinking. I changed "rewrite" to "return" # in the description provided here: # http://nodotcom.org/nginx-image-hotlink-rewrite.html # Also see: # http://nginx.org/en/docs/http/ngx_http_referer_module.html ############################################################### valid_referers none blocked ~\.google\. ~\.printfriendly\. ~\.bing\. ~\.yahoo\. ~\.baidu.com server_names ~($host); if ($invalid_referer) { return 301 https://$host/pictures/denied.png; } } # Needed because of above hotlink redirection. location = /pictures/denied.png { } ####################################################################### # Certificates and private keys. # Send both ECC and RSA certificates. # Generating and renewing Let's Encrypt certificates described here: # https://cromwell-intl.com/open-source/google-freebsd-tls/tls-certificate.html ####################################################################### # ECC ssl_certificate /usr/local/etc/letsencrypt/ecc-live/cromwell-intl.com/fullchain.pem; ssl_certificate_key /usr/local/etc/letsencrypt/ecc-live/cromwell-intl.com/privkey.pem; # RSA ssl_certificate /usr/local/etc/letsencrypt/rsa-live/cromwell-intl.com/fullchain.pem; ssl_certificate_key /usr/local/etc/letsencrypt/rsa-live/cromwell-intl.com/privkey.pem; ####################################################################### # Security headers ####################################################################### # HTTP/3 over QUIC # Should need just the first one, eventually: h3=":443";ma=86400 # However, some testing sites like https://domsignal.com/ and thus # probably lots of browsers need h3-29 or h3-Q050. add_header Alt-Svc 'h3=":443";ma=86400, h3-25=":443";ma=86400, h3-29=":443";ma=86400, h3-Q050=":443";ma-86400, h3-Q046=":443";ma=86400, quic=":443";ma=86400'; # HSTS # The HSTS Preloading mechanism uses a list compiled by Google, # and is used by major browsers such as Chrome, Firefox, Opera, # Safari, and Edge. add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"; # OCSP Stapling # Support for OCSP Must-Staple is done in the CSR when the certificate # is generated. ssl_stapling on; ssl_stapling_verify on; # X-Frame-Options add_header X-Frame-Options "SAMEORIGIN"; # Turn on XSS / Cross-Site Scripting protection in browsers. # "1" = on, # "mode=block" = block an attack, don't try to sanitize it add_header X-Xss-Protection "1; mode=block"; # Tell the browser (Chrome and Explorer, anyway) not to "sniff" the # content and try to figure it out, but simply use the MIME type # reported by the server. # This means that all files named "*.jpg" must be JPEG, and so on! add_header X-Content-Type-Options "nosniff"; # I set Referrer-Policy liberally. I think referrer info # can be helpful without being absolutely trustworthy, # and I don't have any scandalous or sensitive URLs. # See https://scotthelme.co.uk/a-new-security-header-referrer-policy/ add_header Referrer-Policy "no-referrer-when-downgrade"; # Content Security Policy # *If* a client could add a stylesheet, then they could drastically # change visibility and appearance. Also, there is a way to steal # sensitive data from within a page. See: # https://www.mike-gualtieri.com/posts/stealing-data-with-css-attack-and-defense # However, none of my pages have forms or handle sensitive data. # So, I feel safe using 'unsafe-inline' below. # # NOTE that if I didn't do that, I would have to convert every single # 'style="..."' string to a CSS class instead. The below limits CSS # to coming from my site and *.googleapis.com and cdn.jsdelivr.net # while allowing inline 'style="..."', which seems plenty safe for me. # Also see the above comment block about setting $noncestring, and # why my attempt to replace 'unsafe-inline' with 'nonce-$noncestring' # failed. # # NOTE that adding a similar parameter for style-src breaks both # Google AdSense and Infolinks ads, even with 'unsafe-inline'. add_header Content-Security-Policy "upgrade-insecure-requests; style-src https://cromwell-intl.com https://*.googleapis.com https://cdn.jsdelivr.net 'unsafe-inline'; frame-ancestors 'self';"; # Permissions Policy, see: # https://scotthelme.co.uk/goodbye-feature-policy-and-hello-permissions-policy/ add_header Permissions-Policy "fullscreen=(self)"; # As recommended by https://frog.tips/ add_header X-Frog-Unsafe "0"; # Check security headers at: # https://cspvalidator.org/ # https://report-uri.io/home/analyse # https://securityheaders.io/ # Include the TLS protocol version and negotiated cipher # in the HTTP headers, so we can capture them in the logs. add_header X-HTTPS-Protocol $ssl_protocol; add_header X-HTTPS-Cipher $ssl_cipher; add_header X-HTTPS-Curve $ssl_curve; ####################################################################### # Process all *.html files as PHP. The php-fam service must be # running, listening on TCP/9000 on localhost only. # The try_files line serves the 404 error page if they ask for # a non-existent file. Without that, you get a cryptic error AND # these is a security hole, see: # http://forum.nginx.org/read.php?2,88845,page=3 ####################################################################### location ~ \.html$ { try_files $uri =404; include fastcgi_params; fastcgi_index Index.html; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param TLS_PROTOCOL $ssl_protocol; fastcgi_param TLS_CIPHER $ssl_cipher; fastcgi_param TLS_CURVE $ssl_curve; fastcgi_param QUERY_STRING $query_string; fastcgi_param REQUEST_METHOD $request_method; fastcgi_param CONTENT_TYPE $content_type; fastcgi_param CONTENT_LENGTH $content_length; fastcgi_param SCRIPT_NAME $fastcgi_script_name; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param REQUEST_URI $request_uri; fastcgi_param DOCUMENT_URI $document_uri; fastcgi_param DOCUMENT_ROOT $document_root; fastcgi_param SERVER_PROTOCOL $server_protocol; fastcgi_param REMOTE_ADDR $remote_addr; fastcgi_param REMOTE_PORT $remote_port; fastcgi_param SERVER_ADDR $server_addr; fastcgi_param SERVER_PORT $server_port; fastcgi_param SERVER_NAME $server_name; fastcgi_hide_header X-Powered-By; fastcgi_pass 127.0.0.1:9000; } ####################################################################### # Now the URL rewriting / redirection rules. # A note from: # https://www.nginx.com/resources/wiki/start/topics/tutorials/config_pitfalls/ # BAD: # rewrite ^/(.*)$ http://example.com/$1 permanent; # GOOD: # rewrite ^ http://example.com$request_uri? permanent; # BETTER: # return 301 http://example.com$request_uri; # # For rewrites: # rewrite OLD_REGEX NEW permanent; # "permanent" = HTTP 301, permanent redirect # "redirect" = HTTP 302, temporary redirect ####################################################################### [... many lines of rewriting and redirection rules for this site...] } [... HTTP and HTTPS server { ... } blocks for the other sites ...] }
Results — A+ from Qualys / ssllabs.com
Here's the score, you can check it yourself.
Further Notes
What's the Point of Asymmetric Encryption? How Does Asymmetric Cryptography Work? Quantum Computing and Quantum-Safe Cryptography
US NIST Post-QUantum Cryptography Project Cloudflare discussion of PQC (April 2022) Cloudflare Research page to test your browser The SSL Store blog on Google Chrome PQC support (August 2023) Open Quantum Safe Project Open Quantum Safe at Github open-quantum-safe / liboqs open-quantum-safe / oqs-provider oqs-provider algorithm names and IDs Lan Tian's Blog