Vendors are increasingly looking to leverage OpenSSL 3.x as their cryptographic module of choice within their products. At the same time, entropy continues to a be a focus in both FIPS 140-3 and Common Criteria projects. For those transitioning from OpenSSL 1.0.2 and the FIPS Object Module 2.0.x, the ways in which entropy can seed the DRBG in OpenSSL 3.0 differs.
Let’s begin with an analysis of how sources of entropy seed the OpenSSL DRBG in the default state. To understand this, you need to be aware that OpenSSL has created the concept of “providers” which can offer implementations of specific discrete functionality. (The concept of a “provider” is the backbone of the entire FIPS module.) Information on providers can be found in the OpenSSL 3 migration guide. More technical information on the provider framework and API calls can be found in the OpenSSL manual pages for providers.
There are two types of providers in OpenSSL: statically built-in providers, such as “base” and “default”, and dynamically enabled providers such as “fips” or “legacy”. Within the “default” provider (code in $SRC/providers/defltprov.c
), there is an offered function within the random number operations algorithm sets called “SEED-SRC”. The “SEED-SRC” function is the entropy-seeding mechanism (found in $SRC/providers/implementations/rands/seed_src.c
). Of specific interest is the function seed_get_seed()
which is where calls to seed a DRBG are made to. In turn, this function calls ossl_pool_acquire_entropy()
which is where the real entropy data comes from.
Statically-Defined Entropy Sources
Depending on the underlying operating system for which OpenSSL 3.0 is being compiled, the function ossl_pool_acquire_entropy
will resolve to a function found in one of the files in $SRC/providers/implementations/rands/seeding
. For the purposes of this article, we will focus on the rand_unix.c
file. This file can be tricky to read because of the large number of conditional compilation blocks needed to accommodate various flavours of Un*x. However, effectively, within ossl_pool_acquire_entropy
there are several entropy sources which are read in sequential order, from most preferred to least preferred. If at any time the entropy requirements are fulfilled, the function terminates. These statically defined mechanisms were actually implemented in OpenSSL 1.1.x, though to end-users of OpenSSL 1.0.2, this information will be new to them. Importantly: there is no way to re-order the sequence (though there is a way to force which of the sources are enabled).
- Depending on the Un*x environment used, the most preferred way for OpenSSL to seed itself is to call into the kernel and make a direct system call. The specific function call depends on the underlying operating system such as Linux, BSD, HP-UX, etc. As an example, for Linux, it will be either
getentropy()
orgetrandom()
, depending on which system call is implemented. - A method called “librandom” would theoretically be next. However, it is unimplemented.
- Third in the list is the use of well-known file-based random number generator devices. This includes /dev/urandom, /dev/random, /dev/hwrng, and /dev/srandom depending on the underlying operating system. For users familiar with OpenSSL 1.0.x, use of these devices is how entropy was previously read. The specific device files and/or order of the device list can be altered through the use of compile-time options.
- A method called “RDTSC” which theoretically reads from the high-frequency timer register on many implementing CPUs. Implemented in
$SRC/providers/implementations/rands/seeding/rand_tsc.c
, the TSC is currently disabled from being used due to concerns around its ability to provide sufficient entropy. - A method called “RDCPU” which reads entropy from Intel (and equivalent) RDSEED and RDRAND instruction calls. Specifically implemented in
$SRC/providers/implementations/rands/seeding/rand_cpu*.c
, the function will read from RDSEED first, and if insufficient entropy is gathered, it will then attempt to read from RDRAND. - Finally, the last method tried is to source from an “entropy gathering daemon” (EGD). Also familiar to OpenSSL 1.0.x, the EGD source reads from a series of well-known named sockets and/or devices to get the entropy needed. The other end of the socket or device would be a user-space daemon feeding the entropy to the reader. The set of named files that can be read can be modified during compile-time to accommodate the entropy-gathering daemon of choice.
These entropy sources can be enabled and disabled only at compile-time using the --with-rand-seed
parameter. The permitted choices for this are “getrandom
” “devrandom
” “os
” “egd
” “none
” “rdcpu
” and “librandom
“. More than one choice can be provided when separating the source keywords using commas. (As you can see, “rdtsc
” is not one of the permitted choices and therefore cannot currently be activated by compilation.) The “none
” option is interesting as well, and a big warning is generated if you choose this option. By default, “os
” is chosen and it is essentially an alias of “getrandom
,devrandom
“.
The specifically compiled seed source is immutable and built into the default provider. It can further be queried at the command-line using openssl version -a
which will emit a line called ‘Seeding source’. As an example, here’s the output for a custom build of OpenSSL 3.2-dev we have at Lightship in which we’ve enabled the ‘rdcpu’ and the ‘os’ options, simultaneously:
OpenSSL 3.2.0-dev (Library: OpenSSL 3.2.0-dev )
built on: Fri Jan 6 07:48:14 2023 UTC
platform: linux-x86_64
options: bn(64,64)
compiler: gcc -fPIC -pthread -m64 -Wa,--noexecstack -Wall -O3 -DOPENSSL_USE_NODELETE -DL_ENDIAN -DOPENSSL_PIC -DOPENSSL_BUILDING_OPENSSL -DNDEBUG
OPENSSLDIR: "/usr/local/ssl"
ENGINESDIR: "/usr/local/lib64/engines-3"
MODULESDIR: "/usr/local/lib64/ossl-modules"
Seeding source: rdrand ( rdseed rdrand ) os-specific
CPUINFO: OPENSSL_ia32cap=0xfefa32034f8bffff:0x1c27ab
(While the “Seeding source” line indicates the order is “rdrand” and “os-specific”, the order in which entropy is acquired is as per the flow defined previously.)
Based on the above, we can see that there are several flexible ways to define the entropy source feeding the OpenSSL DRBG. Of an important particular note, the FIPS provider defines entropy sources as being outside of the FIPS boundary and therefore the same defined SEED-SRC is used to feed the FIPS provider DRBG as well. However, based on some of the descriptions above, it would appear that even with some flexibility in defining the entropy sources, it has to be done at compile-time (therefore requiring any vendor to maintain their own build) and there are some disadvantages to choosing the predefined options.
Dynamically-Defined Entropy Sources
Further investigation has uncovered a way to create a dynamically enabled entropy source through careful use of the provider framework. As mentioned earlier, we can create dynamic providers similar to how the “fips” provider is constructed. However, instead of building an entire suite of algorithms, we can, instead, define just to offer the seeding mechanism for random number generators.
Building a provider to offer a seeding mechanism may seem like over-engineering but it has distinct advantages over the static seeding mechanisms. The main advantage is that a vendor need not maintain their own version of the OpenSSL source code tree since a provider can be used with a compatible OpenSSL binary without recompilation.
A typical functional provider skeleton implementing the provider API is about 100 lines of code. On top of this, fleshing out the random seeding function borrows heavily from the $SRC/providers/implementations/rands/seed_src.c
file and adds another 200+ lines of code. Finally, adding the specific seeding functionality to produce the necessary entropy data samples to a function similar to seed_get_seed()
is implementation-specific.
As an example, Lightship has constructed an entropy source provider which leverages Jitter Entropy as the primary source. Since this is a dynamic entropy source, the results of “openssl version -a
” will still show the statically compiled mechanisms, but we can see that we have defined another loaded provider if we query the provider matrix using “openssl list -providers
“:
Providers: default name: OpenSSL Default Provider version: 3.2.0 status: active entropy name: Jitter Entropy Provider version: Provider 1.0 (jent 3.4.1) status: active fips name: OpenSSL FIPS Provider version: 3.2.0 status: active
Use of our “entropy” provider will enable a new seed source to be made available which we can force as the primary functional mechanism, overriding the default “SEED-SRC”:
Provided RNGs and seed sources: CTR-DRBG @ default CTR-DRBG @ fips HASH-DRBG @ default HASH-DRBG @ fips HMAC-DRBG @ default HMAC-DRBG @ fips JENT-SEED-SRC @ entropy SEED-SRC @ default TEST-RAND @ default TEST-RAND @ fips
The OpenSSL configuration file in the “random” section can be used to define which seed source to use.
Using a dynamic provider can permit a vendor to carefully ensure they are using a certified entropy source to seed their certified DRBG. Use of a dynamic provider can permit additional vendor-specific flexibility while also ensuring they do not need to maintain their own copy of the OpenSSL code base since a provider can be compiled out-of-tree. However, it should be noted that relying on a single source of entropy may be a security risk: one should always have multiple possibilities to ensure action can be taken in the event of a security flaw. Careful inclusion and modification of any entropy source should be heavily scrutinized.
For more information on how you might leverage OpenSSL 3.x in your certification project, please contact Lightship Security.
Greg McLearn is Lightship’s Technical Director. He has been doing Common Criteria and security certifications for 10+ years and enjoys getting his hands on some of the latest technology. He has authored several tools to help facilitate security testing.