Monday, October 17, 2016

Digging into browser CSPRNG

Browsers nowadays support the window.crypto.getRandomValues() API for obtaining cryptographically secure random number values suitable for generating private keys and session tokens. And while it’s questionable if in-browser JavaScript crypto is really secure (it still requires a flawless TLS configuration. Forget about encrypting stuff without HTTPS enabled), more clients and customers ask me for implementing crypto in the browser. Oh well.

Here is a quick review of how the getRandomValues() API is implemented in open source browsers, as of 18 October 2016, so that we can be sure that nothing shady or lame (such as running current time through the Mersenne Twister) is going on.

WebKit

Here it’s pretty straightforward. The first function file is the implementation of the API and it goes directly to the OS.

Chromium

Chromium has their own fork of WebKit but essentially it’s the same story. It uses a global random number generator in base namespace.

Firefox

Uh … here it, uhm… complicated?

The journey starts in the cpp file responsible for that JS function:

Here it’s invoking the random number generation service, “@mozilla.org/security/random-generator”. Well, uh, hope it can’t be overriden from chrome JavaScript or something.
The service implementation is here and it calls PK11_GenerateRandomOnSlot:

This one calls C_GenerateRandom here (or possibly other PKCS11 implementations, https://dxr.mozilla.org/mozilla-central/search?q=C_GenerateRandom)

There is a deterministic random byte generator here. It calls RNG_SystemRNG once on boot to init its internal state.

The windows implementation calls RtlGenRandom instead of CryptGenRandom which is the official
CSPRNG API on Windows. Although the docs don’t say it is crypto-safe, it is used by rand_s from the CRT and that is documented to be crypto-secure.

Live Action

Then I attached a debugger and put a breakpoint at the critical points. And ran the crypto JS API in a loop. Here we can see it goes through the path as expected:

>   freebl3.dll!prng_generateNewBytes(RNGContextStr * rng, unsigned char * returned_bytes, unsigned int no_of_returned_bytes, const unsigned char * additional_input, unsigned int additional_input_len) Line 338   C
    freebl3.dll!prng_GenerateGlobalRandomBytes(RNGContextStr * rng, void * dest, unsigned int len) Line 642 C
    freebl3.dll!RNG_GenerateGlobalRandomBytes(void * dest, unsigned int len) Line 659   C
    nss3.dll!PK11_GenerateRandomOnSlot(PK11SlotInfoStr * slot, unsigned char * data, int len) Line 2247 C
    xul.dll!nsRandomGenerator::GenerateRandomBytes(unsigned int aLength, unsigned char * * aBuffer) Line 37 C++
    xul.dll!mozilla::dom::Crypto::GetRandomValues(JSContext * aCx, const mozilla::dom::ArrayBufferView_base<&js::UnwrapArrayBufferView,&js::GetArrayBufferViewLengthAndData,&JS_GetArrayBufferViewType> & aArray, JS::MutableHandle<JSObject *> aRetval, mozilla::ErrorResult & aRv) Line 105   C++
    xul.dll!mozilla::dom::CryptoBinding::getRandomValues(JSContext * cx, JS::Handle<JSObject *> obj, mozilla::dom::Crypto * self, const JSJitMethodCallArgs & args) Line 70 C++

And seeding the DRBG from the OS once on startup:

>   freebl3.dll!rng_init() Line 419 C
    nss3.dll!PR_CallOnce(PRCallOnceType * once, PRStatus(*)() func) Line 779    C
    freebl3.dll!RNG_RNGInit() Line 495  C
    nss3.dll!secmod_ModuleInit(SECMODModuleStr * mod, SECMODModuleStr * * reload, int * alreadyLoaded) Line 232 C
    nss3.dll!secmod_LoadPKCS11Module(SECMODModuleStr * mod, SECMODModuleStr * * oldModule) Line 480 C
    nss3.dll!SECMOD_LoadModule(char * modulespec, SECMODModuleStr * parent, int recurse) Line 1537  C
    nss3.dll!SECMOD_LoadModule(char * modulespec, SECMODModuleStr * parent, int recurse) Line 1572  C
    nss3.dll!nss_InitModules(const char * configdir, const char * certPrefix, const char * keyPrefix, const char * secmodName, const char * updateDir, const char * updCertPrefix, const char * updKeyPrefix, const char * updateID, const char * updateName, char * configName, char * configStrings, int pwRequired, int readOnly, int noCertDB, int noModDB, int forceOpen, int optimizeSpace, int isContextInit) Line 436   C
    nss3.dll!nss_Init(const char * configdir, const char * certPrefix, const char * keyPrefix, const char * secmodName, const char * updateDir, const char * updCertPrefix, const char * updKeyPrefix, const char * updateID, const char * updateName, NSSInitContextStr * * initContextPtr, NSSInitParametersStr * initParams, int readOnly, int noCertDB, int noModDB, int forceOpen, int noRootInit, int optimizeSpace, int noSingleThreadedModules, int allowAlreadyInitializedModules, int dontFinalizeModules) Line 638   C
    nss3.dll!NSS_Initialize(const char * configdir, const char * certPrefix, const char * keyPrefix, const char * secmodName, unsigned int flags) Line 812  C
    xul.dll!mozilla::psm::InitializeNSS(const char * dir, bool readOnly, bool loadPKCS11Modules) Line 976   C++
    xul.dll!nsNSSComponent::InitializeNSS() Line 1742   C++
    xul.dll!nsNSSComponent::Init() Line 1948    C++
    xul.dll!nsNSSComponentConstructor(nsISupports * aOuter, const nsID & aIID, void * * aResult) Line 174   C++
    xul.dll!nsComponentManagerImpl::CreateInstanceByContractID(const char * aContractID, nsISupports * aDelegate, const nsID & aIID, void * * aResult) Line 1203    C++
    xul.dll!nsComponentManagerImpl::GetServiceByContractID(const char * aContractID, const nsID & aIID, void * * aResult) Line 1561 C++
    xul.dll!nsCOMPtr_base::assign_from_gs_contractid(const nsGetServiceByContractID aGS, const nsID & aIID) Line 103    C++
    xul.dll!nsCOMPtr<nsINSSComponent>::nsCOMPtr<nsINSSComponent>(const nsGetServiceByContractID aGS) Line 541   C++
    xul.dll!EnsureNSSInitialized(EnsureNSSOperator op) Line 196 C++

Conclusion

In summary, the browsers behave as expected, providing random numbers seeded by the OS crypto-safe random number generator.

No comments:

Post a Comment