-
Notifications
You must be signed in to change notification settings - Fork 119
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
Add support for NIST P-256 curve #185
Conversation
d6d7176
to
30c5166
Compare
…nt) (#51) An initial rough implementation of hybrid transport - formerly known as Cloud Assisted Bluetooth Low Energy, or caBLE. ## Includes * Generates QR code for QR-initiated transactions * Adds WebAuthn example which prints QR code to terminal (`examples/webauthn_cable.rs`) * Adds submodule fork of `snow` (noise protocol framework) with NIST P-256 curve implementation, as required by CTAP spec (see mcginty/snow#185) ## Limitations and follow ups * Unable to discover Android devices (created issue #52) * Does not yet support state-assisted transactions (part of #31) * Clean up (part of #31)
Friendy ping on this @mcginty - I would need this change to be upstreamed so we can begin publishing linux-credentials/libwebauthn to crates.io. TIA! |
@AlfioEmanueleFresta Wow, I'm so sorry I dropped the ball on this! Reviewing now. |
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 the PR! This is looking pretty good, I just have some small comments/questions.
src/constants.rs
Outdated
@@ -4,7 +4,7 @@ pub const TAGLEN: usize = 16; | |||
|
|||
pub const MAXHASHLEN: usize = 64; | |||
pub const MAXBLOCKLEN: usize = 128; | |||
pub const MAXDHLEN: usize = 56; | |||
pub const MAXDHLEN: usize = 65; |
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 we should feature-gate this as well so we don't change any constants in the default state (although really our MAXDHLEN
should be 32 by default here, since we don't support Curve448 as of yet, but that's another fix outside of this PR :)
pub const MAXDHLEN: usize = 65; | |
// P-256 uncompressed SEC-1 encodings are 65 bytes long, larger than the `MAXDHLEN` in the official Noise spec. | |
#[cfg(feature = "p256")] | |
pub const MAXDHLEN: usize = 65; | |
// Curve448 keys are the largest in the official Noise spec. | |
#[cfg(not(feature = "p256"))] | |
pub const MAXDHLEN: usize = 56; |
#[cfg(feature = "p256")] | ||
#[derive(Default)] | ||
struct P256 { | ||
privkey: [u8; 32], |
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.
Any reason not to store privkey
as a p256::SecretKey
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.
I gave this a try again, as I couldn't remember why - it seems it's not trivial:
error[E0515]: cannot return reference to temporary value
--> src/resolvers/default.rs:241:9
|
241 | &self.pubkey.to_sec1_bytes()
| ^---------------------------
| ||
| |temporary value created here
| returns a reference to data owned by the current function
The issue is that the Dh
trait requires exporting slices:
pub trait Dh: Send + Sync {
fn pubkey(&self) -> &[u8];
fn privkey(&self) -> &[u8];
However, p256::SecretKey
and p256::PublicKey
only return owned types (eg. FieldBytes<...>
, or GenericArray<u8, ...>
).
Pre-serializing the keys to their byte representation allows keeping the trait unchanged.
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 also took a quick look and agree this isn't a simple drop-in; the key types available only provide copied-array access to the inner bytes because they are stored as a "generic" field representation.
FWIW I have run into similar issues before with the use of slices for everything in the Dh
implementations. It was when attempting to add support for ECDH directly in the ring provider because those also don't (at all) provide access to a slice representation of private keys.
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.
Got it -- that makes sense to keep it as-is then!
I can address this more holistically in a large rework of this trait later. It doesn't make sense to keep these slices if they're holding us back in the long run.
src/resolvers/default.rs
Outdated
let scalar = | ||
Vec::<u8>::from_hex("58c77a30bb0fa177286346d18f59678ac1b8d3637ee65f1bd88a8f52e49ef189") | ||
.unwrap(); | ||
keypair.set(&scalar); | ||
let public = Vec::<u8>::from_hex( | ||
"042009fddefeed3b342696b11683b423db8ede2ef5cd66af9b7db2772f7deaf3d\ | ||
f1a69a4648d990ae2a4e5928f156f32e15fa08ba4465df8cb17838dc2afb719d2", | ||
) | ||
.unwrap(); | ||
let mut output = [0u8; 32]; | ||
keypair.dh(&public, &mut output).unwrap(); | ||
assert_eq!( | ||
hex::encode(output), | ||
"07505b308650d07e6ead11dd36bbf6f24bf99fc6479649fadd3939faa33ddeb3" | ||
); |
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.
Where did you get these test constants from? It would be good to have a citation to a known-good example to derive trust for this implementation.
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 honestly do not recall, so I've replaced these with the test vectors from RFC 5903, and added some comments to this effect.
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 the PR! This is looking pretty good, I just have some small comments/questions.
As a drive-by comment, which might be helpful context: I asked Trevor at RWC one year what his thoughts were on CTAP Hybrid being specified with a non-standard Noise curve, as the designer of Noise because I was curious, and the answer was that it wasn't a big deal these days and that maybe future versions of Noise would be more curve agnostic anyway. Tl;dr I think this change is worthwhile for sure :) |
@complexspaces thanks for the context! Yeah, generally I'm open to snow supporting "unofficial" general-use Noise modes, as long as the default snow only exposes the officially supported modes and unofficial modifications are feature-gated, just as this PR does. |
Thanks for the review @mcginty! Addressed your comments. @complexspaces that's interesting! FWIW, here are some reasons P-256 might have been chosen for the FIDO specs: fido-alliance/discussions#2 (comment) |
@AlfioEmanueleFresta Yeah it makes a lot of sense why they picked P-256, including the reasons listed in your link. I don't think it was done harmfully to be clear, just out of necessity for what algorithms existing key APIs supported across OSes. |
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 great, thank you very much for making the change and patience in needing to ping me to review it :).
* Adds support for NIST P-256, aka secp256r1, using the p256 crate from RustCrypto. This is a pure-Rust implementation. * As per feedback in previous abandoned PR try to add secp256k1_support #38 (comment), the code is feature gated, as this is a non-standard curve. * Includes PR Update Cargo.toml #181 which updates curve25519-dalek, which unbreaks the build for me.
PR mcginty/snow#185 was merged into snow v0.10.0-alpha.1, adding support for P-256. This is required by the CTAP Hybrid Transport spec. This PR deprecates the snow fork, in favour of the new upstream version containing my changes.
Changes
Motivation
This curve is used for the Noise handshake in CTAP2 Hybrid Transport. This is the protocol used for browsers to access FIDO2 credentials (aka Passkeys) on roaming authenticators connected over the internet, such as phones running Android and iOS.
I'm using snow in my implementation of CTAP2, aimed at bringing Passkeys to the Linux desktop, see xdg-credentials-portal.
Tests
All commands below succeed: