Skip to content

Get random data from OS APIs #13

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Sep 26, 2017
Merged

Conversation

wilzbach
Copy link
Member

@wilzbach wilzbach commented Dec 28, 2016

So I managed to find the issue in the Windows API. It was due to wrong API headers in druntime - for 64-bit pointers need to have 8 bytes.

Let me summarize:

Current Behavior

Todo

  • decide on an API
  • write more documentation
  • add support for D attributes
  • add tests

Future work

  • OpenBSD -> getentropy (Not implemented as I don't have a OpenBSD atm - anyone?)

References

C++ API: http://en.cppreference.com/w/cpp/numeric/random/random_device
LLVM stdcxx: https://github.com/llvm-mirror/libcxx/blob/master/src/random.cpp
G++: https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/src/c%2B%2B11/random.cc
Python urandom: https://github.com/python/cpython/blob/master/Python/random.c
Vibe.d cryptorand: https://github.com/rejectedsoftware/vibe.d/blob/master/crypto/vibe/crypto/cryptorand.d
pycrypto: https://github.com/dlitz/pycrypto/blob/master/src/winrand.c

@wilzbach wilzbach force-pushed the test-random-os-apis branch 2 times, most recently from 88aac41 to 655496f Compare December 28, 2016 08:58
mov RDX, arg2[RBP];
syscall;
mov ret, RAX;
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@9il Is dmd guaranteed to execute the asm block in one go?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I got the inspiration to use this syscall style from here: https://github.com/kubo39/syscall-d/blob/master/source/syscalld/arch/syscall_x86_64.d
Hence also the synchronized statements, but afaict we don't need it - e.g.:
https://wiki.dlang.org/LDC_inline_assembly_expressions#X86-64

@wilzbach wilzbach force-pushed the test-random-os-apis branch 14 times, most recently from fc776c3 to 0c14f5a Compare December 29, 2016 07:43
@wilzbach
Copy link
Member Author

wilzbach commented Jan 4, 2017

I accidentally came by two other implementation in the D ecosystem:

https://github.com/LightBender/SecureD/blob/master/source/random.d
https://github.com/etcimon/botan/blob/master/source/botan/libstate/libstate.d#L190

(the latter is interesting as it uses quite a bunch of entropy sources)

@9il
Copy link
Member

9il commented Jan 4, 2017

I accidentally came by two other implementation in the D ecosystem:

Awesome!

@9il
Copy link
Member

9il commented Jan 4, 2017

But do not spent much time on deprecated targets ;)

@9il
Copy link
Member

9il commented Jan 7, 2017

@wilzbach do you need help with this PR?

@wilzbach wilzbach force-pushed the test-random-os-apis branch from be9bb7c to cb955a8 Compare January 7, 2017 17:04
@wilzbach
Copy link
Member Author

wilzbach commented Jan 7, 2017

@wilzbach do you need help with this PR?

Oh sorry for letting this get stalled.

As discussed this exposes two different functions with C-style like interface, but with D-guarantees in terms of @safe nothrow @nogc:

  • getRandomBlocking
  • getRandomNonBlocking

The difference between these two functions is that the Blocking one will loop until the buffer is filled (i.e. until enough entropy is generated), while the NonBlocking one will yield an error code.
If initialization needs to be done, it's done on the first call.

@9il please let me know if there's sth. missing for this PR.

About Blocking behavior:

tl;dr: only useful on Linux.

The /dev/random device is a legacy interface which dates back to a
time where the cryptographic primitives used in the implementation of
/dev/urandom were not widely trusted. It will return random bytes
only within the estimated number of bits of fresh noise in the
entropy pool, blocking if necessary. /dev/random is suitable for
applications that need high quality randomness, and can afford
indeterminate delays.

When the entropy pool is empty, reads from /dev/random will block
until additional environmental noise is gathered.

And for the newer getRandom:

GRND_NONBLOCK
By default, when reading from the random source, getrandom()
blocks if no random bytes are available, and when reading from
the urandom source, it blocks if the entropy pool has not yet
been initialized. If the GRND_NONBLOCK flag is set, then
getrandom() does not block in these cases, but instead
immediately returns -1 with errno set to EAGAIN.

  • Windows doesn't support blocking behavior (the documentation doesn't even state how CryptGenRandom behaves if the entropy pool is empty)
  • macOS doesn't support blocking either (according to the docs it isn't necessary to block as a special SecurityServer takes care of ensuring that there's always enough entropy)

Copy link
Member

@9il 9il left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Each public function should be extern(C) and starts with mir_random_. mir_random_genRandomBlocking. An alias can be added: genRandomBlocking = mir_random_genRandomBlocking. Please replace get* with gen*.

k ^= k >> 33;
return cast(size_t)k;
size_t seed;
//getRandomBlocking(&seed, seed.sizeof);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Lets preserve old version if getRandomBlocking returns -1
  2. and uncomment this line)

import core.sys.posix.sys.utsname : utsname;

// druntime isn't properly annotated
extern(C) int uname(utsname* __name) @nogc nothrow;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

private

return false;
}

extern(C) int syscall(size_t ident, size_t n, size_t arg1, size_t arg2) @nogc nothrow;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

private

{
auto res = syscall(GETRANDOM, cast(size_t) ptr + genBytes, len - genBytes, 0);
if (res >= 0)
genBytes -= res;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bug.
Just move ptr and reduce length, instead of genBytes variable

import core.stdc.stdio : fclose, feof, ferror, fopen, fread;
alias IOType = typeof(fopen("a", "b"));
static private IOType fdRandom;
static private IOType fdURandom;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

__gshared

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

private

static private IOType fdRandom;
static private IOType fdURandom;

shared static ~this()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extern(C)

extern(Windows) BOOL CryptGenRandom(HCRYPTPROV, DWORD, PBYTE) @nogc @safe nothrow;
extern(Windows) BOOL CryptAcquireContextA(HCRYPTPROV*, LPCSTR, LPCSTR, DWORD, DWORD) @nogc nothrow;
extern(Windows) BOOL CryptAcquireContextW(HCRYPTPROV*, LPCWSTR, LPCWSTR, DWORD, DWORD) @nogc nothrow;
extern(Windows) BOOL CryptReleaseContext(HCRYPTPROV, ULONG_PTR) @nogc nothrow;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

private

return 0;
}

shared static ~this()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extern(C)

extern(Windows) BOOL CryptAcquireContextW(HCRYPTPROV*, LPCWSTR, LPCWSTR, DWORD, DWORD) @nogc nothrow;
extern(Windows) BOOL CryptReleaseContext(HCRYPTPROV, ULONG_PTR) @nogc nothrow;

private static ULONG_PTR hProvider;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

__gshared

@wilzbach
Copy link
Member Author

wilzbach commented Jan 8, 2017

Each public function should be extern(C) and starts with mir_random_. mir_random_genRandomBlocking

Ok - questions:

  • It's currently in the mir.random.engine - so shouldn't it be mir_random_engine_genRandomBlocking?
  • This will look ugly on the documentation pages. Any trick for this? Can't we use extern(C) on an alias?
  • What about the module deconstructor? Should we expose sth. like mir_random_engine_dtor?

__gshared

I assume I can't use any locking in betterC?
There are four __gshared variables then. In case of a race:

  • hasGetRandom: Linux kernel version will be calculated twice (will always yield the same) -> not a problem
  • hProvider: we could acquire two Windows Crypto contexts (leak)
  • fdRandom / fdURandom: we could open the file twice and leak a file handle

We could provide a special init method which inits the handles and ensures that no leaks can occur. On the D side it could automatically be called with the shared module constructor

if (initGetRandom != 0)
return -1;

while(!CryptGenRandom(hProvider, len, cast(PBYTE) ptr)) {}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For Windows all error codes of CryptGenRandom seem unrecoverable, so the loop here doesn't seem very useful. I think we should directly yield the error here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

@9il
Copy link
Member

9il commented Jan 8, 2017

It's currently in the mir.random.engine - so shouldn't it be mir_random_engine_genRandomBlocking?

no

This will look ugly on the documentation pages. Any trick for this? Can't we use extern(C) on an alias?

This should be a part of documentation.

What about the module deconstructor? Should we expose sth. like mir_random_engine_dtor?

Yes, but extern(C) should be added to the D's module destructor.

@9il
Copy link
Member

9il commented Jan 8, 2017

We could provide a special init method which inits the handles and ensures that no leaks can occur. On the D side it could automatically be called with the shared module constructor

mir_random_engine_ctor and the extern(C) D's module constructor

@9il
Copy link
Member

9il commented Feb 3, 2017

Hey @wilzbach , could you please finalise this PR?

@wilzbach
Copy link
Member Author

wilzbach commented Mar 2, 2017

Hey @wilzbach , could you please finalise this PR?

Sorry that I left this pending for such a long time.

mir_random_engine_ctor and the extern(C) D's module constructor

Added :)

@9il
Copy link
Member

9il commented Mar 7, 2017

@wilzbach please convert all private functions to templates and add version(LDC) pragma(inline, true); for them. This will make object files clearer.

@9il 9il merged commit 65a07f9 into libmir:master Sep 26, 2017
n8sh added a commit to n8sh/vibe.d that referenced this pull request Mar 1, 2018
n8sh added a commit to n8sh/vibe.d that referenced this pull request Mar 1, 2018
n8sh added a commit to n8sh/vibe.d that referenced this pull request Mar 1, 2018
n8sh added a commit to n8sh/vibe.d that referenced this pull request Mar 1, 2018
@wilzbach wilzbach deleted the test-random-os-apis branch April 6, 2018 17:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants