This commit is contained in:
Mirek Fidler 2026-04-28 11:38:01 +02:00
commit 65ae756caf
11 changed files with 684 additions and 8 deletions

View file

@ -6,6 +6,8 @@ namespace Upp {
#ifdef UPP_HEAP
#ifndef _DEBUG // temporary solution unless we find the source of all those harmless leaks
static int64 UPP_SSL_alloc = 0;
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
@ -81,6 +83,8 @@ static void *SslRealloc(void *ptr, size_t size)
#endif
#endif
void SocketInit();
INITIALIZER(SSL)

146
uppsrc/Core/SSL/Random.cpp Normal file
View file

@ -0,0 +1,146 @@
#include "SSL.h"
#define LLOG(x) // DLOG("SecureRandomGenerator: " << x)
namespace Upp {
namespace {
std::atomic<bool> sForked(false);
std::atomic<uint64> sId(0);
std::atomic<uint64> sCounter(0);
SpinLock sLock;
constexpr const int NONCE_MIN = 12;
constexpr const int NONCE_STRUCTURED_MIN = 16;
inline void FillRandom(void* ptr, int len)
{
if(len <= 0)
return;
#if OPENSSL_VERSION_NUMBER < 0x10100000L
if(RAND_status() != 1) {
RAND_poll();
if(RAND_status() != 1)
throw Exc("SecureRandom: RNG not seeded");
}
#endif
if(RAND_bytes(reinterpret_cast<byte*>(ptr), len) != 1)
throw Exc("SecureRandom: RAND_bytes failed");
}
void Init()
{
static_assert(sizeof(uint64) == 8, "Secure random/nonce generator requires 64-bit integers");
SslInitThread();
ONCELOCK {
uint32 seed = 0;
FillRandom(&seed, sizeof(seed));
sCounter = (uint64) seed;
#ifdef PLATFORM_POSIX
pthread_atfork(nullptr, nullptr, [] {
sForked = true;
#if OPENSSL_VERSION_NUMBER < 0x10100000L
RAND_cleanup();
#endif
});
#endif
}
}
void HandleFork()
{
#ifdef PLATFORM_POSIX
if(!sForked.load())
return;
// After fork(), child inherits RNG state. We must reseed once to avoid
// nonce/counter reuse. SpinLock ensures only one thread performs reseed
// while others wait until state becomes consistent.
SpinLock::Lock __(sLock);
if(sForked.load()) {
uint32 seed = 0;
FillRandom(&seed, sizeof(seed));
sCounter = (uint64) seed;
sId = 0;
sForked = false;
}
#endif
}
uint64 GetNonceDomainId()
{
if(uint64 v = sId.load(); v)
return v;
uint64 x = 0;
FillRandom(&x, sizeof(x));
if(!x) x = 1;
uint64 expected = 0;
if(sId.compare_exchange_strong(expected, x))
return x;
return sId.load();
}
uint64 NextCounter()
{
// simple atomic increment is enough here
uint64 v = ++sCounter;
if(v == 0)
throw Exc("SecureRandom: counter overflow");
return v;
}
}
SecureBuffer<byte> SecureRandom(int n)
{
Init();
HandleFork();
n = max(1, n);
SecureBuffer<byte> out(n);
FillRandom(~out, n);
return pick(out);
}
SecureBuffer<byte> SecureNonce(int n)
{
Init();
HandleFork();
uint64 did = GetNonceDomainId();
uint64 cnt = NextCounter();
n = max(n, NONCE_MIN);
SecureBuffer<byte> out(n);
byte *p = ~out;
// 12-15 byte layout
// 4 bytes PID | 8 bytes counter | [random tail]
if(n < NONCE_STRUCTURED_MIN) {
Poke32(p, (dword) did);
p += sizeof(dword);
Poke64(p, (int64) cnt);
p += sizeof(int64);
if(int len = n - NONCE_MIN; len > 0)
FillRandom(p, len);
return pick(out);
}
// 16-byte structured layout
// 8 bytes PID | 8 bytes counter | [random tail]
Poke64(p, (int64) did);
p += sizeof(int64);
Poke64(p, (int64) cnt);
p += sizeof(int64);
if(int len = n - NONCE_STRUCTURED_MIN; len > 0)
FillRandom(p, len);
return pick(out);
}
}

View file

@ -185,4 +185,21 @@ bool AES256Decrypt(Stream& in, const String& password, Stream& out, Gate<int64,
// Secure buffer
#include "Buffer.hpp"
// Secure Random Generator
SecureBuffer<byte> SecureRandom(int n);
SecureBuffer<byte> SecureNonce(int n);
inline SecureBuffer<byte> GetAESGCMNonce() { return SecureNonce(12); } // 12 bytes, optimal for AES-GCM
inline SecureBuffer<byte> GetChaChaPoly1305Nonce() { return SecureNonce(12); } // 12 bytes, standard for ChaCha20-Poly1305
inline SecureBuffer<byte> GetTLSNonce() { return SecureNonce(12); } // 12 bytes, used in TLS 1.2/1.3
inline SecureBuffer<byte> GetAESCCMNonce() { return SecureNonce(13); } // 13 bytes, max size for AES-CCM
inline SecureBuffer<byte> GetJWTNonce() { return SecureNonce(16); } // 16 bytes, good for JWT
inline SecureBuffer<byte> GetOAuthNonce() { return SecureNonce(16); } // 16 bytes, common for OAuth
inline SecureBuffer<byte> GetOCSPNonce() { return SecureNonce(20); } // 20 bytes, OCSP nonce extension
inline SecureBuffer<byte> GetECDSANonce() { return SecureNonce(32); } // 32 bytes, for ECDSA signatures
inline SecureBuffer<byte> GetDTLSCookie() { return SecureNonce(32); } // 32 bytes, DTLS cookie
}

View file

@ -14,6 +14,7 @@ file
P7S.cpp,
AES.cpp,
Buffer.hpp,
Random.cpp,
SSL.icpp,
Docs readonly separator,
src.tpp,

View file

@ -0,0 +1,69 @@
topic "Secure random data and nonce generation";
[i448;a25;kKO9;2 $$1,0#37138531426314131252341829483380:class]
[l288;2 $$2,2#27521748481378242620020725143825:desc]
[0 $$3,0#96390100711032703541132217272105:end]
[H6;0 $$4,0#05600065144404261032431302351956:begin]
[i448;a25;kKO9;2 $$5,0#37138531426314131252341829483370:item]
[l288;a4;*@5;1 $$6,6#70004532496200323422659154056402:requirement]
[l288;i1121;b17;O9;~~~.1408;2 $$7,0#10431211400427159095818037425705:param]
[i448;b42;O9;2 $$8,8#61672508125594000341940100500538:tparam]
[b42;2 $$9,9#13035079074754324216151401829390:normal]
[2 $$0,0#00000000000000000000000000000000:Default]
[{_}
[ {{10000@(113.42.0) [s0;%% [*@7;4 Secure Random and Nonce Generators]]}}&]
[s2; &]
[s2;%% These functions provides a cryptographically secure random
number and nonces compliant with NIST SP 800`-38D, tailored for
high`-security applications that demand guaranteed uniqueness
and strong collision resistance. The implementation ensures
process`-unique nonces and is fork`-safe on POSIX systems, automatically
reseeding after a fork to avoid duplication. &]
[s2;%% &]
[s2;%% The implementation is [/ thread`-safe], supporting concurrent
initialization and generation across threads without race conditions.
It enforces a minimum nonce size of 12 bytes, aligning with cryptographic
standards. &]
[s2;%% &]
[s2;%% The generator offers two distinct modes: one for producing
unique, non`-repeating nonces, and another for extracting purely
random data suitable for general`-purpose cryptographic use.&]
[s2; &]
[s3; &]
[ {{10000F(128)G(128)@1 [s0;%% [* Function List]]}}&]
[s3; &]
[s5;:Upp`:`:SecureRandom`(int`): SecureBuffer<[@(0.255.0) byte]> [* SecureRandom]([@(0.0.255) i
nt] [*@3 n])&]
[s2;%% Generates [%-*@3 n] cryptographically secure random bytes. Enforces
a minimum size of 1 bytes. Throws [^topic`:`/`/Core`/src`/Exc`_en`-us`#Exc`:`:class^ e
xception ]on failure.&]
[s3; &]
[s4; &]
[s5;:Upp`:`:SecureNonce`(int`): SecureBuffer<[@(0.255.0) byte]> [* SecureNonce]([@(0.0.255) i
nt] [*@3 n])&]
[s0;l288;%% Generates a secure nonce of [%-*@3 n] bytes. Enforces a
minimum size of 12 bytes. Throws [^topic`:`/`/Core`/src`/Exc`_en`-us`#Exc`:`:class^ e
xception ]on failure. The returned value is a structured binary
nonce produced from internal&]
[s2;%% domain, counter, and optional entropy components. All internal
multi`-byte fields are encoded using little`-endian layout.&]
[s3; &]
[ {{10000F(128)G(128)@1 [s0;%% [* Standard Nonce Helpers]]}}&]
[s3; &]
[s5;:Upp`:`:GetAESGCMNonce`(`): SecureBuffer<[@(0.255.0) byte]> [* GetAESGCMNonce]()&]
[s5;:Upp`:`:GetChaChaPoly1305Nonce`(`): SecureBuffer<[@(0.255.0) byte]>
[* GetChaChaPoly1305Nonce]()&]
[s5;:Upp`:`:GetTLSNonce`(`): SecureBuffer<[@(0.255.0) byte]> [* GetTLSNonce]()&]
[s5;:Upp`:`:GetAESCCMNonce`(`): SecureBuffer<[@(0.255.0) byte]> [* GetAESCCMNonce]()&]
[s5;:Upp`:`:GetJWTNonce`(`): SecureBuffer<[@(0.255.0) byte]> [* GetJWTNonce]()&]
[s5;:Upp`:`:GetOAuthNonce`(`): SecureBuffer<[@(0.255.0) byte]> [* GetOAuthNonce]()&]
[s5;:Upp`:`:GetOCSPNonce`(`): SecureBuffer<[@(0.255.0) byte]> [* GetOCSPNonce]()&]
[s5;:Upp`:`:GetECDSANonce`(`): SecureBuffer<[@(0.255.0) byte]> [* GetECDSANonce]()&]
[s5;:Upp`:`:GetDTLSCookie`(`): SecureBuffer<[@(0.255.0) byte]> [* GetDTLSCookie]()&]
[s2;%% These helper functions generate cryptographically secure nonces
of commonly required lengths for widely used protocols and standards
such as AES`-GCM, ChaCha20`-Poly1305, TLS, JWT, and ECDSA. Each
helper ensures the nonce meets the expected size and uniqueness
guarantees of its respective use case. Each helper throws [^topic`:`/`/Core`/src`/Exc`_en`-us`#Exc`:`:class^ e
xception ]on failure.&]
[s3; &]
[s0;%% ]]

View file

@ -8,7 +8,7 @@ uses
library(WIN32) "advapi32 comdlg32 comctl32 imm32";
library(PORTABLE_HYBRID) "brotlidec brotlicommon bz2 Xau";
library(PORTABLE_HYBRID) "brotlidec brotlicommon bz2 Xau uuid";
pkg_config(POSIX !OSX !VIRTUALGUI) "freetype2 x11 xinerama xrender xft xdmcp fontconfig xcb xext";

View file

@ -580,11 +580,21 @@ bool GccBuilder::Link(const Vector<String>& linkfile, const String& linkoptions,
if(!HasFlag("SOLARIS") && !HasFlag("OSX") && !HasFlag("OBJC"))
lnk << " -Wl,--start-group ";
for(String s : pkg_config)
lnk << " `" << Host::CMDLINE_PREFIX << "pkg-config --libs " << s << "`";
if(portable) {
DDUMP(s);
Vector<String> libs = Split(HostSys("pkg-config --libs " + s), CharFilterWhitespace);
DDUMP(libs);
libs.RemoveIf([&](int i) { return findarg(libs[i], "-ldl", "-lpthread", "-lrt", "-lm") >= 0; });
DDUMP(libs);
if(libs.GetCount())
lnk << ' ' << Join(libs, " ");
}
else
lnk << " `" << Host::CMDLINE_PREFIX << "pkg-config --libs " << s << "`";
for(int pass = 0; pass < 2; pass++) {
for(i = 0; i < lib.GetCount(); i++) {
String ln = lib[i];
if(portable && (ln == "dl" || ln == "pthread" || ln == "rt"))
if(portable && (ln == "dl" || ln == "pthread" || ln == "rt" || ln == "m"))
continue;
String ext = ToLower(GetFileExt(ln));
@ -618,7 +628,7 @@ bool GccBuilder::Link(const Vector<String>& linkfile, const String& linkoptions,
lnk << " -Wl,--end-group";
}
if(portable)
lnk << " -Wl,-Bdynamic -lpthread -ldl -lrt";
lnk << " -Wl,-Bdynamic -lpthread -ldl -lrt -lm";
PutConsole("Linking...");
bool error = false;