mirror of
https://github.com/trussed-dev/fido-authenticator.git
synced 2026-06-20 04:16:16 -07:00
Implement third-party payment extension
This commit is contained in:
+2
-1
@@ -15,7 +15,7 @@ name = "usbip"
|
||||
required-features = ["dispatch"]
|
||||
|
||||
[dependencies]
|
||||
ctap-types = { version = "0.2.0", features = ["large-blobs"] }
|
||||
ctap-types = { version = "0.2.0", features = ["large-blobs", "third-party-payment"] }
|
||||
cosey = "0.3"
|
||||
delog = "0.1.0"
|
||||
heapless = "0.7"
|
||||
@@ -49,6 +49,7 @@ log-error = []
|
||||
aes = "0.8.4"
|
||||
cbc = { version = "0.1.2", features = ["alloc"] }
|
||||
ciborium = { version = "0.2.2" }
|
||||
ciborium-io = "0.2.2"
|
||||
cipher = "0.4.4"
|
||||
ctaphid = { version = "0.3.1", default-features = false }
|
||||
delog = { version = "0.1.6", features = ["std-log"] }
|
||||
|
||||
+19
-1
@@ -196,6 +196,13 @@ impl Credential {
|
||||
Self::Stripped(credential) => &credential.key,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn third_party_payment(&self) -> Option<bool> {
|
||||
match self {
|
||||
Self::Full(credential) => credential.data.third_party_payment,
|
||||
Self::Stripped(credential) => credential.third_party_payment,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The main content of a `FullCredential`.
|
||||
@@ -239,6 +246,9 @@ pub struct CredentialData {
|
||||
// extensions (cont. -- we can only append new options due to index-based deserialization)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub large_blob_key: Option<ByteArray<32>>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub third_party_payment: Option<bool>,
|
||||
}
|
||||
|
||||
// TODO: figure out sizes
|
||||
@@ -332,6 +342,7 @@ impl FullCredential {
|
||||
hmac_secret: Option<bool>,
|
||||
cred_protect: Option<CredentialProtectionPolicy>,
|
||||
large_blob_key: Option<ByteArray<32>>,
|
||||
third_party_payment: Option<bool>,
|
||||
nonce: [u8; 12],
|
||||
) -> Self {
|
||||
info!("credential for algorithm {}", algorithm);
|
||||
@@ -347,6 +358,7 @@ impl FullCredential {
|
||||
hmac_secret,
|
||||
cred_protect,
|
||||
large_blob_key,
|
||||
third_party_payment,
|
||||
|
||||
use_short_id: Some(true),
|
||||
};
|
||||
@@ -456,6 +468,8 @@ pub struct StrippedCredential {
|
||||
// TODO: HACK -- remove
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub large_blob_key: Option<ByteArray<32>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub third_party_payment: Option<bool>,
|
||||
}
|
||||
|
||||
impl StrippedCredential {
|
||||
@@ -491,6 +505,7 @@ impl From<&FullCredential> for StrippedCredential {
|
||||
hmac_secret: credential.data.hmac_secret,
|
||||
cred_protect: credential.data.cred_protect,
|
||||
large_blob_key: credential.data.large_blob_key,
|
||||
third_party_payment: credential.data.third_party_payment,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -525,6 +540,7 @@ mod test {
|
||||
cred_protect: None,
|
||||
use_short_id: Some(true),
|
||||
large_blob_key: Some(ByteArray::new([0xff; 32])),
|
||||
third_party_payment: Some(true),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -611,6 +627,7 @@ mod test {
|
||||
cred_protect: None,
|
||||
use_short_id: Some(true),
|
||||
large_blob_key: Some(random_byte_array()),
|
||||
third_party_payment: Some(false),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -693,6 +710,7 @@ mod test {
|
||||
hmac_secret: Some(true),
|
||||
cred_protect: Some(CredentialProtectionPolicy::Required),
|
||||
large_blob_key: Some(ByteArray::new([0xff; 32])),
|
||||
third_party_payment: Some(true),
|
||||
};
|
||||
trussed::virt::with_ram_client("fido", |mut client| {
|
||||
let kek = syscall!(client.generate_chacha8poly1305_key(Location::Internal)).key;
|
||||
@@ -702,7 +720,7 @@ mod test {
|
||||
.try_into()
|
||||
.unwrap();
|
||||
let id = credential.id(&mut client, kek, &rp_id_hash).unwrap();
|
||||
assert_eq!(id.0.len(), 239);
|
||||
assert_eq!(id.0.len(), 241);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -91,6 +91,7 @@ impl<UP: UserPresence, T: TrussedRequirements> Authenticator for crate::Authenti
|
||||
hmac_secret: None,
|
||||
cred_protect: None,
|
||||
large_blob_key: None,
|
||||
third_party_payment: None,
|
||||
};
|
||||
|
||||
// info!("made credential {:?}", &credential);
|
||||
|
||||
+15
-6
@@ -56,6 +56,7 @@ impl<UP: UserPresence, T: TrussedRequirements> Authenticator for crate::Authenti
|
||||
if self.config.supports_large_blobs() {
|
||||
extensions.push(Extension::LargeBlobKey).unwrap();
|
||||
}
|
||||
extensions.push(Extension::ThirdPartyPayment).unwrap();
|
||||
|
||||
let mut pin_protocols = Vec::new();
|
||||
for pin_protocol in self.pin_protocols() {
|
||||
@@ -221,6 +222,7 @@ impl<UP: UserPresence, T: TrussedRequirements> Authenticator for crate::Authenti
|
||||
// let mut cred_protect_requested = CredentialProtectionPolicy::Optional;
|
||||
let mut cred_protect_requested = None;
|
||||
let mut large_blob_key_requested = false;
|
||||
let mut third_party_payment_requested = false;
|
||||
if let Some(extensions) = ¶meters.extensions {
|
||||
hmac_secret_requested = extensions.hmac_secret;
|
||||
|
||||
@@ -243,6 +245,8 @@ impl<UP: UserPresence, T: TrussedRequirements> Authenticator for crate::Authenti
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
third_party_payment_requested = extensions.third_party_payment.unwrap_or_default();
|
||||
}
|
||||
|
||||
// debug_now!("hmac-secret = {:?}, credProtect = {:?}", hmac_secret_requested, cred_protect_requested);
|
||||
@@ -360,6 +364,7 @@ impl<UP: UserPresence, T: TrussedRequirements> Authenticator for crate::Authenti
|
||||
hmac_secret_requested,
|
||||
cred_protect_requested,
|
||||
large_blob_key,
|
||||
third_party_payment_requested.then_some(true),
|
||||
nonce,
|
||||
);
|
||||
|
||||
@@ -1502,9 +1507,11 @@ impl<UP: UserPresence, T: TrussedRequirements> crate::Authenticator<UP, T> {
|
||||
&mut self,
|
||||
get_assertion_state: &state::ActiveGetAssertionData,
|
||||
extensions: &ctap2::get_assertion::ExtensionsInput,
|
||||
_credential: &Credential,
|
||||
credential: &Credential,
|
||||
credential_key: KeyId,
|
||||
) -> Result<Option<ctap2::get_assertion::ExtensionsOutput>> {
|
||||
let mut output = ctap2::get_assertion::ExtensionsOutput::default();
|
||||
|
||||
if let Some(hmac_secret) = &extensions.hmac_secret {
|
||||
let pin_protocol = hmac_secret
|
||||
.pin_protocol
|
||||
@@ -1565,12 +1572,14 @@ impl<UP: UserPresence, T: TrussedRequirements> crate::Authenticator<UP, T> {
|
||||
|
||||
shared_secret.delete(&mut self.trussed);
|
||||
|
||||
let mut extensions = ctap2::get_assertion::ExtensionsOutput::default();
|
||||
extensions.hmac_secret = Some(Bytes::from_slice(&output_enc).unwrap());
|
||||
Ok(Some(extensions))
|
||||
} else {
|
||||
Ok(None)
|
||||
output.hmac_secret = Some(Bytes::from_slice(&output_enc).unwrap());
|
||||
}
|
||||
|
||||
if extensions.third_party_payment.unwrap_or_default() {
|
||||
output.third_party_payment = Some(credential.third_party_payment().unwrap_or_default());
|
||||
}
|
||||
|
||||
Ok(output.is_set().then_some(output))
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
|
||||
@@ -453,6 +453,8 @@ where
|
||||
response.public_key = Some(cose_public_key);
|
||||
response.cred_protect = cred_protect;
|
||||
response.large_blob_key = credential.data.large_blob_key;
|
||||
response.third_party_payment =
|
||||
Some(credential.data.third_party_payment.unwrap_or_default());
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
|
||||
+95
-34
@@ -10,9 +10,9 @@ use hex_literal::hex;
|
||||
|
||||
use virt::{Ctap2, Ctap2Error};
|
||||
use webauthn::{
|
||||
ClientPin, CredentialManagement, CredentialManagementParams, GetAssertion, GetInfo,
|
||||
KeyAgreementKey, MakeCredential, MakeCredentialOptions, PinToken, PubKeyCredDescriptor,
|
||||
PubKeyCredParam, PublicKey, Rp, SharedSecret, User,
|
||||
ClientPin, CredentialManagement, CredentialManagementParams, ExtensionsInput, GetAssertion,
|
||||
GetInfo, KeyAgreementKey, MakeCredential, MakeCredentialOptions, PinToken,
|
||||
PubKeyCredDescriptor, PubKeyCredParam, PublicKey, Rp, SharedSecret, User,
|
||||
};
|
||||
|
||||
#[test]
|
||||
@@ -215,38 +215,85 @@ fn test_make_credential() {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct TestGetAssertion {
|
||||
mc_third_party_payment: Option<bool>,
|
||||
ga_third_party_payment: Option<bool>,
|
||||
}
|
||||
|
||||
impl TestGetAssertion {
|
||||
fn run(&self) {
|
||||
println!("{}", "=".repeat(80));
|
||||
println!("Running test:");
|
||||
println!("{self:#?}");
|
||||
println!();
|
||||
|
||||
let rp_id = "example.com";
|
||||
// TODO: client data
|
||||
let client_data_hash = &[0; 32];
|
||||
|
||||
virt::run_ctap2(|device| {
|
||||
let rp = Rp::new(rp_id);
|
||||
let user = User::new(b"id123")
|
||||
.name("john.doe")
|
||||
.display_name("John Doe");
|
||||
let pub_key_cred_params = vec![PubKeyCredParam::new("public-key", -7)];
|
||||
let mut request = MakeCredential::new(client_data_hash, rp, user, pub_key_cred_params);
|
||||
if let Some(third_party_payment) = self.mc_third_party_payment {
|
||||
request.extensions = Some(ExtensionsInput {
|
||||
third_party_payment: Some(third_party_payment),
|
||||
});
|
||||
}
|
||||
let response = device.exec(request).unwrap();
|
||||
let credential = response.auth_data.credential.unwrap();
|
||||
|
||||
let mut request = GetAssertion::new(rp_id, client_data_hash);
|
||||
request.allow_list = Some(vec![PubKeyCredDescriptor::new(
|
||||
"public-key",
|
||||
credential.id.clone(),
|
||||
)]);
|
||||
if let Some(third_party_payment) = self.ga_third_party_payment {
|
||||
request.extensions = Some(ExtensionsInput {
|
||||
third_party_payment: Some(third_party_payment),
|
||||
});
|
||||
}
|
||||
let response = device.exec(request).unwrap();
|
||||
assert_eq!(response.credential.ty, "public-key");
|
||||
assert_eq!(response.credential.id, credential.id);
|
||||
assert_eq!(response.auth_data.credential, None);
|
||||
credential.verify_assertion(&response.auth_data, client_data_hash, &response.signature);
|
||||
if self.ga_third_party_payment.unwrap_or_default() {
|
||||
let extensions = response.auth_data.extensions.unwrap();
|
||||
assert_eq!(
|
||||
extensions.get("thirdPartyPayment"),
|
||||
Some(&Value::from(
|
||||
self.mc_third_party_payment.unwrap_or_default()
|
||||
))
|
||||
);
|
||||
} else {
|
||||
assert!(response.auth_data.extensions.is_none());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_assertion() {
|
||||
let rp_id = "example.com";
|
||||
// TODO: client data
|
||||
let client_data_hash = &[0; 32];
|
||||
|
||||
virt::run_ctap2(|device| {
|
||||
let rp = Rp::new(rp_id);
|
||||
let user = User::new(b"id123")
|
||||
.name("john.doe")
|
||||
.display_name("John Doe");
|
||||
let pub_key_cred_params = vec![PubKeyCredParam::new("public-key", -7)];
|
||||
let request = MakeCredential::new(client_data_hash, rp, user, pub_key_cred_params);
|
||||
let response = device.exec(request).unwrap();
|
||||
let credential = response.auth_data.credential.unwrap();
|
||||
|
||||
let mut request = GetAssertion::new(rp_id, client_data_hash);
|
||||
request.allow_list = Some(vec![PubKeyCredDescriptor::new(
|
||||
"public-key",
|
||||
credential.id.clone(),
|
||||
)]);
|
||||
let response = device.exec(request).unwrap();
|
||||
assert_eq!(response.credential.ty, "public-key");
|
||||
assert_eq!(response.credential.id, credential.id);
|
||||
assert_eq!(response.auth_data.credential, None);
|
||||
credential.verify_assertion(&response.auth_data, client_data_hash, &response.signature);
|
||||
});
|
||||
for mc_third_party_payment in [Some(false), Some(true), None] {
|
||||
for ga_third_party_payment in [Some(false), Some(true), None] {
|
||||
TestGetAssertion {
|
||||
mc_third_party_payment,
|
||||
ga_third_party_payment,
|
||||
}
|
||||
.run()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct TestListCredentials {
|
||||
pin_token_rp_id: bool,
|
||||
third_party_payment: Option<bool>,
|
||||
}
|
||||
|
||||
impl TestListCredentials {
|
||||
@@ -272,6 +319,11 @@ impl TestListCredentials {
|
||||
request.options = Some(MakeCredentialOptions::default().rk(true));
|
||||
request.pin_auth = Some(pin_auth);
|
||||
request.pin_protocol = Some(2);
|
||||
if let Some(third_party_payment) = self.third_party_payment {
|
||||
request.extensions = Some(ExtensionsInput {
|
||||
third_party_payment: Some(third_party_payment),
|
||||
});
|
||||
}
|
||||
let reply = device.exec(request).unwrap();
|
||||
assert_eq!(
|
||||
reply.auth_data.flags & 0b1,
|
||||
@@ -327,6 +379,10 @@ impl TestListCredentials {
|
||||
let user: BTreeMap<String, Value> = reply.user.unwrap().deserialized().unwrap();
|
||||
assert_eq!(reply.total_credentials, Some(1));
|
||||
assert_eq!(user.get("id").unwrap(), &Value::from(user_id.as_slice()));
|
||||
assert_eq!(
|
||||
reply.third_party_payment,
|
||||
Some(self.third_party_payment.unwrap_or_default())
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -334,11 +390,16 @@ impl TestListCredentials {
|
||||
#[test]
|
||||
fn test_list_credentials() {
|
||||
for pin_token_rp_id in [false, true] {
|
||||
let test = TestListCredentials { pin_token_rp_id };
|
||||
println!("{}", "=".repeat(80));
|
||||
println!("Running test:");
|
||||
println!("{test:#?}");
|
||||
println!();
|
||||
test.run();
|
||||
for third_party_payment in [Some(false), Some(true), None] {
|
||||
let test = TestListCredentials {
|
||||
pin_token_rp_id,
|
||||
third_party_payment,
|
||||
};
|
||||
println!("{}", "=".repeat(80));
|
||||
println!("Running test:");
|
||||
println!("{test:#?}");
|
||||
println!();
|
||||
test.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -301,6 +301,7 @@ pub struct MakeCredential {
|
||||
rp: Rp,
|
||||
user: User,
|
||||
pub_key_cred_params: Vec<PubKeyCredParam>,
|
||||
pub extensions: Option<ExtensionsInput>,
|
||||
pub options: Option<MakeCredentialOptions>,
|
||||
pub pin_auth: Option<[u8; 32]>,
|
||||
pub pin_protocol: Option<u8>,
|
||||
@@ -318,6 +319,7 @@ impl MakeCredential {
|
||||
rp,
|
||||
user,
|
||||
pub_key_cred_params: pub_key_cred_params.into(),
|
||||
extensions: None,
|
||||
options: None,
|
||||
pin_auth: None,
|
||||
pin_protocol: None,
|
||||
@@ -339,6 +341,9 @@ impl From<MakeCredential> for Value {
|
||||
.map(Value::from)
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
if let Some(extensions) = request.extensions {
|
||||
map.push(6, extensions);
|
||||
}
|
||||
if let Some(options) = request.options {
|
||||
map.push(7, options);
|
||||
}
|
||||
@@ -352,6 +357,21 @@ impl From<MakeCredential> for Value {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ExtensionsInput {
|
||||
pub third_party_payment: Option<bool>,
|
||||
}
|
||||
|
||||
impl From<ExtensionsInput> for Value {
|
||||
fn from(extensions: ExtensionsInput) -> Value {
|
||||
let mut map = Map::default();
|
||||
if let Some(third_party_payment) = extensions.third_party_payment {
|
||||
map.push("thirdPartyPayment", third_party_payment);
|
||||
}
|
||||
map.into()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct MakeCredentialOptions {
|
||||
rk: Option<bool>,
|
||||
@@ -476,6 +496,7 @@ pub struct GetAssertion {
|
||||
rp_id: String,
|
||||
client_data_hash: Vec<u8>,
|
||||
pub allow_list: Option<Vec<PubKeyCredDescriptor>>,
|
||||
pub extensions: Option<ExtensionsInput>,
|
||||
}
|
||||
|
||||
impl GetAssertion {
|
||||
@@ -484,6 +505,7 @@ impl GetAssertion {
|
||||
rp_id: rp_id.into(),
|
||||
client_data_hash: client_data_hash.into(),
|
||||
allow_list: None,
|
||||
extensions: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -497,6 +519,9 @@ impl From<GetAssertion> for Value {
|
||||
let values: Vec<_> = allow_list.into_iter().map(Value::from).collect();
|
||||
map.push(0x03, values);
|
||||
}
|
||||
if let Some(extensions) = request.extensions {
|
||||
map.push(0x04, extensions);
|
||||
}
|
||||
map.into()
|
||||
}
|
||||
}
|
||||
@@ -708,6 +733,7 @@ pub struct CredentialManagementReply {
|
||||
pub total_rps: Option<usize>,
|
||||
pub user: Option<Value>,
|
||||
pub total_credentials: Option<usize>,
|
||||
pub third_party_payment: Option<bool>,
|
||||
}
|
||||
|
||||
impl From<Value> for CredentialManagementReply {
|
||||
@@ -719,6 +745,7 @@ impl From<Value> for CredentialManagementReply {
|
||||
total_rps: map.remove(&5).map(|value| value.deserialized().unwrap()),
|
||||
user: map.remove(&6),
|
||||
total_credentials: map.remove(&9).map(|value| value.deserialized().unwrap()),
|
||||
third_party_payment: map.remove(&0x0c).map(|value| value.deserialized().unwrap()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user