Skip to content

Comments

Add master DRBG for reseeding#4402

Closed
mspncp wants to merge 4 commits intoopenssl:masterfrom
mspncp:pr-add-master-drbg
Closed

Add master DRBG for reseeding#4402
mspncp wants to merge 4 commits intoopenssl:masterfrom
mspncp:pr-add-master-drbg

Conversation

@mspncp
Copy link
Contributor

@mspncp mspncp commented Sep 22, 2017

Checklist
  • documentation is added or updated (as comments)
  • tests are added or updated

The master DRBG

Up to now, there was an asymmetry between the public and the private global DRBG. Only the former was reseeded by the RAND_add() call. This pull request introduces a master DRBG which is not publicly accessible. So there are now three global DRBGs:

 <os entropy sources>
        |
     <master>          \
      /   \            | three global DRBGs with locking
<public>  <private>    /
    |
  <ssl>

The master DRBG is the only DRBG that pulls entropy from the trusted entropy sources, and that is reseeded by the RAND_add() call. Three of the DRBGs, namely master, public and private DRBG, have a locking mechanism, while the DRBGs chained to the public DRBG are intended to be used by a single thread and have no locking.

Naming

In order to get a consistent naming of the three global DRBGs, the two existing DRBGs had to be renamed. This includes the RAND_DRBG_get0_*() method which are exported from libcrypto to libssl, but that's no problem because these functions were introduced in master only recently and are not part of an official API yet.

Module globals

static RAND_DRBG drbg_master;  /* The master DRBG. Seeded from the os */
static RAND_DRBG drbg_public;  /* The global public DRBG. */
static RAND_DRBG drbg_private; /* The global private-key DRBG. */

Accessors

RAND_DRBG *RAND_DRBG_get0_master(void);
RAND_DRBG *RAND_DRBG_get0_public(void);
RAND_DRBG *RAND_DRBG_get0_private(void);

Seed Propagation

Instead of having RAND_add() reseed all global DRBGs (which would require taking all three locks), we only reseed the master DRBG (because it holds the lock already) and use a simple mechanism to propagate the reseeding to to all chained DRBGs: Every DRBG has a seed count which is incremented whenever the DRBG pulls fresh random input. Any chained DRBG compares in the RAND_DRBG_generate() call its seed count with the one of its parent. If there is a mismatch, it seeds itself automatically.

This propagation works for regular reseedings, too, not only for the ones triggered by RAND_add().

Update: As suggested by @paulidale, seed propagation will not proceed to the DRBGs which are owned by a thread or an SSL context, because this would make RAND_add() a very expensive operation. Only the global public and private DRBGs will be automatically restarted.

Remark: This "seed count" should have been named seed_count, however this name unfortunately collides with the reseed_counter member which actually counts generate requests, not reseeds. However, the name reseed_counter comes from the NIST standard, so I didn't dare to rename it. Hence I called my counter the restart_count. If anybody has a better suggestion for the name, I'm happy to hear about it.

Locking

Currently, only the three global DRBGs have a lock. The other chained (SSL) locks are considered to be owned by a single thread. So all locking code needs to check for the presence of the lock first. The lock is currently taken by the caller and it is not clear, whether all cases are currently handled. This is somewhat unsatisfying.

  • The lock is owned by the RAND_DRBG "class", which should be an opaque structure, in particular the locking should be managed entirely from within the RAND_DRBG "methods". (Sorry for my C++ speak).
  • I doubt that the performance gain of having an rwlock only in some dedicated global DRBGs does not justifies destroying simplicity. So why not have an rwlock in all DRBGs and don't have to check for the presence of the rwlock. (But I'm not an expert w.r.t. the performance of rwlocks.)

Debugging

You can debug into the code paths of RAND_add() using the openssl rand command:

echo 'this is some random input' > /tmp/randfile
./util/shlib_wrap.sh gdb --args ./apps/openssl rand -hex -rand /tmp/randfile  12

(gdb) start
(gdb) b drbg_setup
(gdb) b drbg_add

Copy link
Contributor

Choose a reason for hiding this comment

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

Is the locking needed here? I thought integer reads were considered atomic.

It might require an atomic increment when the count is changed but that's cheaper than locking when it is available.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Then I just leave it as it is: Only the three global DRBGs are created with a lock, the other DRBGs don’t have a lock by default. For completeness sake, I would add a RAND_DRBG_enable_locking() method to the RAND_DRBG API.

I agree with you that letting CRYPTO_THREAD_xxx_lock() accept NULL pointers as no-ops is probably not a good idea. So the call needs to be wrapped by a conditional.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for suggesting the atomic operations. I hope the following question does not sound naive: I was wondering whether any special precaution is necessary in the first place. Even in the unlikely case that due to concurrency issues the if-block

if (drbg->restart_count != drbg->parent->restart_count) {
   …
}

is not entered erroneously, this would only mean that automatic restarting of the chained DRBG would be postponed until the next generate request. And failing twice in a row is extremely unlikely. What do you think about this kind of reasoning?

Copy link
Contributor

Choose a reason for hiding this comment

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

integer reads were considered atomic

In many cases, yes. Certain combinations of processor architecture, memory coherency model in use, and alignment of access can cause integer reads to not be atomic, so when there is a potential for concurrent reads/writes, it's generally best to explicitly use a primitive that guarantees atomicity -- in the common case, such primitives are supposed to decay to "normal" memory accesses.
I guess with the current APIs one would have to CRYPTO_atomic_add() with a zero increment to perform an atomic read, incurring a write as well; we could probably add a CRYPTO_atomic_read() primitive if the overhead was a concern.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I would prefer the CRYPTO_atomic_read(), not for performance reasons but for clarity. Provided that's not too much effort for you to add.

Copy link
Contributor

Choose a reason for hiding this comment

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

CRYPTO_atomic_read sounds like a good idea.

Copy link
Contributor

Choose a reason for hiding this comment

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

If there is a race reading either drbg->restart_count or drbg->parent->restart_count, a reseed will occur now and again the next time the DRBG is used. I.e. not a big concern. If a stale value remains in cache, the reseed will be delayed.

Copy link
Contributor

Choose a reason for hiding this comment

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

See #4414

Copy link
Contributor

Choose a reason for hiding this comment

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

Unnecessary locking again?

Copy link
Contributor

Choose a reason for hiding this comment

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

Certainly the global public and private DRBGs should reseed when the master does. I'm less sure about the levels below these.

There could be hundreds or thousands of DRBGs in the SSL objects and this would make a reseeding an exceedingly expensive operation. I recognise that the child DRBGs will reseed in a lazy manner so the cost is spread over time but it's still a large cost.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I understand your concerns about the reseeding chain reaction. As you suggested, I could enable automatic restarting only for the two global chained DRBGs (private and public). There are three possibilities to selectively enable this auto-restart feature for DRBGs:

  1. Use lock != 0 as an indicator that automatic restart is enabled. => Looks hacky to me.
  2. Add an auto_restart member. => Adds a seldomly used member: Most DRBGs will have it disabled, so there are two unused members, auto_restart and restart_count.
  3. Use restart_count != 0 as indicator that automatic restart is enabled. In order to enable auto restart, one simply sets the counter to 1. An integer overflow check when incrementing can safely be omitted. Before this ever overflows, madness rules ;-).

As usual, the third variant is my favourite choice. :-) What is your opinion?

Copy link
Contributor

@paulidale paulidale Sep 23, 2017

Choose a reason for hiding this comment

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

  1. If we aren't reseeding from a parent, reseed the global public and private DRBGs. It's a bit hacky but could be reworked to "if we're reseeding the global master, flag the other two global RNGs for reseeding".

  2. Have a global variable indicating that all DRBGs should be reseeded rather than just the global ones. It wouldn't need locking since it will essentially be read-only. I can't think of a case where you want some but not all of the non-global DRBGs reseeded. That usually means there are several and I'm just not thinking straight enough.

@paulidale
Copy link
Contributor

Another possibility would be changing the CRYPTO_THREAD_xxx_lock calls to accept a NULL argument, returning 1 ? I'm not sure I like this. Nothing else is coming to mind at the moment.

The cost of using an unlocked lock is implementation and architecture dependent. The worst case would likely be a context switch to the operating system (which is extremely expensive), the best an atomic memory operation (which is a lot more than a normal instruction but not thousands of cycles).

@richsalz
Copy link
Contributor

i think the master forcing a reseed on the two global ones, and using the regular locking, is probaby the good-enough trade-off for this work. I really really like it. I will remove more closely when i get home. I will not be upset if it gets approved and merged before then :)

@richsalz
Copy link
Contributor

BTW, the writeup you put into your PR's is awesome. I think the commit message might want to explicitly point to the PR text for more details.

@mspncp
Copy link
Contributor Author

mspncp commented Sep 22, 2017

Rich, your compliments make me blush again... 😊

@openssl-machine openssl-machine added the hold: cla required The contributor needs to submit a license agreement label Sep 22, 2017
@mspncp
Copy link
Contributor Author

mspncp commented Sep 22, 2017

Ups, the cla-check is failing again?!

@mspncp
Copy link
Contributor Author

mspncp commented Sep 22, 2017

But seriously, I believe that an important aspect of a pull request is to convince other people and make them agree with the change. For a trivial pull request, the patch will be self explanatory, but for a larger work-in-progress, it is probably always a good idea to explain what one is doing.

@dot-asm
Copy link
Contributor

dot-asm commented Sep 22, 2017

I'm not going do remove cla-check, nor do I want others to do so. I'd like to see/verify that next commit will remove it automatically...

@openssl-machine openssl-machine removed the hold: cla required The contributor needs to submit a license agreement label Sep 22, 2017
@mspncp
Copy link
Contributor Author

mspncp commented Sep 22, 2017

Pushed and removed an additional commit to trigger the cla-check

@mspncp
Copy link
Contributor Author

mspncp commented Sep 22, 2017

OS reseeding interval

One question to everybody: With the private and public DRBG now being chained, the number of generate requests (RAND_bytes() resp. RAND_priv_bytes()) between two OS reseeds now effectively jumps from 2^24 to 2^48. Does everybody agree that one should compensate this change by reducing the reseed_interval of the three global DRBG's (and only these) accordingly?

Please vote:

  1. Leave all reseeding intervals at 2^24.
  2. Change the reseeding intervals of the three global DRBG's to 2^12.
  3. Change the reseeding interval of the public and private DRBG to 2^16, and of the master DRBG to 2^8.
  4. Other choice.

@richsalz
Copy link
Contributor

if we make them all 2^8, then reseeding happens every 2^16 private keygens.
I think boring reseeds before every private keygen but I think that's too much
I mean, the "official math" says for the mode we use 2^24, right?

@paulidale
Copy link
Contributor

I suspect something that uses both a time based reseed and a bytes produced reseed. I.e. if time since last reseed > X or number of bytes > Y then reseed. The values for X and Y should perhaps be via a setter/getter interface. 2^48 for Y seems way too much.

For long life private stuff, an explicit reseed is beneficial. I've got an item on my to do list to create some fast low quality non-blocking sources that can be added in this situation to provide some differentiation even if it isn't cryptographically good. The idea being to mutate the state to make things more difficult. I'm thinking things like gettimeofday, clock_gettime, TSC, RDRAND, /dev/urandom for this. A full reseed every time is better from a security viewpoint but isn't practical.

If the caller requests prediction resistance, then a proper reseed from the OS should always be done. I think this is mentioned in NIST's SP 800-90A.

Both these will be new PRs.

@mspncp
Copy link
Contributor Author

mspncp commented Sep 25, 2017

Just in case anybody wonders why this PR is stalled: The reviewers are keeping my busy on #4328 ;-)

@paulidale paulidale mentioned this pull request Sep 25, 2017
2 tasks
@paulidale
Copy link
Contributor

I misunderstood slightly about the 2^48 in my previous response.

NIST's claim is that DRBGs are secure for 2^24 bytes. I don't see a reason to dispute this, so the status quo is likely fine. If we want to reduce this, we can but I'd not go too low due to the overhead involved in reseeding. 2^16 should be fine. 2^8 seems a bit too frequent.

NIST permits chains of DRBGs without crediting all of the output from all the DRBGs to the root of the chain. For example, a chain of five DRBGs would reseed the root every 2^120 bytes output but at any point no DRBG will have produced more than 2^24 which is safe.

I still suggest a time based reseed lifetime as well, but again it doesn't have to be extremely frequent.

@mspncp
Copy link
Contributor Author

mspncp commented Sep 29, 2017

The code does not compile yet and still needs testing. I commited nevertheless to give you a short update about where I'm heading to in this PR.

In addition to automatic reseeding based on generate counts and seed propagation from master to its children, I also implemented an automatic reseeding of the master DRBG based on elapsed time.

See the commit message for more details. (And yes: the contents of the commit message should better go into a manual page...)

@mspncp
Copy link
Contributor Author

mspncp commented Sep 29, 2017

I chose to lower the reseeding interval of the public and private DRBG to 2^16, and of the master DRBG to 2^8. This ensures that randomness from the os entropy source is queried every 2^24 generate requests.

From the NIST point of view this would not be necessary, since a DRBG with access to a trusted entropy source is more or less equivalent to a NDRBG. So it would be sufficient to reseed from the OS every 2^48 requests. However, in practice there is an essential difference: all three DRBGs live in the address space of the user program, whereas the OS RNG lives in the kernel, so its internal state is more secured than the state of the user space DRBGs: If one of the DRBG states gets leaked, the states of the other two might not be secret anymore either.

@mspncp mspncp force-pushed the pr-add-master-drbg branch 2 times, most recently from acb06b9 to de66186 Compare September 29, 2017 07:18
@kroeckx
Copy link
Member

kroeckx commented Oct 18, 2017

Can you rebase this now that the other commits have been merged?

@mspncp
Copy link
Contributor Author

mspncp commented Oct 18, 2017

Of course, this PR will be my next focus. Just give me a day.

@mspncp mspncp force-pushed the pr-add-master-drbg branch from de66186 to a780a8d Compare October 21, 2017 00:02
@mspncp mspncp changed the title WIP: Add master DRBG Add master RAND_DRBG for reseeding Oct 21, 2017
test/drbgtest.c Outdated
Copy link
Member

Choose a reason for hiding this comment

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

It should be time(NULL), there is also an extra space.

(Can be fixed when merging)

test/drbgtest.c Outdated
Copy link
Member

Choose a reason for hiding this comment

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

time(NULL)

test/drbgtest.c Outdated
Copy link
Member

Choose a reason for hiding this comment

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

extra space

@kroeckx
Copy link
Member

kroeckx commented Dec 17, 2017

@paulidale: Can you confirm you're happy with this?

@paulidale
Copy link
Contributor

The recent changes look good.

I didn't go through everything again but can if desirable.

@kroeckx
Copy link
Member

kroeckx commented Dec 17, 2017

I think we're still missing a test for the reseed_time_interval, but maybe we can do that in an other PR.

@kroeckx
Copy link
Member

kroeckx commented Dec 17, 2017

@paulidale: It's really up to you to say if this is good to merge or not. It's up to you to decide what is needed for you to approve it.

@mspncp
Copy link
Contributor Author

mspncp commented Dec 17, 2017

@kroeckx, @paulidale Thank you very much for reviewing my pr.

I would like to add the final fixups myself, because they belong to two different commits. Please let me know if it's ok for you when I autosquash the fixups (without moving the base) while leaving the unsquashed fix at mspncp:pr-add-master-drbg-unsquashed for inspection. If you prefer to do the rebasing yourself, then please do it using git rebase -i --autosquash after I added the fixups.

@kroeckx
Copy link
Member

kroeckx commented Dec 17, 2017

Feel free to rebase/squash as needed

A third shared DRBG is added, the so called master DRBG. Its sole purpose
is to reseed the two other shared DRBGs, the public and the private DRBG.
The randomness for the master DRBG is either pulled from the os entropy
sources, or added by the application using the RAND_add() call.

The master DRBG reseeds itself automatically after a given number of generate
requests, but can also be reseeded using RAND_seed() or RAND_add().
A reseeding of the master DRBG is automatically propagated to the public
and private DRBG. This construction fixes the problem, that up to now
the randomness provided by RAND_add() was added only to the public and
not to the private DRBG.

Signed-off-by: Dr. Matthias St. Pierre <[email protected]>
Every DRBG now supports automatic reseeding not only after a given
number of generate requests, but also after a specified time interval.

Signed-off-by: Dr. Matthias St. Pierre <[email protected]>
…ther

Previously, the RAND_DRBG_uninstantiate() call was not exactly inverse to
RAND_DRBG_instantiate(), because some important member values of the
drbg->ctr member where cleared. Now these values are restored internally.

Signed-off-by: Dr. Matthias St. Pierre <[email protected]>
@mspncp mspncp force-pushed the pr-add-master-drbg branch from 7f35b71 to 70d4dcb Compare December 17, 2017 22:13
@mspncp
Copy link
Contributor Author

mspncp commented Dec 17, 2017

Autosquashed and rebased to the tip of master. Thanks again to everybody for taking the time to review this pr!

@mspncp
Copy link
Contributor Author

mspncp commented Dec 17, 2017

I think we're still missing a test for the reseed_time_interval, but maybe we can do that in an other PR.

Maybe I'll find some time the next days to prepare a pr for the missing test.

@paulidale
Copy link
Contributor

Okay, I've gone through it again and still approve.
I'll merge.

levitte pushed a commit that referenced this pull request Dec 17, 2017
A third shared DRBG is added, the so called master DRBG. Its sole purpose
is to reseed the two other shared DRBGs, the public and the private DRBG.
The randomness for the master DRBG is either pulled from the os entropy
sources, or added by the application using the RAND_add() call.

The master DRBG reseeds itself automatically after a given number of generate
requests, but can also be reseeded using RAND_seed() or RAND_add().
A reseeding of the master DRBG is automatically propagated to the public
and private DRBG. This construction fixes the problem, that up to now
the randomness provided by RAND_add() was added only to the public and
not to the private DRBG.

Signed-off-by: Dr. Matthias St. Pierre <[email protected]>

Reviewed-by: Paul Dale <[email protected]>
Reviewed-by: Kurt Roeckx <[email protected]>
(Merged from #4402)
levitte pushed a commit that referenced this pull request Dec 17, 2017
Every DRBG now supports automatic reseeding not only after a given
number of generate requests, but also after a specified time interval.

Signed-off-by: Dr. Matthias St. Pierre <[email protected]>

Reviewed-by: Paul Dale <[email protected]>
Reviewed-by: Kurt Roeckx <[email protected]>
(Merged from #4402)
levitte pushed a commit that referenced this pull request Dec 17, 2017
Signed-off-by: Dr. Matthias St. Pierre <[email protected]>

Reviewed-by: Paul Dale <[email protected]>
Reviewed-by: Kurt Roeckx <[email protected]>
(Merged from #4402)
levitte pushed a commit that referenced this pull request Dec 17, 2017
…ther

Previously, the RAND_DRBG_uninstantiate() call was not exactly inverse to
RAND_DRBG_instantiate(), because some important member values of the
drbg->ctr member where cleared. Now these values are restored internally.

Signed-off-by: Dr. Matthias St. Pierre <[email protected]>

Reviewed-by: Paul Dale <[email protected]>
Reviewed-by: Kurt Roeckx <[email protected]>
(Merged from #4402)
@paulidale
Copy link
Contributor

Merged but not squashed to a single commit.

@paulidale paulidale closed this Dec 17, 2017
@paulidale
Copy link
Contributor

Thanks for everyone's patience.

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.

7 participants