dalek-cryptography / subtle

Pure-Rust traits and utilities for constant-time cryptographic implementations.
BSD 3-Clause "New" or "Revised" License
249 stars 81 forks source link

The documentation should show an example of typical correct usage #122

Open dpc opened 3 months ago

dpc commented 3 months ago

While it might seem obvious to people familiar with this crate, I am actually confused how to use this crate after just landing on it looking for a constant comparison crate.

What I want to do is:

if password_in_request != password_in_the_config {
   bail!("Wrong password");
}

for usual reasons.

I open https://docs.rs/subtle/ , I can see ConstantTimeEq and it is clear I should use ct_eq. But then ... why is it returning Choice? Am I supposed to call .into() on it? It seems so... ? But why can't ct_eq return bool right away by doing that .into() under the hood? Unclear.

So, it seems to me that the documentation can be improved in two ways:

hackerbirds commented 3 months ago

(Disclaimer: I never contributed to the crate, I merely use it and I want to help)

Why does [Choice] exist, why can't ct_eq just return bool.

When using bool, the compiler can sometimes find optimizations for it and make your operations/branch conditions not constant-time. subtle works by using Choice is a wrapper struct around a fake bool (Choice is just a u8 that can be 1 or 0) that tries to make sure the compiler won't optimize it back into a bool. Choice implements bit operations etc the same way a bool does.

Why can't ct_eq return bool right away by doing that .into() under the hood? Unclear.

You don't always want to convert back into bool directly. You might still need to do extra constant-time operations after calling ct_eq().

For example, say you're checking both a username and a password: (username_in_request.ct_ne(&username_in_the_config)) | (password_in_request.ct_ne(&password_in_the_config)). You're doing two constant-time equality checks (one for the username, one for the password), and they both return Choice, but the OR "|" also needs to be constant time, so you have to do Choice | Choice, which gives you yet another Choice. Only after you're done doing all that, you then convert back that last Choice into a bool.

Am I supposed to call .into() on it?

In your case, I think yes you can call .into() to convert back into bool after you're done doing your constant-time checks.

Assuming your passwords are say byte arrays &[u8], which implement ConstantTimeEq, something like this should work (Warning, I didn't test it):

if bool::from(password_in_request.ct_ne(&password_in_the_config)) {
   bail!("Wrong password");
}

However, after you convert into a bool, you stop being in "constant-time land", so be careful, because you may also need to use Choice elsewhere in your code, in which case converting back to bool can make things not constant-time anymore.


Please add an idiomatic example on a front page of a correct usage. It will take one short paragraph and give an immediate answer to developers that are just looking for a solution.

I think it would be a good idea to have examples and better explanations. Maybe I'll try making a PR for it. OTOH it's easy to use that crate incorrectly, and simply throwing an example that everyone will copy-paste without understanding what the crate does will probably cause problems.

dpc commented 3 months ago

Thanks. BTW. From the top of my head: if password_in_request.ct_ne(&password_in_the_config).into() { works just fine.

hackerbirds commented 2 months ago

Yeah, you can write .into(). I just wrote bool::from as a personal preference because it makes it more explicit that you're using a bool instead of Choice.