Return public key metadata from user verification enrollment (#44877)

## Why

The trusted UI host needs the local credential's public metadata to complete backend registration. `userVerification/enroll` previously returned only `credentialId`.

## What changed

- Return `algorithm` and `publicKey` for newly created or reused credentials. The algorithm is `ecdsaP256Sha256X962`; the public key is unpadded base64url SPKI-DER.
- Keep both fields optional in the protocol for compatibility with older app-servers, while current servers populate both.
- Document caller-owned backend registration and revocation, including checking metadata, signing an enrollment challenge with `userVerification/verify`, matching credential IDs, and preserving the authenticated account throughout registration.

## Testing

Add protocol coverage for absent or null metadata and populated-response round trips. Add an RPC assertion for enrollment metadata and extend the local enrollment test to check metadata when creating and reusing a key.

GitOrigin-RevId: f0726e8c430e27559e1a01ba2ea635993cbeba09
This commit is contained in:
riley-oai
2026-09-11 17:36:50 +00:00
committed by copyberry
parent 2c9e1a5775
commit 7b491281c8
8 changed files with 89 additions and 9 deletions
@@ -109,11 +109,19 @@ pub struct UserVerificationStatusResponse {
#[ts(export_to = "v2/")]
pub struct UserVerificationEnrollParams {}
/// Public metadata for a created or reused local credential. The caller completes
/// backend registration; this response does not establish server enrollment.
/// Older app-servers omit the metadata fields; callers must check both before registration.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct UserVerificationEnrollResponse {
pub credential_id: String,
// TODO: Make both metadata fields required after the experimental/alpha rollout
// guarantees them on the oldest supported app-server version.
pub algorithm: Option<String>,
/// Unpadded base64url of the SubjectPublicKeyInfo DER encoding.
pub public_key: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq, JsonSchema, TS)]
@@ -2,6 +2,35 @@ use super::*;
use pretty_assertions::assert_eq;
use serde_json::json;
#[test]
fn enrollment_response_accepts_older_servers_without_public_metadata() {
for response in [
json!({"credentialId": "credential"}),
json!({"credentialId": "credential", "algorithm": null, "publicKey": null}),
] {
assert_eq!(
serde_json::from_value::<UserVerificationEnrollResponse>(response).unwrap(),
UserVerificationEnrollResponse {
credential_id: "credential".into(),
algorithm: None,
public_key: None,
}
);
}
let response = UserVerificationEnrollResponse {
credential_id: "credential".into(),
algorithm: Some("ecdsaP256Sha256X962".into()),
public_key: Some("public-key".into()),
};
assert_eq!(
serde_json::from_value::<UserVerificationEnrollResponse>(
serde_json::to_value(&response).unwrap()
)
.unwrap(),
response
);
}
#[test]
fn local_readiness_retains_credential_during_biometric_unavailability() {
let status = UserVerificationStatusResponse {