Implement third-party payment extension

This commit is contained in:
Robin Krahl
2024-06-27 12:50:57 +02:00
parent df9f2def54
commit 30e2b909ad
7 changed files with 161 additions and 42 deletions
+2 -1
View File
@@ -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
View File
@@ -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);
});
}
+1
View File
@@ -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
View File
@@ -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) = &parameters.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)]
+2
View File
@@ -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
View File
@@ -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();
}
}
}
+27
View File
@@ -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()),
}
}
}