Web IDL support 3/N: response JSON serialization#155
Web IDL support 3/N: response JSON serialization#155AlfioEmanueleFresta wants to merge 29 commits intomasterfrom
Conversation
41fbcff to
68c78a6
Compare
|
@msirringhaus — Following up on your review comment on PR #138 regarding I investigated the WebAuthn Level 3 specification and found that
This matches the behavior observed on demo.yubico.com, which always returns So the current implementation in this PR is correct — we always include
Both cases result in the same output per the spec, since This comment was written by GitHub Copilot (Claude) operating on @AlfioEmanueleFresta's instructions. |
68c78a6 to
d3c31bb
Compare
fbb40b2 to
39cca52
Compare
This adds the inverse of JSON request parsing - serializing WebAuthn responses back to JSON format per WebAuthn Level 3 specification. Changes: - Add WebAuthnIDLResponse trait for response-to-JSON conversion - Add RegistrationResponseJSON and AuthenticationResponseJSON models - Implement to_json() for MakeCredentialResponse and Assertion - Refactor requests to store challenge/origin/cross_origin separately - Add client_data_hash() and client_data_json() helper methods - Update all examples and tests to use new request structure - Add unit tests for response serialization
d3c31bb to
d97c80d
Compare
msirringhaus
left a comment
There was a problem hiding this comment.
Looking good to me. Some minor comments and questions inline.
| impl ClientData { | ||
| pub fn hash(&self) -> Vec<u8> { | ||
| /// Returns the canonical JSON representation of the client data. | ||
| pub fn to_json_bytes(&self) -> Vec<u8> { |
There was a problem hiding this comment.
I wonder if it would be more versatile to make this to_json() and return a String. Those who need the bytes can then call into_bytes() on the result.
| hash: Vec::from(challenge), | ||
| challenge: Vec::from(challenge), | ||
| origin: "example.org".to_string(), | ||
| cross_origin: None, |
There was a problem hiding this comment.
Do we have any tests where cross_origin is not None?
| self.client_data().hash() | ||
| } | ||
|
|
||
| pub fn client_data_json(&self) -> Vec<u8> { |
There was a problem hiding this comment.
Same question as above. More so here: The naming client_data_json() would lead me to expect a String.
There was a problem hiding this comment.
I agree; we should also add the comment that it produces canonical JSON so that users don't have to guess whether they can use the bytes directly or not.
| let parsed: serde_json::Value = serde_json::from_str(&json_str).unwrap(); | ||
|
|
||
| // Verify required fields | ||
| assert!(parsed.get("id").is_some()); |
There was a problem hiding this comment.
Do we want to also check the contents of the fields, not just if they are Some()?
|
|
||
| /// JSON output format options. | ||
| #[derive(Debug, Clone, Copy, Default)] | ||
| pub enum JsonFormat { |
There was a problem hiding this comment.
Is it intentional to 'reimplement' this, instead of exposing a serde_json-Value, which the user can then use and format according to whatever serde_json offers?
I don't have a strong preference for either way, just wanting to understand the reasoning better, if there is any.
| request: &Self::Context, | ||
| ) -> Result<Self::InnerModel, ResponseSerializationError> { | ||
| // Get credential ID from attested credential data | ||
| let credential_id_bytes = self |
There was a problem hiding this comment.
Same comment as for the GetAssertion-case regarding crential IDs.
| }, | ||
| authenticator_attachment: None, | ||
| client_extension_results, | ||
| r#type: "public-key".to_string(), |
There was a problem hiding this comment.
I'm ok with this for now, as we do it in lots of places, but somehow my engineering mind would feel more at ease, if we could make this a transparent enum instead of a hardcoded string in a bunch of places, to avoid typos. But this doesn't have to be in this PR.
| let parsed: serde_json::Value = serde_json::from_str(&json_str).unwrap(); | ||
|
|
||
| // Verify required fields | ||
| assert!(parsed.get("id").is_some()); |
There was a problem hiding this comment.
Same as with GetAssertion: Can we check the contents as well for those that are easy/obvious?
iinuwa
left a comment
There was a problem hiding this comment.
Overall, this looks good. I agree with @msirringhaus's comments. Once those are cleaned up, we should merge this and start using it
- Rename 'uv_requirement' to 'user_verification' to match WebAuthn IDL - Add Default derive for UserVerificationRequirement (required by serde) - Update references in get_assertion.rs
|
I believe that I have properly rebased the code here and pushed it to the json-2a branch. Sorry for the trouble :/ |
Summary
This PR is the second in a series (follows #138) and adds JSON serialization for WebAuthn responses - the inverse of the existing JSON request parsing functionality.
Changes
New Response Serialization Infrastructure
WebAuthnIDLResponsetrait - Providesto_json()andto_inner_model()methods for converting responses to JSON formatJsonFormatenum - Supports both minified and prettified JSON outputResponseSerializationError- Error type for serialization failuresNew JSON Response Models (per WebAuthn Level 3 spec)
RegistrationResponseJSON- ForMakeCredentialResponseserializationAuthenticationResponseJSON- ForAssertionserializationAuthenticatorAttestationResponseJSON- Attestation response detailsAuthenticatorAssertionResponseJSON- Assertion response detailsAuthenticationExtensionsClientOutputsJSON- Extension output serializationRequest Structure Refactoring
Refactored
MakeCredentialRequestandGetAssertionRequestto enable client data generation on-the-fly:hash: Vec<u8>withchallenge: Vec<u8>- Store raw challenge instead of pre-computed hashorigin: Stringfield - Store origin explicitlycross_origin: Option<bool>field - Optional cross-origin flagclient_data: ClientDatafield - No longer needed as separate fieldclient_data()- BuildsClientDatainternallyclient_data_hash()- Computes SHA-256 hash on demandclient_data_json()- Returns JSON bytes for response serializationImplementation Details
WebAuthnIDLResponseforMakeCredentialResponsewith full attestation object CBOR serializationWebAuthnIDLResponseforAssertionwith signature and authenticator data handlingcredProps,hmacCreateSecret,hmacGetSecret,largeBlob,prfSerializederives to attestation statement types for CBOR encodingUpdated Files
client_data_hash()Testing
webauthn_json_hid.rsexample to demonstrate JSON response outputExample Usage