Skip to content

Commit

Permalink
Add Entropy Source and DRNG Manager (ESDM) RNG support
Browse files Browse the repository at this point in the history
ESDM is a Linux-based user-space PRNG daemon, with configurable entropy
sources.

See: https://github.com/smuellerDD/esdm

It currently gets used, when a high level of control over entropy
sources is desirable, e.g. for VPN appliance solutions.

In order to use this source, the ESDM server daemon has to be running
and botan must be configured --with-esdm.

ESDM currently works only on Linux.

Signed-off-by: Markus Theil <[email protected]>
  • Loading branch information
thillux committed Nov 7, 2024
1 parent 679ca89 commit 4574d17
Show file tree
Hide file tree
Showing 13 changed files with 306 additions and 6 deletions.
2 changes: 1 addition & 1 deletion configure.py
Original file line number Diff line number Diff line change
Expand Up @@ -584,7 +584,7 @@ def add_enable_disable_pair(group, what, default, msg=optparse.SUPPRESS_HELP):
'disable building of deprecated features and modules')

# Should be derived from info.txt but this runs too early
third_party = ['boost', 'bzip2', 'lzma', 'commoncrypto', 'sqlite3', 'zlib', 'tpm', 'tpm2']
third_party = ['boost', 'bzip2', 'esdm_rng', 'lzma', 'commoncrypto', 'sqlite3', 'zlib', 'tpm', 'tpm2']

for mod in third_party:
mods_group.add_option('--with-%s' % (mod),
Expand Down
2 changes: 1 addition & 1 deletion readme.rst
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ Other Useful Things
* Full C++ PKCS #11 API wrapper
* Interfaces for TPM v1.2 and v2.0 device access
* Simple compression API wrapping zlib, bzip2, and lzma libraries
* RNG wrappers for system RNG and hardware RNGs
* RNG wrappers for system RNG, ESDM and hardware RNGs
* HMAC_DRBG and entropy collection system for userspace RNGs
* SRP-6a password authenticated key exchange
* Key derivation functions including HKDF, KDF2, SP 800-108, SP 800-56A, SP 800-56C
Expand Down
20 changes: 18 additions & 2 deletions src/cli/cli_rng.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
#include <botan/auto_rng.h>
#endif

#if defined(BOTAN_HAS_ESDM_RNG)
#include <botan/esdm_rng.h>
#endif

#if defined(BOTAN_HAS_SYSTEM_RNG)
#include <botan/system_rng.h>
#endif
Expand All @@ -36,6 +40,15 @@ std::shared_ptr<Botan::RandomNumberGenerator> cli_make_rng(const std::string& rn
}
#endif

#if defined(BOTAN_HAS_ESDM_RNG)
if(rng_type == "esdm-full") {
return std::make_shared<Botan::ESDM_RNG>(false);
}
if(rng_type == "esdm-pr") {
return std::make_shared<Botan::ESDM_RNG>(true);
}
#endif

const std::vector<uint8_t> drbg_seed = Botan::hex_decode(hex_drbg_seed);

#if defined(BOTAN_HAS_AUTO_SEEDING_RNG)
Expand Down Expand Up @@ -89,7 +102,10 @@ std::shared_ptr<Botan::RandomNumberGenerator> cli_make_rng(const std::string& rn

class RNG final : public Command {
public:
RNG() : Command("rng --format=hex --system --rdrand --auto --entropy --drbg --drbg-seed= *bytes") {}
RNG() :
Command(
"rng --format=hex --system --esdm-full --esdm-pr --rdrand --auto --entropy --drbg --drbg-seed= *bytes") {
}

std::string group() const override { return "misc"; }

Expand All @@ -100,7 +116,7 @@ class RNG final : public Command {
std::string type = get_arg("rng-type");

if(type.empty()) {
for(std::string flag : {"system", "rdrand", "auto", "entropy", "drbg"}) {
for(std::string flag : {"system", "rdrand", "auto", "entropy", "drbg", "esdm-full", "esdm-pr"}) {
if(flag_set(flag)) {
type = flag;
break;
Expand Down
2 changes: 2 additions & 0 deletions src/lib/ffi/ffi.h
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,8 @@ typedef struct botan_rng_struct* botan_rng_t;
* @param rng rng object
* @param rng_type type of the rng, possible values:
* "system": system RNG
* "esdm-full": ESDM RNG (fully seeded)
* "esdm-pr": ESDM RNG (w. prediction resistance)
* "user": userspace RNG
* "user-threadsafe": userspace RNG, with internal locking
* "rdrand": directly read RDRAND
Expand Down
10 changes: 10 additions & 0 deletions src/lib/ffi/ffi_rng.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
#if defined(BOTAN_HAS_JITTER_RNG)
#include <botan/jitter_rng.h>
#endif
#if defined(BOTAN_HAS_ESDM_RNG)
#include <botan/esdm_rng.h>
#endif

extern "C" {

Expand Down Expand Up @@ -54,6 +57,13 @@ int botan_rng_init(botan_rng_t* rng_out, const char* rng_type) {
rng = std::make_unique<Botan::Jitter_RNG>();
}
#endif
#if defined(BOTAN_HAS_ESDM_RNG)
else if(rng_type_s == "esdm-full") {
rng = std::make_unique<Botan::ESDM_RNG>(false);
} else if(rng_type_s == "esdm-pr") {
rng = std::make_unique<Botan::ESDM_RNG>(true);
}
#endif

if(!rng) {
return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
Expand Down
87 changes: 87 additions & 0 deletions src/lib/rng/esdm_rng/esdm_rng.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* ESDM RNG
* (C) 2024, Markus Theil <[email protected]>
*
* Botan is released under the Simplified BSD License (see license.txt)
*/

#include <botan/esdm_rng.h>

#include <esdm/esdm_rpc_client.h>

namespace Botan {

namespace {
/**
* This helper makes sure that the ESDM service is initialized and
* finalized as needed in a threadsafe fashion. Finalization happens
* as soon as all instances of ESDM_RNG are destructed. This may
* happen multiple times in the lifetime of the process.
*/
class ESDM_Context {
public:
[[nodiscard]] static std::shared_ptr<void> instance() {
static ESDM_Context g_instance;
return g_instance.acquire();
}

private:
ESDM_Context() = default;

[[nodiscard]] std::shared_ptr<void> acquire() {
std::scoped_lock lk(m_mutex);
if(m_refs++ == 0) {
if(esdm_rpcc_init_unpriv_service(nullptr) != 0) {
throw Botan::System_Error("unable to initialize ESDM unprivileged service");
}
}
return std::shared_ptr<void>{nullptr, [this](void*) { this->release(); }};
}

void release() {
std::scoped_lock lk(m_mutex);
if(m_refs-- == 1) {
esdm_rpcc_fini_unpriv_service();
}
}

private:
std::mutex m_mutex;
size_t m_refs = 0;
};
} // namespace

ESDM_RNG::ESDM_RNG(bool prediction_resistance) :
m_prediction_resistance(prediction_resistance), m_ctx(ESDM_Context::instance()) {}

void ESDM_RNG::fill_bytes_with_input(std::span<uint8_t> out, std::span<const uint8_t> in) {
if(!in.empty()) {
ssize_t ret = 0;
/*
* take additional input, but do not account entropy for it,
* as this information is not included in the API
*/
esdm_invoke(esdm_rpcc_write_data(in.data(), in.size()));
if(ret != 0) {
throw Botan::System_Error("Writing additional input to ESDM failed");
}
}
if(!out.empty()) {
ssize_t ret = 0;
if(m_prediction_resistance) {
esdm_invoke(esdm_rpcc_get_random_bytes_pr(out.data(), out.size()));
} else {
esdm_invoke(esdm_rpcc_get_random_bytes_full(out.data(), out.size()));
}
if(ret != static_cast<ssize_t>(out.size())) {
throw Botan::System_Error("Fetching random bytes from ESDM failed");
}
}
}

RandomNumberGenerator& esdm_rng() {
static ESDM_RNG g_esdm_rng;
return g_esdm_rng;
}

} // namespace Botan
111 changes: 111 additions & 0 deletions src/lib/rng/esdm_rng/esdm_rng.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*
* ESDM RNG
* (C) 2024, Markus Theil <[email protected]>
*
* Botan is released under the Simplified BSD License (see license.txt)
*/

#ifndef BOTAN_ESDM_RNG_H_
#define BOTAN_ESDM_RNG_H_

#include <botan/rng.h>
#include <memory>

namespace Botan {

/**
* Return a shared reference to a global PRNG instance provided by ESDM
*/
BOTAN_PUBLIC_API(3, 7) RandomNumberGenerator& esdm_rng();

/**
* Entropy Source and DRNG Manager (ESDM) is a Linux/Unix based
* PRNG manager, which can be seeded from NTG.1/SP800-90B sources.
*
* See:
* - Repository: https://github.com/smuellerDD/esdm
* - Further Docs: https://www.chronox.de/esdm/index.html
*
* Different entropy sources can be configured in respect of beeing
* active and how much entropy is accounted for each of them.
*
* ESDM tracks its seed and reseed status and blocks, when not fully seeded.
* For this functionality, the esdm_rpcc_get_random_bytes_pr (prediction resistant) or
* esdm_rpcc_get_random_bytes_full (fully seeded) calls have to be used.
*
* Configurable modes:
* - fully seeded (-> fast): provide entropy from a DRBG/PRNG after beeing fully seeded,
* block until this point is reached, reseed from after a time
* and/or invocation limit, block again if reseeding is not possible
* - prediction resistance (-> slow): reseed ESDM with fresh entropy after each invocation
*
* You typically want to use the fast fully seeded mode, which is the default.
*
* Instances of this class communicate over RPC with ESDM. The esdm_rpc_client
* library, provided by ESDM, is leveraged for this.
*
* Thread safety:
* It is fine to construct, destruct and use objects of this class concurrently.
* The communication with ESDM is thread-safe, as handled by esdm_rpc_client.
* The initialization of esdm_rpc_client is not thread safe, therefore this class
* takes care of it, with its embedded ESDM_Context.
*/
class BOTAN_PUBLIC_API(3, 7) ESDM_RNG final : public Botan::RandomNumberGenerator {
public:
/**
* Default constructor for ESDM, fully seeded mode
*/
ESDM_RNG() : ESDM_RNG(false) {}

/**
* Construct ESDM instance with configurable mode
*/
explicit ESDM_RNG(bool prediction_resistance);

std::string name() const override {
if(m_prediction_resistance) {
return "esdm-pr";
} else {
return "esdm-full";
}
}

/**
* ESDM blocks, if it is not seeded,
*
* @return true
*/
bool is_seeded() const override { return true; }

/**
* ESDM can inject additional inputs
* but we do not account entropy for it
*
* @return true
*/
bool accepts_input() const override { return true; }

/**
* the ESDM RNG does not hold any state outside ESDM, that should be cleared
* here
*/
void clear() override {}

protected:
void fill_bytes_with_input(std::span<uint8_t> out, std::span<const uint8_t> in) override;

private:
/**
* tracks if predicition resistant or fully seeded interface should be queried
*/
bool m_prediction_resistance;

/**
* takes care of thread-safe esdm_rpc_client initialization
*/
std::shared_ptr<void> m_ctx;
};

} // namespace Botan

#endif /* BOTAN_ESDM_RNG_H_ */
18 changes: 18 additions & 0 deletions src/lib/rng/esdm_rng/info.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<defines>
ESDM_RNG -> 20241107
</defines>

<module_info>
name -> "ESDM RNG"
brief -> "RNG based on ESDM - Entropy Source and DRNG Manager"
</module_info>

load_on vendor

<header:public>
esdm_rng.h
</header:public>

<libs>
linux -> esdm_rpc_client
</libs>
14 changes: 14 additions & 0 deletions src/scripts/ci/setup_gh_actions.sh
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,20 @@ if type -p "apt-get"; then
sudo apt-get -qq install doxygen python3-docutils python3-sphinx

fi

# ESDM for shared, coverage and sanitizer target
if [ "$TARGET" = "shared" ] || [ "$TARGET" = "coverage" ] || [ "$TARGET" = "sanitizer" ]; then
sudo apt-get -qq install libprotobuf-c-dev meson

# install ESDM 1.2.0
wget -O esdm.tar.gz https://github.com/smuellerDD/esdm/archive/refs/tags/v1.2.0.tar.gz
tar xvfz esdm.tar.gz
pushd esdm-*
meson setup build -Dselinux=disabled -Dais2031=false -Dlinux-devfiles=disabled -Des_jent=disabled --prefix=/usr --libdir=lib
meson compile -C build
sudo meson install -C build
popd
fi
else
export HOMEBREW_NO_AUTO_UPDATE=1
export HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1
Expand Down
15 changes: 15 additions & 0 deletions src/scripts/ci_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,9 @@ def sanitize_kv(some_string):
# Workaround for https://github.com/actions/runner-images/issues/10004
flags += ['--extra-cxxflags=/D_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR']

if target_os == 'linux' and target in ['shared', 'coverage', 'sanitizer']:
flags += ['--with-esdm_rng']

if target in ['minimized']:
flags += ['--minimized-build', '--enable-modules=system_rng,sha2_32,sha2_64,aes']

Expand Down Expand Up @@ -922,12 +925,24 @@ def main(args=None):
cmds.append(make_cmd + ['clean'])
cmds.append(make_cmd + ['distclean'])

# start ESDM in background, if on Linux
if target in ['shared', 'coverage', 'sanitizer'] and platform.system() == "Linux":
print('Starting esdm-server for this target')
esdm_process = subprocess.Popen('sudo /usr/bin/esdm-server -f', shell=True)
assert esdm_process.poll() is None, f"esdm-server did not start for target {target}"
else:
print('Not starting esdm-server for this target')

for cmd in cmds:
if options.dry_run:
print('$ ' + ' '.join(cmd))
else:
run_cmd(cmd, root_dir, build_dir)

if target in ['shared', 'coverage', 'sanitizer'] and platform.system() == "Linux":
print('Stopping esdm-server')
esdm_process.kill()

return 0

if __name__ == '__main__':
Expand Down
2 changes: 1 addition & 1 deletion src/scripts/config_for_oss_fuzz.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"--build-fuzzers=libfuzzer",
"--build-targets=static",
"--without-os-features=getrandom,getentropy",
"--disable-modules=system_rng,processor_rng",
"--disable-modules=system_rng,processor_rng,esdm_rng",
"--with-fuzzer-lib=FuzzingEngine",
]

Expand Down
7 changes: 6 additions & 1 deletion src/scripts/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -705,7 +705,12 @@ def cli_rng_tests(_tmp_dir):

hex_10 = re.compile('[A-F0-9]{20}')

for rng in ['system', 'auto', 'entropy']:
rngs = ['system', 'auto', 'entropy']
# execute ESDM tests only on Linux
if platform.system() == "Linux":
rngs += ['esdm-full', 'esdm-pr']

for rng in rngs:
output = test_cli("rng", ["10", '--%s' % (rng)], use_drbg=False)
if output == "D80F88F6ADBE65ACB10C":
logging.error('RNG produced DRBG output')
Expand Down
Loading

0 comments on commit 4574d17

Please sign in to comment.