From f0ea93f77ef2a8dcd0a58e3eddeebc89977d9ed3 Mon Sep 17 00:00:00 2001 From: Jacek Sieka Date: Wed, 15 Jun 2022 22:53:40 +0200 Subject: [PATCH] rand: convenience API (#28) Helpers to generate random numbers, including simple Nim types * disable C++ tests * there are too many `const` vs `non-const` issues to run these tests - C++ never actually worked * add generateBytes to make fresh seqs Co-authored-by: Mamy Ratsimbazafy Co-authored-by: Etan Kissling --- .github/workflows/ci.yml | 1 - README.md | 5 ++ bearssl/rand.nim | 100 +++++++++++++++++++++++++++++++++++++-- tests/test_rand.nim | 62 ++++++++++++++++++++++++ 4 files changed, 163 insertions(+), 5 deletions(-) create mode 100644 tests/test_rand.nim diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dc533f0..4c1ba70 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -163,4 +163,3 @@ jobs: nimble --version nimble install -y --depsOnly env TEST_LANG="c" nimble test - env TEST_LANG="cpp" nimble test diff --git a/README.md b/README.md index 50ae5bd..7823f3d 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,11 @@ In general, the mappings follow the conventions of the original BearSSL library * `uint` used instead of `csize_t` - these are the same type in Nim, but spelled more conveniently * Canonical nim code will have to be careful when converting existing `int` lengths, looking out for out-of-range values +In addition to the raw `C`-like api, convenience functions are added where applicable - these follow a similar set of conventions: + +* named after the function they simplify, but take advantage of types and overload support in Nim +* help turn pointers and bytes into Nim types + ## Installation You can install the developement version of the library through nimble with the following command: diff --git a/bearssl/rand.nim b/bearssl/rand.nim index 024b3ed..300363f 100644 --- a/bearssl/rand.nim +++ b/bearssl/rand.nim @@ -1,8 +1,100 @@ import - ./abi/bearssl_rand + typetraits, + ./abi/[bearssl_hash, bearssl_rand] export bearssl_rand -func hmacDrbgGenerate*(ctx: var HmacDrbgContext, output: var openArray[byte]) = - if output.len > 0: - hmacDrbgGenerate(ctx, addr output[0], uint output.len) +# About types used in helpers: +# `bool` types are problematic because because they only use one bit of the +# entire byte - a similar problem occurs with `object` types with alignment +# gaps - `supportsCopyMem` is wrong here, we should be using `supportsMemCmp` or +# something similar that takes into account these issues, but alas, there's no +# such trait as of now + +proc init*[S](T: type HmacDrbgContext, seed: openArray[S]): HmacDrbgContext = + ## Create a new randomness context with the given seed - typically, a single + ## instance per thread should be created. + ## + ## The seed can later be topped up with `update`. + static: doAssert supportsCopyMem(S) and sizeof(S) > 0 and S isnot bool + + if seed.len == 0: + hmacDrbgInit(result, addr bearssl_hash.sha256Vtable, nil, 0) + else: + # In theory the multiplication can overflow, but practically we can't + # allocate that much memory, so it won't + hmacDrbgInit( + result, addr sha256Vtable, unsafeAddr seed[0], uint seed.len * sizeof(S)) + +proc new*(T: type HmacDrbgContext): ref HmacDrbgContext = + ## Create a new randomness context intended to be shared between randomness + ## consumers - typically, a single instance per thread should be created. + ## + ## The context is seeded with randomness from the OS / system. + ## Returns `nil` if the OS / system has no randomness API. + let seeder = prngSeederSystem(nil) + if seeder == nil: + return nil + + let rng = (ref HmacDrbgContext)() + hmacDrbgInit(rng[], addr sha256Vtable, nil, 0) + + if seeder(addr rng.vtable) == 0: + return nil + + rng + +func generate*(ctx: var HmacDrbgContext, v: var auto) = + ## Fill `v` with random data - `v` must be a simple type + static: doAssert supportsCopyMem(type v) + + when sizeof(v) > 0: + when v is bool: + # `bool` would result in a heavily biased value because >0 == true + var tmp: byte + hmacDrbgGenerate(ctx, addr tmp, uint sizeof(tmp)) + v = (tmp and 1'u8) == 1 + else: + hmacDrbgGenerate(ctx, addr v, uint sizeof(v)) + +func generate*[V](ctx: var HmacDrbgContext, v: var openArray[V]) = + ## Fill `v` with random data - `T` must be a simple type + static: doAssert supportsCopyMem(V) and sizeof(V) > 0 + + when V is bool: + for b in v.mitems: + ctx.generate(b) + else: + if v.len > 0: + # In theory the multiplication can overflow, but practically we can't + # allocate that much memory, so it won't + hmacDrbgGenerate(ctx, addr v[0], uint v.len * sizeof(V)) + +template generate*[V](ctx: var HmacDrbgContext, v: var seq[V]) = + generate(ctx, v.toOpenArray(0, v.high())) + +func generateBytes*(ctx: var HmacDrbgContext, n: int): seq[byte] = + # https://github.com/nim-lang/Nim/issues/19357 + if n > 0: + result = newSeqUninitialized[byte](n) + ctx.generate(result) + +func generate*(ctx: var HmacDrbgContext, T: type): T {.noinit.} = + ## Create a new instance of `T` filled with random data - `T` must be + ## a simple type + ctx.generate(result) + +func update*[S](ctx: var HmacDrbgContext, seed: openArray[S]) = + ## Update context with additional seed data + static: doAssert supportsCopyMem(S) and sizeof(S) > 0 and S isnot bool + + if seed.len > 0: + # In theory the multiplication can overflow, but practically we can't + # allocate that much memory, so it won't + hmacDrbgUpdate(ctx, unsafeAddr seed[0], uint seed.len * sizeof(S)) + +# Convenience helpers using bearssl naming + +template hmacDrbgGenerate*( + ctx: var HmacDrbgContext, output: var openArray[byte]) = + generate(ctx, output) diff --git a/tests/test_rand.nim b/tests/test_rand.nim new file mode 100644 index 0000000..2ee0afd --- /dev/null +++ b/tests/test_rand.nim @@ -0,0 +1,62 @@ +import + unittest2, + ../bearssl/rand + +{.used.} + +suite "random": + test "simple random ops": + # Some of these tests may end up triggering false fails, but given their + # probability, should be fine + + let rng = HmacDrbgContext.new() + + var v: array[1024, byte] + rng[].generate(v) + + let v2 = rng[].generate(array[1024, byte]) + check: + v != default(array[1024, byte]) # probable + v2 != default(array[1024, byte]) # probable + + for i in 0..<1000: + doAssert cast[int](rng[].generate(bool)) in [0, 1] + + var bools: array[64 * 1024, bool] + rng[].generate(bools) + + check: + true in bools # probable + false in bools # probable + + var + xxx = newSeq[int](1024) + yyy = xxx + rng[].generate(xxx) + check: + xxx != yyy # probable + + test "seed": + for seed in [@[byte 0], @[byte 1], @[byte 1, 1], @[byte 42, 13, 37]]: + var + rng = HmacDrbgContext.init(seed) + rng2 = HmacDrbgContext.init(seed) + + check: + rng.generate(uint64) == rng2.generate(uint64) + + for seed in [@[0], @[1], @[1, 1], @[42, 1337, -5]]: + var + rng = HmacDrbgContext.init(seed) + rng2 = HmacDrbgContext.init(seed) + + check: + rng.generate(uint64) == rng2.generate(uint64) + + test "antiseed": + var + rng = HmacDrbgContext.init([0]) + rng2 = HmacDrbgContext.init([1]) + + check: + rng.generate(array[1024, byte]) != rng2.generate(array[1024, byte])