feat: add passkey-rs

This commit is contained in:
Hatter Jiang
2025-06-28 09:50:50 +08:00
parent 0491407e58
commit ad25ed2453
4 changed files with 1750 additions and 0 deletions

1504
__crypto/passkey-rs-demo/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,12 @@
[package]
name = "passkey-rs-demo"
version = "0.1.0"
edition = "2024"
[dependencies]
passkey = "0.4.0"
url = "2.5.4"
async-trait = "0.1.88"
passkey-client = "0.4.0"
coset = "0.3.8"
tokio = { version = "1.45.1", features = ["full"] }

View File

@@ -0,0 +1,4 @@
Source code: https://github.com/1Password/passkey-rs/blob/main/passkey/examples/usage.rs

View File

@@ -0,0 +1,230 @@
use passkey::{
authenticator::{Authenticator, UserCheck, UserValidationMethod},
client::{Client, WebauthnError},
types::{crypto::sha256, ctap2::*, rand::random_vec, webauthn::*, Bytes, Passkey},
};
use coset::iana;
use passkey_client::DefaultClientData;
use url::Url;
// MyUserValidationMethod is a stub impl of the UserValidationMethod trait, used later.
struct MyUserValidationMethod {}
#[async_trait::async_trait]
impl UserValidationMethod for MyUserValidationMethod {
type PasskeyItem = Passkey;
async fn check_user<'a>(
&self,
_credential: Option<&'a Passkey>,
presence: bool,
verification: bool,
) -> Result<UserCheck, Ctap2Error> {
Ok(UserCheck {
presence,
verification,
})
}
fn is_verification_enabled(&self) -> Option<bool> {
Some(true)
}
fn is_presence_enabled(&self) -> bool {
true
}
}
// Example of how to set up, register and authenticate with a `Client`.
async fn client_setup(
challenge_bytes_from_rp: Bytes,
parameters_from_rp: PublicKeyCredentialParameters,
origin: &Url,
user_entity: PublicKeyCredentialUserEntity,
) -> Result<(CreatedPublicKeyCredential, AuthenticatedPublicKeyCredential), WebauthnError> {
// First create an Authenticator for the Client to use.
let my_aaguid = Aaguid::new_empty();
let user_validation_method = MyUserValidationMethod {};
// Create the CredentialStore for the Authenticator.
// Option<Passkey> is the simplest possible implementation of CredentialStore
let store: Option<Passkey> = None;
let my_authenticator = Authenticator::new(my_aaguid, store, user_validation_method);
// Create the Client
// If you are creating credentials, you need to declare the Client as mut
let mut my_client = Client::new(my_authenticator);
// The following values, provided as parameters to this function would usually be
// retrieved from a Relying Party according to the context of the application.
let request = CredentialCreationOptions {
public_key: PublicKeyCredentialCreationOptions {
rp: PublicKeyCredentialRpEntity {
id: None, // Leaving the ID as None means use the effective domain
name: origin.domain().unwrap().into(),
},
user: user_entity,
challenge: challenge_bytes_from_rp,
pub_key_cred_params: vec![parameters_from_rp],
timeout: None,
exclude_credentials: None,
authenticator_selection: None,
hints: None,
attestation: AttestationConveyancePreference::None,
attestation_formats: None,
extensions: None,
},
};
// Now create the credential.
let my_webauthn_credential = my_client
.register(origin, request, DefaultClientData)
.await?;
// Let's try and authenticate.
// Create a challenge that would usually come from the RP.
let challenge_bytes_from_rp: Bytes = random_vec(32).into();
// Now try and authenticate
let credential_request = CredentialRequestOptions {
public_key: PublicKeyCredentialRequestOptions {
challenge: challenge_bytes_from_rp,
timeout: None,
rp_id: Some(String::from(origin.domain().unwrap())),
allow_credentials: None,
user_verification: UserVerificationRequirement::default(),
hints: None,
attestation: AttestationConveyancePreference::None,
attestation_formats: None,
extensions: None,
},
};
let authenticated_cred = my_client
.authenticate(origin, credential_request, DefaultClientData)
.await?;
Ok((my_webauthn_credential, authenticated_cred))
}
async fn authenticator_setup(
user_entity: PublicKeyCredentialUserEntity,
client_data_hash: Bytes,
algorithms_from_rp: PublicKeyCredentialParameters,
rp_id: String,
) -> Result<get_assertion::Response, StatusCode> {
let store: Option<Passkey> = None;
let user_validation_method = MyUserValidationMethod {};
let my_aaguid = Aaguid::new_empty();
let mut my_authenticator = Authenticator::new(my_aaguid, store, user_validation_method);
let reg_request = make_credential::Request {
client_data_hash: client_data_hash.clone(),
rp: make_credential::PublicKeyCredentialRpEntity {
id: rp_id.clone(),
name: None,
},
user: user_entity,
pub_key_cred_params: vec![algorithms_from_rp],
exclude_list: None,
extensions: None,
options: make_credential::Options::default(),
pin_auth: None,
pin_protocol: None,
};
let credential: make_credential::Response =
my_authenticator.make_credential(reg_request).await?;
ctap2_creation_success(credential);
let auth_request = get_assertion::Request {
rp_id,
client_data_hash,
allow_list: None,
extensions: None,
options: make_credential::Options::default(),
pin_auth: None,
pin_protocol: None,
};
let response = my_authenticator.get_assertion(auth_request).await?;
Ok(response)
}
fn ctap2_creation_success(credential: make_credential::Response) {
println!(
"CTAP2 credential creation succeeded:\n\n{:?}\n\n",
credential
);
}
fn ctap2_auth_success(credential: get_assertion::Response) {
println!(
"CTAP2 credential authentication succeeded:\n\n{:?}\n\n",
credential
);
}
fn ctap2_credential_not_found() {
println!("CTAP2 error: Credential not found.");
}
fn ctap2_other_error(code: StatusCode) {
println!("CTAP2 error: Other Status Code: {:?}", code);
}
#[tokio::main]
async fn main() -> Result<(), WebauthnError> {
let rp_url = Url::parse("https://example.com/").expect("Should Parse");
let user_entity = PublicKeyCredentialUserEntity {
id: random_vec(32).into(),
display_name: "Hatter Jiang".into(),
name: "jpasskey@example.org".into(),
};
// Set up a client, create and authenticate a credential, then report results.
let (created_cred, authed_cred) = client_setup(
random_vec(32).into(), // challenge_bytes_from_rp
PublicKeyCredentialParameters {
ty: PublicKeyCredentialType::PublicKey,
alg: iana::Algorithm::ES256,
},
&rp_url, // origin
user_entity.clone(),
)
.await?;
println!("Webauthn credential created:\n\n{:?}\n\n", created_cred);
println!("Webauthn credential auth'ed:\n\n{:?}\n\n", authed_cred);
// Generate the client_data_hash from the created_cred response
let client_data_hash = sha256(&created_cred.response.client_data_json).to_vec();
// Authenticator Version
let authenticator_result = authenticator_setup(
user_entity,
client_data_hash.into(),
PublicKeyCredentialParameters {
ty: PublicKeyCredentialType::PublicKey,
alg: iana::Algorithm::ES256,
},
rp_url
.domain()
.expect("Our example should unwrap.")
.to_string(), // tld_from_rp
)
.await;
match authenticator_result {
Ok(authresponse) => {
ctap2_auth_success(authresponse);
}
Err(StatusCode::Ctap2(Ctap2Code::Known(Ctap2Error::NoCredentials))) => {
ctap2_credential_not_found()
}
Err(status_code) => ctap2_other_error(status_code),
};
Ok(())
}