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
Fixes to cryptobox API #712
Fixes to cryptobox API #712
Conversation
Recent testing has shown that sometimes, errors are mishandled when using the cryptobox API. The problem is: (1) Mac check failure is returned using a `bool`, but this doesn't have the `#[must_use]` annotation that `core::Result` does. (2) Cryptobox itself was still using the `Error::MacFailed` which was supposed to go away. (3) Cryptobox itself was not using xoloki's CtAead trait, it was still using the variable time thing, and only the return codes were changing. The solution proposed is: (1) Introduce CtDecryptResult type which is a new-type wrapper around `subtle::Choice` which has the must-use annotation. Put this in `mc-crypto-ct-aead` crate and use it. Previously I was worried to use `subtle::Choice` as an API type, but now we have doen that elsewhere and it hasn't caused churn or problems. (2) Make cryptobox return CtDecryptResult instead of bool as it had been doing, and nuke the `MacFailed` error variant which should be gone now. Make cryptobox also use the same version of `aead` crate that `mc-crypto-ct-aead` crate is exporting. (3) Make cryptobox actually use the constant-time aes-gcm. Besides these issues, while updating the fog hint code that uses this, we noticed: (4) The fog hint decryption code which used cryptobox is needlessly complicated, uses an RNG for no real reason, and introduces unnecessary types with special implementations of subtle trait. Proposed change: (4) We have now made it completely determinsitic, which is better for testability, and documented that it doesn't make changes if decryption fails, which is all that is needed. Then the caller can decide what value the output parameter should have in that case. We deleted the unnecessary types and functions.
d6a5362
to
285bcf4
Compare
@@ -94,10 +97,7 @@ impl CryptoBox for RistrettoHkdfBlake2bAes128Gcm { | |||
// AES | |||
let mac_ref = <&GenericArray<u8, AesMacLen>>::from(&tag[RISTRETTO_PUBLIC_LEN..]); | |||
let aead = Aes128Gcm::new(aes_key); | |||
aead.decrypt_in_place_detached(&aes_nonce, &aad, buffer, mac_ref) | |||
.map_err(|_| Error::MacFailed)?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2 bugs here: 1 still using Error::MacFailed, 2 not using the CtAead interface
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this was issue (3)
/// This wraps the value Choice::from(true) when decryption succeeded, and Choice::from(false) otherwise. | ||
#[must_use = "The result of constant time decryption should not be discarded"] | ||
#[derive(Copy, Clone, Debug)] | ||
pub struct CtDecryptResult(pub Choice); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is solution to issue (1) (ignoring the mac check result)
u8, | ||
Diff<EncryptedFogHintSize, <VersionedCryptoBox as CryptoBox<Ristretto>>::FooterSize>, | ||
>; | ||
|
||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] | ||
pub struct Plaintext(PlaintextArray); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this thing did not need to exist, so it is gone (issue 4)
#[inline(never)] | ||
pub fn ct_decrypt( | ||
ingest_server_private_key: &RistrettoPrivate, | ||
ciphertext: &EncryptedFogHint, | ||
output: &mut Self, | ||
) -> Choice { | ||
let mut rng = McRng::default(); | ||
let mut plaintext = Plaintext::default(); | ||
let default_pubkey = RistrettoPublic::from_random(&mut rng); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this default pubkey is removed, we just use the current value of output instead
fn decrypt_in_place_detached( | ||
&self, | ||
key: &KexAlgo::Private, | ||
footer: &GenericArray<u8, Self::FooterSize>, | ||
buffer: &mut [u8], | ||
) -> Result<bool, Error>; | ||
) -> Result<CtDecryptResult, Error>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this (and similar changes) is solution to (2)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good as far as I can tell. Would love if @jcape could also take a look.
I guess I should develop the uprev PR and get passing deploy, will work on that soonish |
confirmed that when upreving in internal repo we see this:
|
&self, | ||
key: &KexAlgo::Private, | ||
cryptogram: &[u8], | ||
) -> Result<(CtDecryptResult, Vec<u8>), Error> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there's a good argument that this should be made to return Zeroizing<Vec<u8>>
, that would mean that all the callers in SDKs etc. dont need to explicitly zeroize this. zeroize is pretty lightweight crate that I think most things already rely on via transaction-core
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this passed deploy, and i decided to punt on zeroize for now because i can't find a good solution for zeroizing generic array. I posted a pull request here:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, have some suggestions.
General question: if the bytes in the fog hint are not actually a valid ristretto point (i.e. decompression fails), do we still need to produce output in constant-time? If so, then the output.public_key
assignment is simply pushing the non-constant-timeness of that (bad) hint into later code.
Co-authored-by: James Cape <jamescape777@gmail.com>
Co-authored-by: James Cape <jamescape777@gmail.com>
the way it works in ingest enclave impl is: |
`mc-transaction-core` should be as portable as possible, but it had a dependency on `mc-crypto-rand` which is not as portable. Getting an RNG is very hard to do portably -- the approach we use in `mc-crypto-rand` is like, use the standard library approach and the getrandom crate, which still requires an OS, or if you have RDRAND instruction, use that, and selecting between these alternatives is what `mc-crypto-rand` does basically. But this will not work on embedded devices that don't have an OS or intel chips. It just happens to work for all our current enclave phone and server targets. But it may pose a portability problem if someone wanted to build `mc-transaction-core` for a more restricted device. Fortunately the only thing that was creating an RNG directly in `mc-transaction-core`, was the code in the `FogHint` which we removed in mobilecoinfoundation#712 So at this point `mc-transaction-core` only needs `mc-crypto-rand` for tests. Moving it to dev-dependency helps make the transaction core more portable.
`mc-transaction-core` should be as portable as possible, but it had a dependency on `mc-crypto-rand` which is not as portable. Getting an RNG is very hard to do portably -- the approach we use in `mc-crypto-rand` is like, use the standard library approach and the getrandom crate, which still requires an OS, or if you have RDRAND instruction, use that, and selecting between these alternatives is what `mc-crypto-rand` does basically. But this will not work on embedded devices that don't have an OS or intel chips. It just happens to work for all our current enclave phone and server targets. But it may pose a portability problem if someone wanted to build `mc-transaction-core` for a more restricted device. Fortunately the only thing that was creating an RNG directly in `mc-transaction-core`, was the code in the `FogHint` which we removed in #712 So at this point `mc-transaction-core` only needs `mc-crypto-rand` for tests. Moving it to dev-dependency helps make the transaction core more portable.
Recent testing has shown that sometimes, errors are mishandled
when using the cryptobox API. The problem is:
(1) Mac check failure is returned using a
bool
, but this doesn'thave the
#[must_use]
annotation thatcore::Result
does.(2) Cryptobox itself was still using the
Error::MacFailed
whichwas supposed to go away.
(3) Cryptobox itself was not using xoloki's CtAead trait, it was
still using the variable time thing, and only the return codes
were changing.
The solution proposed is:
(1) Introduce CtDecryptResult type which is a new-type wrapper around
subtle::Choice
which has the must-use annotation. Put thisin
mc-crypto-ct-aead
crate and use it.Previously I was worried to use
subtle::Choice
as an API type,but now we have doen that elsewhere and it hasn't caused churn
or problems.
(2) Make cryptobox return CtDecryptResult instead of bool as it had
been doing, and nuke the
MacFailed
error variant which shouldbe gone now. Make cryptobox also use the same version of
aead
crate that
mc-crypto-ct-aead
crate is exporting.(3) Make cryptobox actually use the constant-time aes-gcm.
Besides these issues, while updating the fog hint code that uses this,
we noticed:
(4) The fog hint decryption code which used cryptobox is needlessly
complicated, uses an RNG for no real reason, and introduces
unnecessary types with special implementations of subtle trait.
Proposed change:
(4) We have now made it completely determinsitic, which is better
for testability, and documented that it doesn't make changes
if decryption fails, which is all that is needed. Then the
caller can decide what value the output parameter should have
in that case. We deleted the unnecessary types and functions.
We also made the fog hint code zeroize the temporary buffers,
per cryptobox documentation.