feat: add passkey-rs
This commit is contained in:
1504
__crypto/passkey-rs-demo/Cargo.lock
generated
Normal file
1504
__crypto/passkey-rs-demo/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
12
__crypto/passkey-rs-demo/Cargo.toml
Normal file
12
__crypto/passkey-rs-demo/Cargo.toml
Normal 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"] }
|
||||
4
__crypto/passkey-rs-demo/README.md
Normal file
4
__crypto/passkey-rs-demo/README.md
Normal file
@@ -0,0 +1,4 @@
|
||||
|
||||
Source code: https://github.com/1Password/passkey-rs/blob/main/passkey/examples/usage.rs
|
||||
|
||||
|
||||
230
__crypto/passkey-rs-demo/src/main.rs
Normal file
230
__crypto/passkey-rs-demo/src/main.rs
Normal 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(())
|
||||
}
|
||||
Reference in New Issue
Block a user