Servers.

Building Nginx and OpenSSL from Source

The Project So Far

Getting Started: Log Analysis and TLS Versions Configuring Nginx for Security and Performance

This page explains how to build both the cryptographic toolkit OpenSSL and the web server Nginx from source code. We will include the Open Quantum Safe library for post-quantum or quantum-safe key encapsulation mechanism (KEM) and digital signature algorithms.

The asummetric cryptography we have been using for key agreement and digital signatures — Diffie-Hellman and Elliptic-Curve Cryptography — could be broken with a quantum computer with an adequate number of adequately stable qubits. We haven't seen one yet, but it seems to be on the way.

If you want to skip the quantum-safe part, you could proceed directly to configuring Nginx for an A+ Qualys score.

But, let's see how to be quantum-safe!

The steps are:
• Download OpenSSL, Nginx, and oqs-provider source code
• Build and install OpenSSL
• Build and install oqs-provider
• Configure OpenSSL to use oqs-provider
• Build and install Nginx
• Configure Nginx to use liboqs and oqs-provider

🚧 The quantum-safe sections of this are still under development and test! 🚧

What Do We Need?

Try Google Cloud Platform and receive $50

When I first did this project in September, 2018, the Apache web server did not support TLS 1.3. It didn't matter if you had the needed shared libraries. Apache still thought that a directive to include TLS 1.3 was incorrect configuration syntax. From the Apache release notes back then:

This release is compatible with OpenSSL versions from 0.9.8a to 1.1.0 only, and does not support TLSv1.3. Future releases of httpd 2.4 are expected to add compatibility with OpenSSL 1.1.1 and enable support for TLSv1.3.

There were some patches that could make it happen, but that would make ongoing maintenance too much of a hassle.

Nginx was the clear choice of web server. It uses the OpenSSL cryptographic libraries to do TLS.

FreeBSD on Google Compute Platform

I was doing this project on FreeBSD, where OpenSSL is included in the base operating system and Nginx is a standard package. However, FreeBSD 11.2-RELEASE included earlier versions: OpenSSL 1.0.2o and Nginx 1.14.0. (That version of Nginx supported TLS 1.3, but it was compiled to use the standard system OpenSSL libraries, which I didn't want to replace.) At the time, Mageia Linux had OpenSSL 1.0.2p, and Mint Linux had OpenSSL 1.1.0g.

The FreeBSD Ports system provides a way of getting more or less the same things. I needed to build Nginx and OpenSSL from source.

Getting The Software

First, download the current version of Nginx.

OpenSSL 1.1.1 had been released on September 11th, 2018, just 3 to 4 weeks after RFC 8446 was published, formally defining TLS 1.3. There had been a series of 28 drafts of that document. Some earlier versions of OpenSSL support Draft #28, which ended up being the final definition. But let's start with the official versions!

Three years later, in September 2021, OpenSSL 3.0.0 was released. Download the current version of OpenSSL.

Verifying
Digital
Signatures

Download the compressed archive files, along with associated digital signatures and hashes.
nginx-x.y.z.tar.gz
nginx-x.y.z.tar.gz.asc
openssl-x.y.z.tar.gz
openssl-x.y.z.tar.gz.sha256

Verify digital signatures and hashes, and make sure you're getting the real software from the original organization.

Here's what I get for the SHA-2-256 hashes of recent versions of the uncompressed archives:

$ openssl sha256 *.tar
SHA256(nginx-1.25.2.tar)= ce89d9ee713d3e8989a5b7ba2f6cc32b3c552afc50ece15da989ebf6124b4192
SHA256(openssl-3.1.2.tar)= 293f8443d1a57308c6a9dc1d3138aaa5cf94e9c95025458bc08e81867c28762f

Building The Software

Where should the software be installed? I have to decide that before I start building it. The standard locations are:

I had my new custom-built OpenSSL install itself in its default location under:
/usr/local/ssl/
and that will hold the configuration file openssl.cnf and other pieces. The executable will be:
/usr/local/bin/openssl
and the new libraries will be:
/usr/local/lib/libcrypto.a
/usr/local/lib/libcrypto.so.3
/usr/local/lib/libssl.a
/usr/local/lib/libssl.so.3

However, I put the custom-built Nginx under:
/usr/local/nginx-version/
This way I could have multiple custom-built versions of Nginx installed at the same time. I could test the very latest but still have the previous one(s) available as a fall-back. Then I could easily remove an older version I no longer need.

You also need to decide whether or not you really want to install the latest version of OpenSSL, versus simply use it. When you build Nginx, it compiles the OpenSSL code to create static libraries libssl.a and libcrypto.a and then embeds them within the nginx binary. So, you don't have to independently build OpenSSL to build the latest Nginx using the latest OpenSSL.

Accelerating the Software Compilation

The compile times reported in the following are what I saw on my server, a FreeBSD system running in the Google Cloud Platform. Running dmesg immediately after booting reports this about the CPU and memory:

[... lines omitted ...]
CPU: Intel(R) Xeon(R) CPU @ 2.20GHz (2200.28-MHz K8-class CPU)
  Origin="GenuineIntel"  Id=0x406f0  Family=0x6  Model=0x4f  Stepping=0
  Features=0x1f83fbff<FPU,VME,DE,PSE,TSC,MSR,PAE,MCE,CX8,APIC,SEP,MTRR,PGE,MCA,CMOV,PAT,PSE36,MMX,FXSR,SSE,SSE2,SS,HTT>
  Features2=0xfefa3203<SSE3,PCLMULQDQ,SSSE3,FMA,CX16,PCID,SSE4.1,SSE4.2,x2APIC,MOVBE,POPCNT,AESNI,XSAVE,OSXSAVE,AVX,F16C,RDRAND,HV>
  AMD Features=0x2c100800<SYSCALL,NX,Page1GB,RDTSCP,LM>
  AMD Features2=0x121<LAHF,ABM,Prefetch>
  Structured Extended Features=0x1c2ffb<FSGSBASE,TSCADJ,BMI1,HLE,AVX2,FDPEXC,SMEP,BMI2,ERMS,INVPCID,RTM,NFPUSG,RDSEED,ADX,SMAP>
  Structured Extended Features3=0xac000400<MD_CLEAR,IBPB,STIBP,ARCH_CAP,SSBD>
  XSAVE Features=0x1<XSAVEOPT>
  IA32_ARCH_CAPS=0x4c<RSBA,SKIP_L1DFL_VME>
  TSC: P-state invariant
Hypervisor: Origin = "KVMKVMKVM"
real memory  = 1073741824 (1024 MB)
avail memory = 1004154880 (957 MB)
Event timer "LAPIC" quality 600
ACPI APIC Table: <Google GOOGAPIC>
FreeBSD/SMP: Multiprocessor System Detected: 2 CPUs
FreeBSD/SMP: 1 package(s) x 1 core(s) x 2 hardware threads
[... many more lines omitted ...]

The following describes compiling three packages, each of which requires about an hour. With one CPU that supports hyperthreading, you might think that the -j 2 option would make the builds run faster. Yes, usually that would be the case — use a parameter that is the number of CPUs, twice that if they're hyperthreaded.

However, my server is on the Google Cloud platform, at the Free Tier level. That allows just a short burst of full CPU utilization and then throttles it to 50% of possible maximum. Compiling takes longer, but for me the price is right. Here's what the Google Cloud console showed me about CPU utilization while make -j 2 had been running for almost 50 minutes to build OpenSSL:

Google Cloud throttling CPU utilization during 'make -j2' Google Cloud monitoring dashboard

Installing the Open Quantum Safe Library

The liboqs package may be available for your operating system. On FreeBSD you can add it with:

# pkg install liboqs

On FreeBSD use pkg info -l liboqs to verify the package adds several *.h files in /usr/local/include/oqs and a shared library in /usr/local/lib/ with timestamps from when the package was built.

$ ls -l /usr/local/lib/liboqs.*
lrwxr-xr-x  1 root  wheel        11 Jul  5  2022 /usr/local/lib/liboqs.so@ -> liboqs.so.0
lrwxr-xr-x  1 root  wheel        19 Jul  5  2022 /usr/local/lib/liboqs.so.0@ -> liboqs.so.0.7.2-dev
-rwxr-xr-x  1 root  wheel  10026872 Jul  5  2022 /usr/local/lib/liboqs.so.0.7.2-dev*

To build and install your own liboqs library, see the project's GitHub page for full details. But here's how to download the source code and build a static liboqs library. I did this after installing the available compiled package:

$ git clone https://github.com/open-quantum-safe/liboqs.git
$ cd liboqs
$ cmake -S . -B _build
[... narrative output ...]
-- Configuring done
-- Generating done
-- Build files have been written to: /home/cromwell/liboqs/_build
$ cmake --build _build
[... narrative output, takes 55-58 minutes ...]
$ su root -c 'cmake --install _build'
[... narrative output ...]
$ ls -l /usr/local/lib/liboqs.*
-rw-r--r--  1 root  wheel   9350502 Apr  4 16:48 /usr/local/lib/liboqs.a
lrwxr-xr-x  1 root  wheel        11 Jul  5  2022 /usr/local/lib/liboqs.so@ -> liboqs.so.0
lrwxr-xr-x  1 root  wheel        19 Jul  5  2022 /usr/local/lib/liboqs.so.0@ -> liboqs.so.0.7.2-dev
-rwxr-xr-x  1 root  wheel  10026872 Jul  5  2022 /usr/local/lib/liboqs.so.0.7.2-dev*

About FIPS Compliance

FIPS Documents

Note that the FIPS 140-3 successor was approved on 22 March 2019 and became effective on 22 September 2019, although various documents continue to reference FIPS 140-2.

TL;DR: If you need FIPS 140 compliance, add enable-fips to the OpenSSL ./config command, and add --with-openssl-opt=enable-fips to the Nginx ./configure command.

"FIPS" refers to a formal U.S. Government standard, FIPS-140 (or the Federal Information Processing Standard 140). Versions FIPS 140-2 and 140-3 were published on 25 May 2001 and 22 March 2019, respectively. Those documents are short, just defining the legal requirements. The approved algorithms for encryption and decryption, hashing, and hashed message authentication codes (or HMAC) are defined in other publications — NIST Special Publications SP 800-140C and SP 800-140D, which in turn reference ISO/IEC 19790:2012(E) and ISO/IEC 24759:2017(E).

People casually speak of "FIPS" or "FIPS compliant" without carefully thinking about what they mean or what the terminology may imply.

Let's be careful. Saying that a software package or shared library "implements the FIPS cipher suite" or that it is "FIPS compliant" might mean any of the following, in increasing order of significance and formality:

  1. It implements all the algorithms defined in the current NIST SP 800-140[CD] documents.
  2. Like #1 but also that the source code has been audited by U.S. NIST (National Institute of Standards and Technology) or the Canadian Centre for Cyber Security, and they found it to be a complete and correct implementation of the cipher suite.
  3. Like #2 but also that the vendor (of the distribution, for Linux, or the organization for, e.g., FreeBSD) has been approved as a trusted group with an approved patch notification system, including digitally signed update packages. This includes both technical analysis of the code and organizational approval of the vendor.

Finally, you can, and for U.S. Government compliance you must, disable the use of non-FIPS ciphers and hash functions. First, do this for the kernel to restrict its use of algorithms in IPsec and encrypted storage. Add fips=1 to the kernel command line by adding it to the file /etc/default/grub, rebuilding the GRUB configuration files, and rebooting.

# grep GRUB_CMDLINE_LINUX /etc/default/grub
GRUB_CMDLINE_LINUX_DEFAULT="quiet fips=1"
GRUB_CMDLINE_LINUX="quiet fips=1"
# update-grub
# reboot

Beware that systemd meddling on RHEL 8 and CentOS Stream 8 very likely breaks the above simple solution, forcing you to deal with the file you can find as:
/boot/loader/entries/$(cat /etc/machine-id)-$(uname -r).conf
See these pages for hints:
https://access.redhat.com/solution/3710121
https://access.redhat.com/solution/3766391
https://systemd.io/BOOT_LOADER_SPECIFICATION
https://www.freedesktop.org/wiki/Specifications/BootLoaderSpec/

Nginx FIPS
Configuration
Apache FIPS
Configuration
OpenSSH FIPS
Configuration

Then you will need to reconfigure applications to only use the approved algorithms. See my next page in this series for how to do that for Nginx, or see this page for Apache's httpd web server. For the SSH service, edit /etc/ssh/sshd_config and specify FIPS algorithms for the Ciphers and MACs parameters. See the sshd_config manual page for details.

Level #1 in my numbered list above is basically "Hey, we tried" and no more, while #3 is required in a U.S. Department of Defense setting. What do you need? If you are working for U.S. DoD or some other U.S. Government agencies, or industry working for them, you need #3. Otherwise, you must decide this for yourself.

Another way to think of this is that #1 in my list is about capability, but without any formal analysis or trust, while #3 is about analysis and certification of code and processes by a trusted entity. Unless you must operate within U.S. Government constraints, there is no simple "correct answer" here.

The OpenSSL 3.0 FIPS module was certified in August 2022.

Specific implementations and packaging by Oracle, Canonical/Ubuntu, Red Hat, and others, were under review by March 2023.

Extract The OpenSSL Archive

Extract the OpenSSL source code archive. In the following examples, I did this in my home directory.

$ tar xf openssl-3.1.2.tar.xz

Configure OpenSSL

This is easy. Beginning with OpenSSL 3.0.2, Perl must be installed. With the enable-fips parameter added, it would build the FIPS module but it would not enforce its use. I'm not specifying:
--prefix=/usr/local/ssl
--openssldir=/usr/local/ssl
because those are the defaults. See the OpenSSL project's Compilation and Installation page for details on the available configuration options.

$ cd openssl-3.1.2
$ ./config
Configuring OpenSSL version 3.1.2 for target BSD-x86_64
Using os-specific seed configuration
Creating configdata.pm
Running configdata.pm
Creating Makefile.in
Creating Makefile
Created include/openssl/configuration.h

**********************************************************************
***                                                                ***
***   OpenSSL has been successfully configured                     ***
***                                                                ***
***   If you encounter a problem while building, please open an    ***
***   issue on GitHub <https://github.com/openssl/openssl/issues>  ***
***   and include the output from the following command:           ***
***                                                                ***
***       perl configdata.pm --dump                                ***
***                                                                ***
***   (If you are new to OpenSSL, you might want to consult the    ***
***   'Troubleshooting' section in the INSTALL file first)         ***
***                                                                ***
**********************************************************************

Build and Install OpenSSL

I will need to build and install OpenSSL under /usr/local so I can specify its use of oqs-provider.

The 1.1.1x series of OpenSSL required 12–14 minutes to compile on a single CPU, an Intel Xeon CPU running at 2.20 GHz.

On that same platform, the 3.1.x version requires 75 minutes to compile! Then the installation takes another 20 to 22 minutes with all its manual page construction.

As for the make test step, your newly compiled software should just simply be ready to work correctly. But you can run that test to be safe and certain. It takes about 70 minutes.

$ make
[... much output, about one hour of compiling on a single CPU ...]
$ make test
[... test output, this runs for about 70 minutes ...]
$ su root -c 'make install'
[... asked for root password, output of installing thousands of files,
     including generating manual pages with pod2man and mkpod2html.pl,
     takes 20-24 minutes to run ...]

Once it's installed, I can test it. With its libraries in the expected /usr/local/lib I can use it without having to set the LD_LIBRARY_PATH environment variable.

$ /usr/local/bin/openssl version
OpenSSL 3.1.2 1 Aug 2023 (Library: OpenSSL 3.1.2 1 Aug 2023)
$ /usr/local/bin/openssl version -a
OpenSSL 3.1.2 1 Aug 2023 (Library: OpenSSL 3.1.2 1 Aug 2023)
built on: Fri Aug 11 11:43:36 2023 UTC
platform: BSD-x86_64
options:  bn(64,64)
compiler: cc -fPIC -pthread -Wa,--noexecstack -Qunused-arguments -Wall -O3 -DL_ENDIAN -DOPENSSL_PIC -D_THREAD_SAFE -D_REENTRANT -DOPENSSL_BUILDING_OPENSSL -DNDEBUG
OPENSSLDIR: "/usr/local/ssl"
ENGINESDIR: "/usr/local/lib/engines-3"
MODULESDIR: "/usr/local/lib/ossl-modules"
Seeding source: os-specific
CPUINFO: OPENSSL_ia32cap=0xfefa32035f8bffff:0x1c2ffb

Notice that the openssl binary is linked to use the libcrypto.so and libssl.so shared libraries that the build created and installed in /usr/local/lib:

$ ldd /usr/local/bin/openssl
/usr/local/bin/openssl:
        libssl.so.3 => /usr/local/lib/libssl.so.3 (0x11a726b70000)
        libcrypto.so.3 => /usr/local/lib/libcrypto.so.3 (0x11a725d63000)
        libthr.so.3 => /lib/libthr.so.3 (0x11a727b1d000)
        libc.so.7 => /lib/libc.so.7 (0x11a728262000)
        [vdso] (0x7ffffffff5d0)
F

We will get our quantum-safe capability through the providers supported by OpenSSL 3. Notice that OpenSSL 1.* does not support providers, while OpenSSL 3 does.

$ which openssl
/usr/bin/openssl
$ openssl version
OpenSSL 1.1.1o-freebsd  3 May 2022
$ openssl list -providers
list: Option unknown option -providers
list: Use -help for summary.
$ /usr/local/bin/openssl version
OpenSSL 3.1.2 1 Aug 2023 (Library: OpenSSL 3.1.2 1 Aug 2023)
$ /usr/local/bin/openssl list -providers
Providers:
  default
    name: OpenSSL Default Provider
    version: 3.1.2
    status: active

As an alternative: I can instead install the FreeBSD openssl-devel package. It includes 135 *.h files in /usr/local/include/ and these libraries, notice the .so.12 from the FreeBSD package instead of .so.3 from what I built:

$ ls -l /usr/local/lib/{libcrypto,libssl}.*
-rw-r--r--  1 root  wheel  8945540 Feb 20 05:01 /usr/local/lib/libcrypto.a
lrwxr-xr-x  1 root  wheel       15 Feb 20 05:01 /usr/local/lib/libcrypto.so@ -> libcrypto.so.12
-rwxr-xr-x  1 root  wheel  4472984 Feb 20 05:01 /usr/local/lib/libcrypto.so.12*
-rw-r--r--  1 root  wheel  1303186 Feb 20 05:01 /usr/local/lib/libssl.a
lrwxr-xr-x  1 root  wheel       12 Feb 20 05:01 /usr/local/lib/libssl.so@ -> libssl.so.12
-rwxr-xr-x  1 root  wheel   690024 Feb 20 05:01 /usr/local/lib/libssl.so.12*

Building oqsprovider

At least this package builds quickly! However...

My naive approach was to simply follow the provided instructions:

$ git clone https://github.com/open-quantum-safe/oqs-provider
$ cd oqs-provider
$ cmake -DOPENSSL_ROOT_DIR=/usr/local -S . -B _build
[... narrative output ...]
-- Configuring done
-- Generating done
-- Build files have been written to: /home/cromwell/oqs-provider/_build
$ cmake --build _build
[... initial compilation steps ...]

However, this resulted in many errors about dynamic relocation:

[... earlier successful compilation steps ...]
[ 64%] Building C object oqsprov/CMakeFiles/oqsprovider.dir/oqs_endecoder_common.c.o
[ 68%] Building C object oqsprov/CMakeFiles/oqsprovider.dir/oqs_decode_der2key.c.o
[ 72%] Building C object oqsprov/CMakeFiles/oqsprovider.dir/oqsprov_bio.c.o
[ 76%] Linking C shared library ../lib/oqsprovider.so
ld: error: can't create dynamic relocation R_X86_64_32S against local symbol in readonly segment; recompile object files with -fPIC or pass '-Wl,-z,notext' to allow text relocations in the output
>>> defined in ../install/lib/libqsc_key_encoder.a(encoding_qsc.c.o)
>>> referenced by encoding_qsc.c
>>>               encoding_qsc.c.o:(qsc_encoding_by_name_oid) in archive ../install/lib/libqsc_key_encoder.a

ld: error: can't create dynamic relocation R_X86_64_64 against symbol: Dilithium_R3_4x4_encodings in readonly segment; recompile object files with -fPIC or pass '-Wl,-z,notext' to allow text relocations in the output
>>> defined in ../install/lib/libqsc_key_encoder.a(encoding_dilithium.c.o)
>>> referenced by encoding_qsc.c
>>>               encoding_qsc.c.o:(qsc_encodings) in archive ../install/lib/libqsc_key_encoder.a

ld: error: can't create dynamic relocation R_X86_64_64 against symbol: Dilithium_R3_6x5_encodings in readonly segment; recompile object files with -fPIC or pass '-Wl,-z,notext' to allow text relocations in the output
>>> defined in ../install/lib/libqsc_key_encoder.a(encoding_dilithium.c.o)
>>> referenced by encoding_qsc.c
>>>               encoding_qsc.c.o:(qsc_encodings) in archive ../install/lib/libqsc_key_encoder.a

[... many continuing errors ...]

ld: error: too many errors emitted, stopping now (use -error-limit=0 to see all errors)
cc: error: linker command failed with exit code 1 (use -v to see invocation)
*** Error code 1

Stop.

The output tells us to use the -fPIC option which creates position-independent code (hence PIC) suitable for dynamic linking and avoiding any limit on the size of the global offset table.

I eventually figured out that the problem came from FreeBSD including the Clang C compiler. In this situation with everything being invoked through cmake, the suggested workarounds were specific to the GCC compiler collection and its gcc and g++. I also had GCC installed, but it wasn't being used by the above process.

$ which cc
/usr/bin/cc
$ cc --version
FreeBSD clang version 13.0.0 (git@github.com:llvm/llvm-project.git llvmorg-13.0.0-0-gd7b669b3a303)
Target: x86_64-unknown-freebsd13.1
Thread model: posix
InstalledDir: /usr/bin
$ which gcc
/usr/local/bin/gcc
$ gcc --version
gcc (FreeBSD Ports Collection) 12.2.0

Here is what worked for me. I doubt that I needed to set CXX and CXXFLAGS.

$ cd
$ rm -r oqs-provider
$ git clone https://github.com/open-quantum-safe/oqs-provider
$ cd oqs-provider
$ export CC=/usr/local/bin/gcc
$ export CXX=/usr/local/bin/g++
$ export CFLAGS="-fPIC"
$ export CXXFLAGS="-fPIC"
$ cmake -DOPENSSL_ROOT_DIR=/usr/local -S . -B _build
-- The C compiler identification is GNU 12.2.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/local/bin/gcc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Creating Release build
-- Build will store public keys in PKCS#8 structures
-- Build will include external encoding library for SPKI/PKCS#8
-- Found OpenSSL: /usr/local/lib/libcrypto.so (found suitable version "3.1.1", minimum required is "3.0")
-- Building commit 0e46745 in /home/cromwell/oqs-provider
CMake Warning at test/CMakeLists.txt:35 (message):
  OpenSSL source build directory
  '/home/cromwell/oqs-provider/test/../openssl' not present.  Some dependent
  tests will be skipped.

-- Configuring done
-- Generating done
-- Build files have been written to: /home/cromwell/oqs-provider/_build
$ cmake --build _build
[... many earlier compilation steps ...]
[ 64%] Building C object oqsprov/CMakeFiles/oqsprovider.dir/oqs_endecoder_common.c.o
[ 68%] Building C object oqsprov/CMakeFiles/oqsprovider.dir/oqs_decode_der2key.c.o
[ 72%] Building C object oqsprov/CMakeFiles/oqsprovider.dir/oqsprov_bio.c.o
[ 76%] Linking C shared library ../lib/oqsprovider.so
[ 76%] Built target oqsprovider
[ 80%] Building C object test/CMakeFiles/oqs_test_signatures.dir/oqs_test_signatures.c.o
[ 84%] Building C object test/CMakeFiles/oqs_test_signatures.dir/test_common.c.o
[ 88%] Linking C executable oqs_test_signatures
[ 88%] Built target oqs_test_signatures
[ 92%] Building C object test/CMakeFiles/oqs_test_kems.dir/oqs_test_kems.c.o
[ 96%] Building C object test/CMakeFiles/oqs_test_kems.dir/test_common.c.o
[100%] Linking C executable oqs_test_kems
[100%] Built target oqs_test_kems

That finished in just about ten seconds! Now I can install it:

$ su root -c 'cmake --install _build'
-- Install configuration: ""
-- Installing: /usr/local/lib/oqsprovider.so.0.5.0-dev
-- Installing: /usr/local/lib/oqsprovider.so.1
-- Set runtime path of "/usr/local/lib/oqsprovider.so.0.5.0-dev" to ""
-- Installing: /usr/local/lib/oqsprovider.so

Notice that the oqsprovider.so shared library is also linked to use the libcrypto.so shared library created when I built OpenSSL.

$ ls -l /usr/local/lib/oqsprovider.so*
lrwxr-xr-x  1 root  wheel       16 Apr  2 17:27 /usr/local/lib/oqsprovider.so -> oqsprovider.so.1
-rwxr-xr-x  1 root  wheel  5939096 Apr  2 17:21 /usr/local/lib/oqsprovider.so.0.5.0-dev
lrwxr-xr-x  1 root  wheel       24 Apr  2 17:27 /usr/local/lib/oqsprovider.so.1 -> oqsprovider.so.0.5.0-dev
$ file /usr/local/lib/oqsprovider.so.0.5.0-dev
/usr/local/lib/oqsprovider.so.0.5.0-dev: ELF 64-bit LSB shared object, x86-64, version 1 (FreeBSD), dynamically linked, for FreeBSD 13.1, with debug_info, not stripped
$ ldd /usr/local/lib/oqsprovider.so.0.5.0-dev
/usr/local/lib/oqsprovider.so.0.5.0-dev:
        libcrypto.so.3 => /usr/local/lib/libcrypto.so.3 (0x1a4f365cb000)
        libc.so.7 => /lib/libc.so.7 (0x1a4f33538000)
        libthr.so.3 => /lib/libthr.so.3 (0x1a4f32b7f000)

Configuring OpenSSL to use oqsprovider

The openssl program is configured by the file openssl.cnf. The version that is part of FreeBSD uses:
Binary: /usr/bin/openssl
Configuration: /etc/ssl/openssl.cnf

For the newer version I built, that becomes:
Binary: /usr/local/bin/openssl
Configuration: /usr/local/ssl/openssl.cnf
Notice that there is a file named openssl.cnf.dist which contains the original content. Treat it like a critical backup file, don't change it!

The file /usr/local/ssl/openssl.cnf contains this section in lines 53 through 72:

[openssl_init]
providers = provider_sect

# List of providers to load
[provider_sect]
default = default_sect
# The fips section name should match the section name inside the
# included fipsmodule.cnf.
# fips = fips_sect

# If no providers are activated explicitly, the default one is activated implicitly.
# See man 7 OSSL_PROVIDER-default for more details.
#
# If you add a section explicitly activating any other provider(s), you most
# probably need to explicitly activate the default provider, otherwise it
# becomes unavailable in openssl.  As a consequence applications depending on
# OpenSSL may not work correctly which could lead to significant system
# problems including inability to remotely access the system.
[default_sect]
# activate = 1

We need to change it as follows, with changes highlighted:

[openssl_init]
providers = provider_sect

# List of providers to load
[provider_sect]
default = default_sect
# The fips section name should match the section name inside the
# included fipsmodule.cnf.
# fips = fips_sect
oqs = oqs_sect

# If no providers are activated explicitly, the default one is activated implicitly.
# See man 7 OSSL_PROVIDER-default for more details.
#
# If you add a section explicitly activating any other provider(s), you most
# probably need to explicitly activate the default provider, otherwise it
# becomes unavailable in openssl.  As a consequence applications depending on
# OpenSSL may not work correctly which could lead to significant system
# problems including inability to remotely access the system.
[default_sect]
activate = 1

[oqs_sect]
module = /usr/local/lib/oqsprovider.so
activate = 1

Notice that I have:

  1. Added oqs to the list of providers, to be defined in a following [oqs_sect] section.
  2. Explicitly enabled the default provider, as strongly suggested.
  3. Created a new [oqs_sect] section which specifies the newly built oqsprovider.so shared library and then activates it.

The diff command should show you these changes:

$ diff /usr/local/ssl/openssl.cnf.dist /usr/local/ssl/openssl.cnf
61a62
> oqs = oqs_sect
72c73
< # activate = 1
---
> activate = 1
73a75,77
> [oqs_sect]
> module = /usr/local/lib/oqsprovider.so
> activate = 1

Now the new openssl program supports the Open Quantum Safe provider!

$ /usr/local/bin/openssl list -providers
Providers:
  default
    name: OpenSSL Default Provider
    version: 3.1.1
    status: active
  oqs
    name: OpenSSL OQS Provider
    version: 0.5.0-dev
    status: active

For more details you could use:

$ /usr/local/bin/openssl list -providers -verbose -provider-path /usr/local/lib
[... much output ...]

Now Build Nginx

Advanced topic: Consider using Poudriere to create and test these as FreeBSD packages.

Thanks to Lukáš Hamrla for spotting my oversight about the --with-openssl-opt parameter! See the Nginx build configuration page for details.

Now we can build Nginx, telling it where to install itself, what to build, and where to find the OpenSSL and liboqs code. Notice that you tell it where the OpenSSL source code is located, and not where the openssl program and libraries and manual pages were installed. Change /home/cromwell in the below to your home directory, or wherever you have the OpenSSL source code. Notice the added --with-openssl-opt=enable-fips parameter. That's needed, as otherwise it will not use the enable-fips parameter included earlier. Also notice the added --with-cc-opt and --with-ld-opt parameters, needed to use the liboqs library. You'll need to figure out where the pieces are, the below code works for the liboqs package on FreeBSD. Something like the following, run as root, may be helpful:
# find / -name oqs -o -name liboqs.so\*
Expect to find some path to a directory .../include/oqs/ and a symbolic link liboqs.so pointing to a symbolic link liboqs.so.0 pointing to a file liboqs.so.0.7.2-dev or similar; you want the directory where those are located.

🚧 The quantum-safe aspects of this section are still under development and test! 🚧
🚧 My remaining problem is that the nginx binary ends up linked to the original system /lib/libcrypto.so.111 and not the 3.* library that I built. The resulting program runs fine except that it does not understand the ssl_ecdh_curve parameters shown in the configuration file below. 🚧

$ cd
$ tar xf nginx-1.25.2.tar.gz
$ cd nginx-1.25.2
$ export LD_LIBRARY_PATH=/usr/local/lib:/lib:/usr/lib
$ ./configure --prefix=/usr/local/nginx-1.25.2	\
		--with-http_ssl_module			\
		--with-http_v2_module			\
		--with-http_v3_module			\
		--with-http_geoip_module		\
		--with-http_gunzip_module		\
		--with-http_sub_module			\
		--with-openssl=/home/cromwell/openssl-3.1.1 \
		--with-cc-opt="-I /usr/local/include/oqs" \
		--with-ld-opt="-L /usr/local/lib"
		
[... much output ...]

Configuration summary
  + using system PCRE2 library
  + using OpenSSL library: /home/cromwell/openssl-3.1.1
  + using system zlib library

  nginx path prefix: "/usr/local/nginx-1.25.2"
  nginx binary file: "/usr/local/nginx-1.25.2/sbin/nginx"
  nginx modules path: "/usr/local/nginx-1.25.2/modules"
  nginx configuration prefix: "/usr/local/nginx-1.25.2/conf"
  nginx configuration file: "/usr/local/nginx-1.25.2/conf/nginx.conf"
  nginx pid file: "/usr/local/nginx-1.25.2/logs/nginx.pid"
  nginx error log file: "/usr/local/nginx-1.25.2/logs/error.log"
  nginx http access log file: "/usr/local/nginx-1.25.2/logs/access.log"
  nginx http client request body temporary files: "client_body_temp"
  nginx http proxy temporary files: "proxy_temp"
  nginx http fastcgi temporary files: "fastcgi_temp"
  nginx http uwsgi temporary files: "uwsgi_temp"
  nginx http scgi temporary files: "scgi_temp"

$ sed 's/libcrypto.a/libcrypto.a -loqs/' objs/Makefile > /tmp/Makefile
$ mv /tmp/Makefile objs/Makefile
	# Note that because of a quirk in sed on FreeBSD and macos, the
	# following attempt to edit the file in place returns an error:
	#    $ sed -i 's/libcrypto.a/libcrypto.a -loqs/' objs/Makefile
	#    sed: 1: "objs/Makefile": invalid command code o
	# You could modify the sed command and edit the file in place:
	#    $ sed -i .bak 's/libcrypto.a/libcrypto.a -loqs/' objs/Makefile
	# or do separate edit and move as shown above.
$ make
[... much output, about an hour of compiling ...]
$ su root -c 'make install'
[... asked for root password, much output ...] 

Compiling Nginx also takes much longer with OpenSSL 3: about 14–16 minutes with OpenSSL 1.1.1x, but 48–54 minutes with 3.0.x and 60 minutes with 3.1.x. This is because whether you already compiled and installed OpenSSL or not, this recompiles the static libraries and embeds them in the nginx binary. Notice that the liboqs.so.0 shared library is in use.
🚧 The quantum-safe aspects of this section are still under development and test! 🚧

$ file nginx
nginx: ELF 64-bit LSB executable, x86-64, version 1 (FreeBSD), dynamically linked, interpreter /libexec/ld-elf.so.1, for FreeBSD 13.1, FreeBSD-style, with debug_info, not stripped
$ ldd nginx
nginx:
	libcrypt.so.5 => /lib/libcrypt.so.5 (0x800c92000)
	libpcre2-8.so.0 => /usr/local/lib/libpcre2-8.so.0 (0x800cb3000)
	liboqs.so.0 => /usr/local/lib/liboqs.so.0 (0x800e00000)
	libz.so.6 => /lib/libz.so.6 (0x800d73000)
	libGeoIP.so.1 => /usr/local/lib/libGeoIP.so.1 (0x800d90000)
	libc.so.7 => /lib/libc.so.7 (0x801793000)
	libthr.so.3 => /lib/libthr.so.3 (0x801b9d000)
	libcrypto.so.111 => /lib/libcrypto.so.111 (0x801bcb000)

The tail end of the build output shows that freshly compiled libraries from OpenSSL 3 were used, as directed by the modified objs/Makefile:

$ make
[... many compilation steps ...]
/usr/local/bin/gcc -o objs/nginx  objs/src/core/nginx.o [... many *.o files listed ...]
...    -L /usr/local/lib -lcrypt -lpcre2-8
...    /home/cromwell/openssl-3.1.2/.openssl/lib/libssl.a
...    /home/cromwell/openssl-3.1.2/.openssl/lib/libcrypto.a
...    -loqs -lz -lGeoIP  -Wl,-E
🚧 The quantum-safe aspects of this section are still under development and test! 🚧
🚧 While the resulting nginx works fine with traditional cryptography, the following is still a problem. 🚧

However, setting the following in /usr/local/nginx/conf/nginx.conf:

ssl_ecdh_curve p256_frodo640aes:p256_bike1l1cpa:p256_kyber90s512:p256_ntru_hps2048509:p256_lightsaber:p256_sidhp434:p256_sikep434:X448:X25519:secp521r1:secp384r1;

results in:

# /usr/local/nginx/sbin/nginx -t
nginx: [emerg] SSL_CTX_set1_curves_list("p256_frodo640aes:p256_bike1l1cpa:p256_kyber90s512:p256_ntru_hps2048509:p256_lightsaber:p256_sidhp434:p256_sikep434:X448:X25519:secp521r1:secp384r1") failed (SSL: error:0A080106:SSL routines::passed invalid argument:group 'p256_frodo640aes' cannot be set)
nginx: configuration file /usr/local/nginx-1.25.2/conf/nginx.conf test failed

Change the ssl_ecdh_curve string back, and:

# /usr/local/nginx/sbin/nginx -t
nginx: the configuration file /usr/local/nginx-1.25.2/conf/nginx.conf syntax is ok
nginx: configuration file /usr/local/nginx-1.25.2/conf/nginx.conf test is successful

I had worked on this earlier, and returned to it in early April 2023. I suspect that the problem is caused by the build linking the nginx binary with the /lib/libcrypto.so.111 shared library that's from OpenSSL 1.1.1 installed with FreeBSD 13.1 instead of the /usr/local/lib/libcrypto.so.3 shared library built and installed from OpenSSL 3.1.2.

I had set LD_LIBRARY_PATH with /usr/local/lib first in the list, but this persisted.

At the time there were ways to get quantum-safe cryptography in Nginx, but everything I found used modified OpenSSL 1.1.1 code. For example:
Lan Itan's blog Samuel Jacquier's repository

Meanwhile, The OpenSSL project reminded us that OpenSSL 1.1.1 would reach End Of Life in September 2023. And, FreeBSD releases were on the way — 13.2 in mid-April 2023, and more significantly, 14.0 later in 2023. I decided to set this aside until FreeBSD 14.0 and see if it wasn't built around OpenSSL 3.

Now I will make symbolic links so generic names point to the current versions.

$ su
# cd /usr/local
# rm -f nginx
# ln -s nginx-1.25.2 nginx 

And, to get the new version pointed to my configuration and content, change this as needed:

# cd /usr/local/nginx-1.25.2
# mv html html-original
# ln -s /home/cromwell/www html
# ln -s /home/cromwell/www/nginx/ssl_dhparam ssl_dhparam
# rm conf/nginx.conf
# ln -s /home/cromwell/www/nginx/nginx.conf conf/nginx.conf 

Make sure that the new software, your configuration file, and the symbolic links are all ready to go:

# /usr/local/nginx/sbin/nginx -t
nginx: the configuration file /usr/local/nginx-1.25.2/conf/nginx.conf syntax is ok
nginx: configuration file /usr/local/nginx-1.25.2/conf/nginx.conf test is successful 

I need to stop the running Apache process, and comment out the line in /etc/rc.conf that starts it at boot time.

$ grep apache /etc/rc.conf
## apache24_enable=YES 

I will create or modify /etc/rc.local to start nginx at boot time.

$ cat /etc/rc.local
#!/bin/sh

# Created just to start Nginx
/usr/local/nginx/sbin/nginx 

Initial Configuration

Dual ECC/RSA
Certificates

The web server should be ready to go once we define a simple configuration. Here are the contents of a basic nginx.conf file. This uses dual ECC and RSA keys and certificates. See my earlier page for details on how to generate and install these, and automatically renew them.

# Nginx configuration

worker_processes  1;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    index  Index.html;

    # HTTP server
    server {
	listen       80;
	server_name  cromwell-intl.com;
	# gzip suggestions from:
	# https://www.digitalocean.com/community/tutorials/how-to-increase-pagespeed-score-by-changing-your-nginx-configuration-on-ubuntu-16-04
	gzip on;
	gzip_comp_level 5;
	gzip_min_length 256;
	gzip_vary on;
	gzip_types text/plain text/css application/javascript application/x-javascript text/javascript image/x-icon application/x-font-ttf;

	return 301 https://$host$request_uri;

        location / {
	    root   html;
	}

	error_page 404 /ssi/404page.html;

    }

    # HTTPS server
    server {
	listen       443 ssl http2;
	server_name  cromwell-intl.com;
	keepalive_timeout 65;
	# gzip suggestions from:
	# https://www.digitalocean.com/community/tutorials/how-to-increase-pagespeed-score-by-changing-your-nginx-configuration-on-ubuntu-16-04
	gzip on;
	gzip_comp_level 5;
	gzip_min_length 256;
	gzip_vary on;
	gzip_types text/plain text/css application/javascript application/x-javascript text/javascript image/x-icon application/x-font-ttf;

	location / {
	    root   html;
	}

	error_page 404 /ssi/404page.html;

	####################################################################
	# Certificates and private keys.
	####################################################################

	# 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;

	####################################################################
	## Cryptography and TLS
	####################################################################

	ssl_protocols TLSv1.2 TLSv1.3;

	# 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_cache    shared:SSL:1m;
	ssl_session_timeout  5m;

	ssl_ciphers EECDH:ECDHE:EDH;

	ssl_prefer_server_ciphers on;

	# If you do not have the quantum-safe part running, uncomment the following line
	# and comment out the longer one with parameters starting "p256_frodo640aes:...".
	# Otherwise, your server will not start!
        # ssl_ecdh_curve X448:X25519:secp521r1:secp384r1;
	ssl_ecdh_curve p256_frodo640aes:p256_bike1l1cpa:p256_kyber90s512:p256_ntru_hps2048509:p256_lightsaber:p256_sidhp434:p256_sikep434:X448:X25519:secp521r1:secp384r1;

    }

}
🚧 The quantum-safe sections of this are still under development and test! 🚧

Start it, and make sure it's running on TCP ports 80 and 443. You will see a master process running as root. There should be a worker process running as nginx and listening on TCP/80 and TCP/443, highlighted here. Then lsof will also show the worker caught in the act of serving content.

# lsof -i tcp:80 -o -i tcp:443
COMMAND     PID   USER   FD   TYPE             DEVICE OFFSET NODE NAME
nginx     11117   root    9u  IPv4 0xfffff80024dde820    0t0  TCP *:http (LISTEN)
nginx     11117   root   10u  IPv4 0xfffff80024e4a410    0t0  TCP *:https (LISTEN)
nginx     11118 nobody    3u  IPv4 0xfffff80024d9e820    0t0  TCP www.c.cromwell-intl.internal:https->74-216-249-130.dedicated.allstream.net:54781 (ESTABLISHED)
nginx     11118 nobody    9u  IPv4 0xfffff80024dde820    0t0  TCP *:http (LISTEN)
nginx     11118 nobody   10u  IPv4 0xfffff80024e4a410    0t0  TCP *:https (LISTEN)
nginx     11118 nobody   13u  IPv4 0xfffff80024900820    0t0  TCP www.c.cromwell-intl.internal:https->ec2-54-172-123-82.compute-1.amazonaws.com:37726 (ESTABLISHED)
nginx     11118 nobody   14u  IPv4 0xfffff80024d98820    0t0  TCP www.c.cromwell-intl.internal:https->66.87.176.179:25208 (ESTABLISHED)
nginx     11118 nobody   15u  IPv4 0xfffff80024b22820    0t0  TCP www.c.cromwell-intl.internal:https->d24-36-29-150.home1.cgocable.net:55150 (ESTABLISHED)
nginx     11118 nobody   16u  IPv4 0xfffff80024b20410    0t0  TCP www.c.cromwell-intl.internal:https->71-80-191-91.static.lsan.ca.charter.com:62074 (ESTABLISHED)
nginx     11118 nobody   17u  IPv4 0xfffff80024e18820    0t0  TCP www.c.cromwell-intl.internal:https->ip72-207-111-102.sd.sd.cox.net:56285 (ESTABLISHED)
nginx     11118 nobody   18u  IPv4 0xfffff80024d9e000    0t0  TCP www.c.cromwell-intl.internal:http->180.115.150.200.static.copel.net:44473 (ESTABLISHED)
nginx     11118 nobody   19u  IPv4 0xfffff80024384410    0t0  TCP www.c.cromwell-intl.internal:https->8.30.181.58:54251 (ESTABLISHED)
nginx     11118 nobody   20u  IPv4 0xfffff80024f1a000    0t0  TCP www.c.cromwell-intl.internal:https->mail.iph-bet.fr:49700 (ESTABLISHED)
nginx     11118 nobody   22u  IPv4 0xfffff80024d9d410    0t0  TCP www.c.cromwell-intl.internal:https->180.115.150.200.static.copel.net:54777 (ESTABLISHED)
nginx     11118 nobody   23u  IPv4 0xfffff800247e4820    0t0  TCP www.c.cromwell-intl.internal:https->CPE84948c4cc1b3-CM84948c4cc1b0.cpe.net.cable.rogers.com:54286 (ESTABLISHED)
nginx     11118 nobody   24u  IPv4 0xfffff80024d98000    0t0  TCP www.c.cromwell-intl.internal:https->CPE84948c4cc1b3-CM84948c4cc1b0.cpe.net.cable.rogers.com:54287 (ESTABLISHED)
nginx     11118 nobody   25u  IPv4 0xfffff800247f5820    0t0  TCP www.c.cromwell-intl.internal:https->CPE84948c4cc1b3-CM84948c4cc1b0.cpe.net.cable.rogers.com:54289 (ESTABLISHED)
nginx     11118 nobody   26u  IPv4 0xfffff8002490e410    0t0  TCP www.c.cromwell-intl.internal:https->CPE84948c4cc1b3-CM84948c4cc1b0.cpe.net.cable.rogers.com:54288 (ESTABLISHED)
nginx     11118 nobody   27u  IPv4 0xfffff80024dde410    0t0  TCP www.c.cromwell-intl.internal:https->172.56.42.132:61366 (ESTABLISHED)
nginx     11118 nobody   28u  IPv4 0xfffff80024384820    0t0  TCP www.c.cromwell-intl.internal:https->190.106.223.59:45182 (ESTABLISHED)
nginx     11118 nobody   29u  IPv4 0xfffff80024d99000    0t0  TCP www.c.cromwell-intl.internal:https->dynamicip-94-181-133-82.pppoe.penza.ertelecom.ru:55386 (ESTABLISHED)
nginx     11118 nobody   56u  IPv4 0xfffff80024e4a000    0t0  TCP www.c.cromwell-intl.internal:https->66.87.176.179:30154 (ESTABLISHED)
python2.7 12908   root    7u  IPv4 0xfffff80024d9f000    0t0  TCP www.c.cromwell-intl.internal:55720->metadata.google.internal:http (ESTABLISHED)

It Should Be Running, But Old Tools Might Not Realize That

The problem is that our measurement tools may fail to show the TLS functionality. Your web browser, the (default) OpenSSL package, the Wireshark protocol analyzer, and online scanners should all be able to test or recognize TLS 1.3. If not, you need to update your tools!

Make sure you can test your server from a distance:

$ openssl s_client -tls1_3 cromwell-intl.com:443
CONNECTED(00000005)
---
Certificate chain
 0 s:CN = cromwell-intl.com
   i:C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3
 1 s:C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3
   i:O = Digital Signature Trust Co., CN = DST Root CA X3
---
Server certificate
-----BEGIN CERTIFICATE-----
MIIFdzCCBF+gAwIBAgISAzBCb9tcMGcrVV2eqVLf7J39MA0GCSqGSIb3DQEBCwUA
MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQD
[... lines deleted ...]
sboGITUCwIdEGAPawfevVlv0xKG2g0oZK3dqhTXNIOuq3euzHf+NA+iUXVrO6ll7
Vy/rjUpz225dyEBtPULkwrVvN5bpNKutKgH8HE++mII47+sv3ef+jRs6b3/95m5f
Oz7WyhK2c3R0mGo=
-----END CERTIFICATE-----
subject=CN = cromwell-intl.com

issuer=C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3

---
No client certificate CA names sent
Peer signing digest: SHA384
Peer signature type: ECDSA
Server Temp Key: X25519, 253 bits
---
SSL handshake has read 2986 bytes and written 321 bytes
Verification error: unable to get local issuer certificate
---
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Server public key is 384 bit
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
Early data was not sent
Verify return code: 20 (unable to get local issuer certificate)
---
QUIT 

Yes! It works!

I captured that traffic with Wireshark. On a recent distribution, Wireshark can figure it out.

Wireshark analyzing TLS 1.3 traffic

Also try testing it with the Qualys web server evaluation scanner. Their development scanner at dev.ssllabs.com may be able to do additional tests.

We Can Do Better

Tuning HTTPS
Security
on Apache

In an earlier series of pages I describe how to configure an Apache web server to score an A+ grade from Qualys and from online HTTP header analyzers. Here's how to do the same thing with Nginx.

I pass two variables to the PHP back end processor, which allows me to put the TLS protocol version, key exchange elliptic curve, and symmetric cipher in the standard footer on each page. The code in the footer looks like this:

<p style="text-align:right; font-size:0.80rem;">
<?php	/* The trick here is to pass these values to the PHP processor
	 * in the Nginx configuration file:
	 *	fastcgi_param  TLS_PROTOCOL $ssl_protocol;
	 *	fastcgi_param  TLS_CURVE $ssl_curve;
	 *	fastcgi_param  TLS_CIPHER $ssl_cipher;
	 */
	echo('Protocol: ' . $_SERVER["SERVER_PROTOCOL"] . '<br />');
	echo('Crypto: ' . $_SERVER["TLS_PROTOCOL"] . ' / ' $_SERVER["TLS_CURVE"] . ' / ' . $_SERVER["TLS_CIPHER"] ); ?>
</p>

The result, specific to your connection to view this page, is:

Protocol: HTTP/1.1
Crypto: TLSv1.3 / X25519 / TLS_AES_256_GCM_SHA384

Maintaining Dual ECC and RSA Certificates

You need at least one key pair, with the public key wrapped in a digital certification issued by a trusted CA (or Certificate Authority). Another page of mine shows how to generate and maintain dual ECC and RSA certificates from the Let's Encrypt project, automatically renewing them.

Configuring the Server

Now you're ready for the last step:
Configuring Nginx for an A+ Qualys Score