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 support for P-521 #54
Conversation
This looks great, ty! Will review more carefully shortly, but seems like no surprises here |
Neat! The only thing I'm missing is updating Update: done |
Something to note: I tried to run the MLS test vectors over p521 HPKE, and I'm getting length off-by-one panics. I don't know if it's the test vectors or something else (my wild guess would be something wrong with the hpke::De|Serialize impl that returns the wrong vector) so I'll look into it.
|
Hmm odd. Especially bc there's nothing in P521 that's supposed to be 65 bytes |
P-521 field elements are 66-bytes. If they're smaller than that you need to pad with a leading zero |
So I manually checked the MLS test vectors and they contain a mixture of 65 and 66-byte secret keys;
Oh thanks, didn't know that :) Now the discussion is on whether hpke needs to account for it or if downstream library users need to do it. I'd go for the second one but I expect GH issues to pop up :/ If you want me to implement normalization I don't see an issue in that. |
The spec seems pretty clear that |
I did the necessary on my MLS side (I fully expect some implementations to misbehave as one of the implementations did produce the test vectors...with 65-byte secret keys), so nevermind the issue. I indeed had to prepend null bytes to reach 66-bytes. I honestly don't think rust-hpke should account for misbehaving implementations. It respects the spec as far as it's concerned so doing more is a bit meh (especially the cost of having to copy/clone the secret keys somewhere to be able to prepend null bytes) Also I found this: mlswg/mls-implementations#176 |
@OtaK the NIST/FIPS test vectors for ECDSA are also like that, as it were, with leading zeros removed. I transcoded them into Rust syntax using |
So after looking around, apparently, P521 private keys can be between 0 to 66 bytes because of some implementations using a minimum-byte representation (i.e. go's crypto/ecdsa) in the following distribution:
But only HPKE (and not FIPS 186-4) mandates that the private keys must be 66 bytes long. I honestly don't know if |
Though there's an open issue about whether that 4-bytes is a good idea or if it should tolerate more (or not allow other sizes at all): RustCrypto/traits#1330 |
Hmm interesting. That would mean that if instead of What do you think @rozbb? Maybe it's worth a shot? |
Correct. I also opened a PR to change the minimum key size to 192-bits, which should be better/more consistent than its current heuristic: RustCrypto/traits#1412 |
I agree it'd be somewhat more ergonomic to allow multiple lengths of
Basically, I'm willing to wait until this becomes a thing people need in the wild before making it a feature. And if it is a feature, I'd specialize it to P-521 and call it like Btw this all looks great and I'm ready to merge |
I meant more like indeed localizing this behavior to p521 KEM only and turning let sk = curve_crate::SecretKey::from_bytes(encoded.into())
.map_err(|_| HpkeError::ValidationError)?; to this let sk = curve_crate::SecretKey::from_slice(encoded)
.map_err(|_| HpkeError::ValidationError)?; And basically with the issue I encountered, it showed that it's something probably needed in the wild considering the specifics of p521; This led to me having such code pretty much everywhere I encounter raw p521 keys: fn normalize_secret_key(sk: &[u8]) -> zeroize::Zeroizing<[u8; 66]> {
let mut sk_buf = zeroize::Zeroizing::new([0u8; 66]);
sk_buf[66 - sk.len()..].copy_from_slice(sk);
sk_buf
} |
Ah I see. I had thought that
|
#[cfg(feature = "p384")] | ||
#[test] | ||
fn test_pubkey_serialize_correctness_p384() { | ||
test_pubkey_serialize_correctness::<DhP384>(); | ||
} | ||
|
||
#[cfg(feature = "256")] |
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.
Good catch. Dunno how this got here
I'm gonna merge but we can continue the conversation here. Again, thanks for the PR, this was very easy to read and review! |
No idea. Wouldn't make sense though as keys of p256 or p384 are full 32 or 48 bytes, whereas p521 is 65 bytes and 1 bit. One could argue that you could omit the first byte if it's equal 0x00 like it is 50% of the time and save up on storage space.
They're hidden from docs but they're still exposed because you can't control the visibility of associated types. I make heavy use of the Kem's
I do though : D I have some generic code that looks like this, in the context of a macro, as an example of the most abusive cases on fn hash_len(&self) -> usize {
<<$kdf as hpke::kdf::Kdf>::HashImpl as digest::Digest>::output_size()
}
fn key_len(&self) -> usize {
<<$kem as hpke::Kem>::PrivateKey as hpke::Serializable>::size()
}
fn aead_tag_len(&self) -> usize {
use typenum::Unsigned as _;
<<$aead as hpke::aead::Aead>::AeadImpl as aead::AeadCore>::TagSize::USIZE
}
fn aead_key_len(&self) -> usize {
use typenum::Unsigned as _;
<<$aead as hpke::aead::Aead>::AeadImpl as aead::KeySizeUser>::KeySize::USIZE
}
fn aead_nonce_len(&self) -> usize {
use typenum::Unsigned as _;
<<$aead as hpke::aead::Aead>::AeadImpl as aead::AeadCore>::NonceSize::USIZE
} It's the drawback of trait-centric approaches: you currently have no way to restrict library users from "reverse-engineering" (with heavy quotes) the implementation internals of those traits, unless you use a delegate pattern. |
Hi again!
This PR brings support for
DHKEM(P-521, HKDF-SHA512)
with the help of the newly released p521 0.13 implementation