-
Notifications
You must be signed in to change notification settings - Fork 18
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
Added asymmetric encrypt and decrypt #63
Added asymmetric encrypt and decrypt #63
Conversation
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.
That looks pretty good! A few comments.
/// Validate the contents of the operation against the attributes of the key it targets | ||
/// | ||
/// This method checks that: | ||
/// * the key policy allows encrypting messages |
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.
encrypting
should perhaps be decrypting
in this context
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.
Yep, whoops! I somehow missed that but caught the one at the end...
/// * the key policy allows encrypting messages | ||
/// * the key policy allows the encryption algorithm requested in the operation | ||
/// * the key type is compatible with the requested algorithm | ||
/// * if the algorithm is RsaPkcs1v15Crypt, it has no salt (it is not compatible with salt) |
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.
Just an idea: would it be wrong to include the salt
inside the AsymmetricEncryption
structure (in the RsaOaep
structure)? That way this condition would be checked statically. But that seems to be a big deviation from the PSA API.
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.
I think that's a good idea, even if it deviates from the PSA API a bit. It sounds more 'Rusty' to me 😉.
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.
If we do that, we need to have separate implementations of RsaOaep
for policy and for operations - it doesn't make any sense to have a salt
for a policy. Then, if we do have an operation-specific enum for algorithms, I don't see why we couldn't include algorithm-specific fields inside those structures/variants.
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.
I see, would be good but probably not as part of this PR.
fn clear_message(&mut self) { self.plaintext.zeroize(); | ||
self.salt.zeroize(); } |
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.
hmm is rustfmt
drunk?
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.
Doesn't appear to be drunk, does appear to be AWOL though! Running cargo fmt --all
on parsec-book
doesn't change that file and presumably the CI checks don't catch it either.
89e1c3e
to
0feda29
Compare
pub struct Result { | ||
/// The `plaintext` field contains the decrypted short message. | ||
#[derivative(Debug = "ignore")] | ||
pub plaintext: Secret<Vec<u8>>, |
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.
I'm not convinced a plaintext is worth wrapping in Secret
- we need to be careful to wipe it out of memory when done, but it's not "passwords, cryptographic keys, access tokens or other credentials". I'm mostly concerned because this is a user-facing structure, if it was internal to the service it wouldn't have mattered.
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.
hmmm yeah forcing the consumer to use Secret
might not be a good idea. If plaintext is put into a Vec<u8>
and give ownership of that to the consumer, does that not mean its their responsibility to deal with zeroing the memory?
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.
It could be passed in Zeroizing<Vec<u8>>
which would handle the cleaning up - and it's inherently their data to deal with anyway. I'm in two minds about this, because it does make sense to have more protection over plain text, but Secret
seems overkill (and not really intended for the purpose). Wondering if we need a new structure of our own for this kind of data.
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.
Oh are you talking about cyphertext
in decrypt
? I thought you meant about returning it in encrypt
. I'm in the process of migrating to Zeroizing<Vec<u8>>
in decrypt
.
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.
I think I'm getting confused here - my initial question was whether plaintext
should be wrapped in a Secret
. If we decide to do that, we should keep it consistent and do that both in the encrypt operation and in the decrypt result. Tbh I'd lean towards just using Zeroizing
, but if you or @hug-dev disagree, you can leave it as a Secret
!
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.
I think if someone is going to the effort of encrypting something, it is secret from something or someone (otherwise, why encrypt it?). And, if they are encrypting a password, cryptographic keys or access tokens - plaintext
will then contain something that Secret
was designed to wrap (according to the description of the crate) 😄.
I guess the question is, where do you draw the line? If we are deciding between Zeroize
and Secret
, what are the downsides to Secret
over Zeroize
in this situation?
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.
because this is a user-facing structure
I think it does not have to be in all clients! We were talking about changing the interface in the Basic client to have references everywhere, so that the client is free to use any type they want, but we zeroize internally everything we clone.
Any of the two is good for me, the important part being that both are zeroizing. Secret
is more difficult to use, but easier to audit and less prone to accidental leaking.
Most probably, the simplicity benefits of using Zeroizing
outweigh the security advantages of Secret
, and hence I would lean towards using Zeroizing
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.
Well, there are 2 sides to this: is the "protection" needed? and is it semantically correct? I'd say the first answer is yes because as you say, we want to protect the client data, but the second is no, because the crate and its contents are explicitly aimed at protecting sensitive material (based on its documentation).
It kinda goes both ways in terms of (perceived) protection, though, if you put the plaintext at the same level as the secret key - some people might say "wow, you really take care of my data", others could see it as "hmm, does that mean my keys are only as important as my data to you?".
In any case, as I've said, I'm ok with this being Secret
, we just need to document it thoroughly. Although this might mean we have to switch the hash
in the signing operations to Secret
too, it's kinda at the same level
// Debug derived as NativeResult enum requires it, even though nothing inside this Result is debuggable | ||
// as `plaintext` is sensitive. | ||
#[derive(Derivative)] | ||
#[derivative(Debug)] | ||
pub struct Result { | ||
/// The `plaintext` field contains the decrypted short message. | ||
#[derivative(Debug = "ignore")] | ||
pub plaintext: Secret<Vec<u8>>, | ||
} |
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.
I'm curious what the debug print of this would be. It might be worth implementing Debug
manually to print something like Result { plaintext: [REDACTED] }
if you're keen on keeping plaintext
as Secret
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.
That might still be needed when using Zeroizing
, if we are keeping Debug
on those structures? I'm sure that with the #[derivative(Debug = "ignore")]
it would print something similar as what you wrote, we can check.
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.
impl<Z: Debug + Zeroize> Debug for Zeroizing<Z>
, Zeroizing
implements Debug
. Do we still want to ignore
it? Most other places that Zeroize
is used don't ignore
, it's only data
in psa_import_key
.
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.
Indeed, stuff that's in Zeroizing
is generally not ignored, but maybe it should be. I don't think anyone would want their data (any data) to be printed by mistake somewhere - guess that's another place where having Secret
helps .
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.
I've added it to plaintext
. I'm not sure about ciphertext
or salt
, they don't sound sensitive enough to me to warrant adding ignore
to, but perhaps users might not agree?
I don't think anyone would want their data (any data) to be printed by mistake somewhere
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.
Though if we're going to the effort of Zeroizing
them...
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.
I'm not sure about ciphertext or salt, they don't sound sensitive enough to me
It's not plaintext data, but could be helpful to an attacker and our goal is to make it as hard as possible for them to access it. And as you say, if we wipe it from memory but log it somewhere...
impl ClearProtoMessage for psa_asymmetric_encrypt::Operation { | ||
fn clear_message(&mut self) { | ||
self.plaintext.zeroize(); | ||
self.salt.zeroize(); | ||
} | ||
} | ||
|
||
impl ClearProtoMessage for psa_asymmetric_decrypt::Operation { | ||
fn clear_message(&mut self) { self.salt.zeroize(); } | ||
} | ||
|
||
impl ClearProtoMessage for psa_asymmetric_decrypt::Result { | ||
fn clear_message(&mut self) { self.plaintext.zeroize(); } | ||
} |
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.
For the fields that implement zeroizing on Drop
(i.e. Secret
and Zeroizing
) you don't need to zeroize them here, it'll be done later anyway. So actually, only ciphertext
in psa_asymmetric_encrypt::Result
needs clearing (even if it's encrypted). Actually, I'm wondering if it should use Zeroizing
as well, since it's a buffer
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.
Ok. Is there a reason that these don't need to be zerioized here but other operations do need to zerioize their Zeroizing
data here? E.g. psa_verify_hash::Operation
has hash
and signature
that are both Zeroizing<Vec<u8>>
and they are zeroized 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.
Ah, sorry, yeah, I realised that this is zeroizing the protobuf stuff 😅 carry on! (although I'd still recommend zeroizing ciphertext
too)
0feda29
to
c361f5d
Compare
#[derive(Debug)] | ||
pub struct Result { | ||
/// Decrypted message | ||
pub plaintext: Vec<u8>, |
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.
Let's put the same Zeroizing
here that we use on Operation
of psa_asymmetric_encrypt
! Basically let's just put Zeroizing
on every plaintext
, ciphertext
and salt
.
46618a0
to
5d76e17
Compare
Signed-off-by: Samuel Bailey <[email protected]>
5d76e17
to
bc47296
Compare
To summarise, the changes are |
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.
Thanks for all the changes!
Signed-off-by: Samuel Bailey [email protected]