saa_custom/passkey/
passkey.rs1use saa_schema::wasm_serde;
2use saa_common::{AuthError, Binary, CredentialId, String, Verifiable, ensure};
3
4#[cfg(any(feature = "wasm", feature = "native"))]
6use {
7 saa_common::hashes::sha256,
8 sha2::{Digest, Sha256}
9};
10
11#[derive(
13 Clone, Debug, PartialEq,
14 ::saa_schema::serde::Serialize,
15 ::saa_schema::serde::Deserialize
16)]
17#[cfg_attr(feature = "wasm",
19 derive(::saa_schema::schemars::JsonSchema),
20 schemars(crate = "::saa_schema::schemars")
21)]
22#[cfg_attr(feature = "substrate", derive(
23 ::saa_schema::scale::Encode,
24 ::saa_schema::scale::Decode
25))]
26#[cfg_attr(feature = "solana", derive(
27 ::saa_schema::borsh::BorshSerialize,
28 ::saa_schema::borsh::BorshDeserialize
29))]
30#[cfg_attr(all(feature = "std", feature="substrate"), derive(
31 saa_schema::scale_info::TypeInfo)
32)]
33pub struct ClientData {
34 #[serde(rename = "type")]
35 pub ty: String,
36 pub challenge: Binary,
37 pub origin: String,
38 #[serde(rename = "crossOrigin")]
39 pub cross_origin: bool
40}
41
42#[cfg_attr(not(feature = "wasm"), derive(
43 ::saa_schema::serde::Serialize,
44 ::saa_schema::serde::Deserialize,
45))]
46#[wasm_serde]
47pub struct PasskeyExtension {
48 #[serde(rename = "type")]
49 pub ty: String,
50 pub origin: String,
52 pub pubkey: Option<Binary>,
54 pub user_handle: Option<String>,
56}
57
58
59#[wasm_serde]
60pub struct PasskeyPayload {
61 pub authenticator_data: Binary,
63 pub client_data: ClientData,
65 pub pubkey: Option<Binary>,
67}
68
69
70
71
72#[wasm_serde]
73pub struct PasskeyCredential {
74 pub id : String,
76 pub signature : Binary,
78 pub authenticator_data : Binary,
80 pub client_data : ClientData,
82 pub user_handle : Option<String>,
84 pub pubkey : Option<Binary>,
87}
88
89
90#[cfg(any(feature = "wasm", feature = "native"))]
91impl PasskeyCredential {
92 fn message_digest(&self) -> Result<Vec<u8>, AuthError> {
93 let client_data_hash = sha256(saa_common::to_json_binary(&self.client_data)?.as_slice());
94 let mut hasher = Sha256::new();
95 hasher.update(&self.authenticator_data);
96 hasher.update(&client_data_hash);
97 let hash = hasher.finalize();
98 Ok(hash.to_vec())
99 }
100}
101
102impl Verifiable for PasskeyCredential {
103
104 fn id(&self) -> CredentialId {
105 self.id.as_bytes().to_vec()
106 }
107
108 fn validate(&self) -> Result<(), AuthError> {
109 ensure!(self.authenticator_data.len() >= 37, AuthError::generic("Invalid authenticator data"));
110 ensure!(self.signature.len() > 0, AuthError::generic("Empty signature"));
111 ensure!(self.client_data.challenge.len() > 0, AuthError::generic("Empty challenge"));
112 ensure!(self.client_data.ty == "webauthn.get", AuthError::generic("Invalid client data type"));
113 ensure!(self.pubkey.is_some(), AuthError::generic("Missing public key"));
114 Ok(())
115 }
116
117 #[cfg(feature = "native")]
118 fn verify(&self) -> Result<(), AuthError> {
119 let res = saa_common::crypto::secp256r1_verify(
120 &self.message_digest()?,
121 &self.signature,
122 self.pubkey.as_ref().unwrap()
123 )?;
124 ensure!(res, AuthError::generic("Signature verification failed"));
125 Ok(())
126 }
127
128
129 #[cfg(feature = "wasm")]
130 #[allow(unused_variables)]
131 fn verify_cosmwasm(&self,
132 api : &dyn saa_common::cosmwasm::Api) -> Result<(), AuthError> {
133 let res = saa_curves::secp256r1::implementation::secp256r1_verify(
134 &self.message_digest()?,
135 &self.signature,
136 &self.pubkey.as_ref().unwrap_or(&Binary::default())
137 )?;
138 ensure!(res, AuthError::Signature("Signature verification failed".to_string()));
139 Ok(())
140 }
141
142}
143