LibCrypto+AK: Merge LibCrypto/SecureRandom into AK/Random

AK/Random is already the same as SecureRandom. See PR for more details.

ProcessPrng is used on Windows for compatibility w/ sandboxing measures
See e.g. https://crbug.com/40277768
This commit is contained in:
Colleirose 2026-01-02 16:48:45 -08:00 committed by Jelle Raaijmakers
parent d942b98549
commit bf7fd80140
15 changed files with 104 additions and 109 deletions

View file

@ -1,68 +1,61 @@
/*
* Copyright (c) 2021, the SerenityOS developers.
* Copyright (c) 2024, the Ladybird developers.
* Copyright (c) 2025-2026, Colleirose <criticskate@pm.me>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Assertions.h>
#include <AK/Platform.h>
#include <AK/Random.h>
#include <AK/UFixedBigInt.h>
#include <AK/UFixedBigIntDivision.h>
#if defined(AK_OS_WINDOWS)
# include <AK/NumericLimits.h>
# include <AK/Windows.h>
# include <bcrypt.h>
# include <ntstatus.h>
#if defined(AK_OS_LINUX)
# include <sys/random.h>
#endif
#if defined(AK_OS_WINDOWS)
# include <AK/Windows.h>
#endif
static inline ErrorOr<void> csprng(void* const buf, size_t size)
{
// We shouldn't use OpenSSL's RAND_bytes function here, because we want to avoid adding dependencies to AK.
// Therefore, we will use the best platform-specific CSPRNG.
#if defined(AK_OS_SERENITY) || defined(AK_OS_ANDROID) || defined(AK_OS_BSD_GENERIC) || defined(AK_OS_HAIKU) || AK_LIBC_GLIBC_PREREQ(2, 36)
// This target also covers MacOS and iOS and they both seem to support arc4random_buf
arc4random_buf(buf, size);
#elif defined(AK_OS_LINUX)
unsigned char* out = (unsigned char*)buf;
while (size > 0u) {
// EINTR can be handled safely by just trying again. Others are fatal
// See manual for more details
int ret = getrandom(out, size, 0);
if (ret == -1 && errno != EINTR) [[unlikely]]
return Error::from_errno(errno);
// If ret > 0 then ret indicates how much was copied
if (ret > 0) [[likely]] {
size -= ret;
out += ret;
}
}
#elif defined(AK_OS_WINDOWS)
// Documented to always return TRUE
g_system.ProcessPrng((PBYTE)buf, size);
#else
static_assert(false, "This build target doesn't have a valid CSPRNG interface specified in AK/Random.cpp.");
#endif
return {};
}
namespace AK {
// NOTE: This function is supposed to always give a random number. If possible it is of good quality, but it can fall
// back to rand() if it fails on some systems. For high speed you should probably use a different generator.
// See MathObject::random() from LibJS. Where cryptographic security is needed use LibCrypto/SecureRandom.h.
void fill_with_random([[maybe_unused]] Bytes bytes)
void fill_with_random(Bytes bytes)
{
#if defined(AK_OS_SERENITY) || defined(AK_OS_ANDROID) || defined(AK_OS_BSD_GENERIC) || defined(AK_OS_HAIKU) || AK_LIBC_GLIBC_PREREQ(2, 36)
arc4random_buf(bytes.data(), bytes.size());
#elif defined(OSS_FUZZ)
#else
auto fill_with_random_fallback = [&]() {
for (auto& byte : bytes)
byte = rand();
};
# if defined(__unix__)
// The maximum permitted value for the getentropy length argument.
static constexpr size_t getentropy_length_limit = 256;
auto iterations = bytes.size() / getentropy_length_limit;
for (size_t i = 0; i < iterations; ++i) {
if (getentropy(bytes.data(), getentropy_length_limit) != 0) {
fill_with_random_fallback();
return;
}
bytes = bytes.slice(getentropy_length_limit);
}
if (bytes.is_empty() || getentropy(bytes.data(), bytes.size()) == 0)
return;
# elif defined(AK_OS_WINDOWS)
if (bytes.size() > NumericLimits<u32>::max()) [[unlikely]] {
fill_with_random_fallback();
return;
}
// NOTE: This is more secure than needed. But on modern hardware it be should more than fast enough.
NTSTATUS result = ::BCryptGenRandom(NULL, bytes.data(), bytes.size(), BCRYPT_USE_SYSTEM_PREFERRED_RNG);
if (result == STATUS_SUCCESS)
return;
# endif
fill_with_random_fallback();
#endif
MUST(csprng(bytes.data(), bytes.size()));
}
u32 get_random_uniform(u32 max_bounds)

View file

@ -44,28 +44,37 @@ NTAPI NTSTATUS NtCreateWaitCompletionPacket(
_Out_ PHANDLE WaitCompletionPacketHandle,
_In_ ACCESS_MASK DesiredAccess,
_In_opt_ POBJECT_ATTRIBUTES ObjectAttributes);
// https://learn.microsoft.com/en-us/windows/win32/seccng/processprng
BOOL WINAPI ProcessPrng(PBYTE pbData, SIZE_T cbData);
}
using PFN_NtCreateWaitCompletionPacket = decltype(&NtCreateWaitCompletionPacket);
using PFN_NtCancelWaitCompletionPacket = decltype(&NtCancelWaitCompletionPacket);
using PFN_NtAssociateWaitCompletionPacket = decltype(&NtAssociateWaitCompletionPacket);
using PFN_ProcessPrng = decltype(&ProcessPrng);
inline struct SystemApi {
PFN_NtAssociateWaitCompletionPacket NtAssociateWaitCompletionPacket = NULL;
PFN_NtCancelWaitCompletionPacket NtCancelWaitCompletionPacket = NULL;
PFN_NtCreateWaitCompletionPacket NtCreateWaitCompletionPacket = NULL;
PFN_ProcessPrng ProcessPrng = NULL;
SystemApi()
{
HMODULE hBcryptprimitives = LoadLibraryW(L"bcryptprimitives.dll");
HMODULE hNtdll = GetModuleHandleW(L"ntdll.dll");
VERIFY(hBcryptprimitives);
VERIFY(hNtdll);
AK_IGNORE_DIAGNOSTIC("-Wcast-function-type-mismatch",
NtAssociateWaitCompletionPacket = (PFN_NtAssociateWaitCompletionPacket)GetProcAddress(hNtdll, "NtAssociateWaitCompletionPacket");
NtCancelWaitCompletionPacket = (PFN_NtCancelWaitCompletionPacket)GetProcAddress(hNtdll, "NtCancelWaitCompletionPacket");
NtCreateWaitCompletionPacket = (PFN_NtCreateWaitCompletionPacket)GetProcAddress(hNtdll, "NtCreateWaitCompletionPacket");)
NtCreateWaitCompletionPacket = (PFN_NtCreateWaitCompletionPacket)GetProcAddress(hNtdll, "NtCreateWaitCompletionPacket");
ProcessPrng = (PFN_ProcessPrng)GetProcAddress(hBcryptprimitives, "ProcessPrng");)
VERIFY(NtAssociateWaitCompletionPacket);
VERIFY(NtCancelWaitCompletionPacket);
VERIFY(NtCreateWaitCompletionPacket);
VERIFY(ProcessPrng);
}
} g_system;

View file

@ -26,7 +26,6 @@ set(SOURCES
PK/MLDSA.cpp
PK/MLKEM.cpp
PK/RSA.cpp
SecureRandom.cpp
)
ladybird_lib(LibCrypto crypto)

View file

@ -7,7 +7,6 @@
#include <AK/ByteBuffer.h>
#include <AK/Debug.h>
#include <AK/Random.h>
#include <LibCrypto/ASN1/ASN1.h>
#include <LibCrypto/ASN1/DER.h>
#include <LibCrypto/ASN1/PEM.h>

View file

@ -1,21 +0,0 @@
/*
* Copyright (c) 2024, the Ladybird developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibCrypto/SecureRandom.h>
#include <openssl/rand.h>
namespace Crypto {
void fill_with_secure_random(Bytes bytes)
{
auto const size = static_cast<int>(bytes.size());
if (RAND_bytes(bytes.data(), size) != 1)
VERIFY_NOT_REACHED();
}
}

View file

@ -1,23 +0,0 @@
/*
* Copyright (c) 2024, the Ladybird developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Span.h>
namespace Crypto {
void fill_with_secure_random(Bytes);
template<typename T>
inline T get_secure_random()
{
T t;
fill_with_secure_random({ &t, sizeof(T) });
return t;
}
}

View file

@ -10,7 +10,6 @@
#include <AK/BuiltinWrappers.h>
#include <AK/Function.h>
#include <AK/Random.h>
#include <LibCrypto/SecureRandom.h>
#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/Iterator.h>

View file

@ -4,7 +4,7 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibCrypto/SecureRandom.h>
#include <AK/Random.h>
#include <LibURL/Origin.h>
#include <LibURL/Parser.h>
#include <LibURL/Site.h>
@ -13,7 +13,7 @@ namespace URL {
Origin Origin::create_opaque()
{
return Origin { Crypto::get_secure_random<Nonce>() };
return Origin { AK::get_random<Nonce>() };
}
// https://html.spec.whatwg.org/multipage/browsers.html#same-site

View file

@ -7,7 +7,6 @@
#include <AK/Random.h>
#include <AK/StringBuilder.h>
#include <LibCrypto/SecureRandom.h>
#include <LibJS/Runtime/TypedArray.h>
#include <LibWeb/Bindings/CryptoPrototype.h>
#include <LibWeb/Bindings/ExceptionOrUtils.h>
@ -67,7 +66,7 @@ WebIDL::ExceptionOr<GC::Root<WebIDL::ArrayBufferView>> Crypto::get_random_values
return WebIDL::QuotaExceededError::create(realm(), "array's byteLength may not be greater than 65536"_utf16);
// 3. Overwrite all elements of array with cryptographically strong random values of the appropriate type.
::Crypto::fill_with_secure_random(array->viewed_array_buffer()->buffer().bytes().slice(array->byte_offset(), array->byte_length()));
fill_with_random(array->viewed_array_buffer()->buffer().bytes().slice(array->byte_offset(), array->byte_length()));
// 4. Return array.
return array;
@ -94,7 +93,7 @@ ErrorOr<String> generate_random_uuid()
u8 bytes[16];
// 2. Fill bytes with cryptographically secure random bytes.
::Crypto::fill_with_secure_random(bytes);
fill_with_random(bytes);
// 3. Set the 4 most significant bits of bytes[6], which represent the UUID version, to 0100.
bytes[6] &= ~(1 << 7);

View file

@ -11,6 +11,7 @@
#include <AK/Base64.h>
#include <AK/HashTable.h>
#include <AK/QuickSort.h>
#include <AK/Random.h>
#include <LibCrypto/ASN1/ASN1.h>
#include <LibCrypto/ASN1/Constants.h>
#include <LibCrypto/ASN1/DER.h>
@ -29,7 +30,6 @@
#include <LibCrypto/PK/MLDSA.h>
#include <LibCrypto/PK/MLKEM.h>
#include <LibCrypto/PK/RSA.h>
#include <LibCrypto/SecureRandom.h>
#include <LibJS/Runtime/Array.h>
#include <LibJS/Runtime/ArrayBuffer.h>
#include <LibJS/Runtime/DataView.h>
@ -274,7 +274,7 @@ static WebIDL::ExceptionOr<void> validate_jwk_key_ops(JS::Realm& realm, Bindings
static WebIDL::ExceptionOr<ByteBuffer> generate_random_key(JS::VM& vm, u16 const size_in_bits)
{
auto key_buffer = TRY_OR_THROW_OOM(vm, ByteBuffer::create_uninitialized(size_in_bits / 8));
::Crypto::fill_with_secure_random(key_buffer);
fill_with_random(key_buffer);
return key_buffer;
}

View file

@ -14,12 +14,12 @@
#include <AK/Debug.h>
#include <AK/GenericLexer.h>
#include <AK/InsertionSort.h>
#include <AK/Random.h>
#include <AK/StringBuilder.h>
#include <AK/TemporaryChange.h>
#include <AK/Time.h>
#include <AK/Utf8View.h>
#include <LibCore/Timer.h>
#include <LibCrypto/SecureRandom.h>
#include <LibGC/RootVector.h>
#include <LibJS/Runtime/Array.h>
#include <LibJS/Runtime/FunctionObject.h>

View file

@ -8,7 +8,6 @@
#include <AK/Base64.h>
#include <AK/Random.h>
#include <LibCrypto/Hash/HashManager.h>
#include <LibCrypto/SecureRandom.h>
#include <LibWebSocket/Impl/WebSocketImplSerenity.h>
#include <LibWebSocket/WebSocket.h>
@ -205,7 +204,7 @@ void WebSocket::send_client_handshake()
// 7. 16-byte nonce encoded as Base64
u8 nonce_data[16];
Crypto::fill_with_secure_random(nonce_data);
fill_with_random(nonce_data);
// FIXME: change to TRY() and make method fallible
m_websocket_key = MUST(encode_base64({ nonce_data, 16 })).to_byte_string();
builder.appendff("Sec-WebSocket-Key: {}\r\n", m_websocket_key);
@ -621,7 +620,7 @@ void WebSocket::send_frame(WebSocket::OpCode op_code, ReadonlyBytes payload, boo
// > Clients MUST choose a new masking key for each frame, using an algorithm
// > that cannot be predicted by end applications that provide data
u8 masking_key[4];
Crypto::fill_with_secure_random(masking_key);
fill_with_random(masking_key);
buf.overwrite(offset, masking_key, 4);
offset += 4;
// don't try to send empty payload

View file

@ -30,6 +30,5 @@ shared_library("LibCrypto") {
"Hash/SHA1.cpp",
"Hash/SHA2.cpp",
"PK/RSA.cpp",
"SecureRandom.cpp",
]
}

View file

@ -55,6 +55,7 @@ set(AK_TEST_SOURCES
TestOwnPtr.cpp
TestQueue.cpp
TestQuickSort.cpp
TestRandom.cpp
TestRedBlackTree.cpp
TestRefPtr.cpp
TestSegmentedVector.cpp

42
Tests/AK/TestRandom.cpp Normal file
View file

@ -0,0 +1,42 @@
/*
* Copyright (c) 2025-2026, Colleirose <criticskate@pm.me>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibTest/TestCase.h>
#include <AK/ByteBuffer.h>
#include <AK/Random.h>
TEST_CASE(csprng_generates_unique_values)
{
constexpr size_t size = 800;
constexpr size_t max_failures = 3;
size_t failures = 0;
for (size_t i = 0; i < 3; i++) {
ByteBuffer first_buffer = MUST(ByteBuffer::create_zeroed(size));
ByteBuffer second_buffer = MUST(ByteBuffer::create_zeroed(size));
AK::fill_with_random(first_buffer);
AK::fill_with_random(second_buffer);
u32 first_u32 = AK::get_random_uniform(size);
u32 second_u32 = AK::get_random_uniform(size);
u64 first_u64 = AK::get_random_uniform_64(size);
u64 second_u64 = AK::get_random_uniform_64(size);
if (first_buffer == second_buffer)
failures++;
if (first_u32 == second_u32)
failures++;
if (first_u64 == second_u64)
failures++;
}
EXPECT(failures < max_failures);
}