Skip to content

Commit

Permalink
change(pass-webauthn): resolving challenge and authority from input data
Browse files Browse the repository at this point in the history
  • Loading branch information
pandres95 committed Oct 11, 2024
1 parent dcac66e commit abb1917
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 12 deletions.
8 changes: 8 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ repository = "https://github.com/virto-network/webauthn"

[workspace.dependencies]
# WebAuthN Verifier
base64 = { package = "simple-base64", version = "0.23.2", default-features = false }
coset = { version = "0.3.0", default-features = false }
futures = { version = "0.3.31", default-features = false, features = [
"executor",
Expand All @@ -21,8 +22,9 @@ passkey-client = { version = "0.3.0", default-features = false, features = [
passkey-types = { version = "0.3.0", default-features = false, features = [
"testable",
] }
sha2 = { version = "0.10.8", default-features = false }
rand = "0.8.5"
sha2 = { version = "0.10.8", default-features = false }
serde_json = { version = "1.0.128", default-features = false }
url = "2.5.2"

# FRAME
Expand Down
4 changes: 4 additions & 0 deletions pass-webauthn/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ repository.workspace = true
version = "0.1.0"

[dependencies]
base64.workspace = true
codec.workspace = true
frame-support.workspace = true
frame-support.optional = true
scale-info.workspace = true
serde_json.workspace = true
traits-authn.workspace = true
verifier.workspace = true

Expand All @@ -35,13 +37,15 @@ runtime-benchmarks = [
"pallet-pass/runtime-benchmarks",
]
std = [
"base64/std",
"codec/std",
"frame-support/std",
"frame-system/std",
"futures/std",
"pallet-balances/std",
"pallet-pass/std",
"scale-info/std",
"serde_json/std",
"sp-io/std",
"traits-authn/std",
"verifier/std",
Expand Down
28 changes: 25 additions & 3 deletions pass-webauthn/src/impls.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use core::marker::PhantomData;

use frame_support::Parameter;
use codec::Decode;
use frame_support::{sp_runtime::traits::TrailingZeroInput, Parameter};
use serde_json::Value;
use traits_authn::{
AuthorityId, Challenge, Challenger, DeviceChallengeResponse, DeviceId, HashedUserId,
UserChallengeResponse,
Expand Down Expand Up @@ -33,7 +35,16 @@ where
}

fn challenge(&self) -> Challenge {
todo!("Extract `challenge`, format into `Challenge` format (that is, [u8; 32])");
|| -> Result<AuthorityId, ()> {
let client_data_json =
serde_json::from_slice::<Value>(&self.client_data).map_err(|_| ())?;

let challenge_str =
base64::decode(client_data_json["challenge"].as_str().ok_or(())?.as_bytes())
.map_err(|_| ())?;
Decode::decode(&mut TrailingZeroInput::new(&challenge_str)).map_err(|_| ())?
}()
.unwrap_or_default()
}
}

Expand All @@ -57,7 +68,18 @@ where
}

fn authority(&self) -> AuthorityId {
todo!("Extract `rp_id`, format into `AuthorityId` format (that is, [u8; 32])");
|| -> Result<AuthorityId, ()> {
let client_data_json =
serde_json::from_slice::<Value>(&self.client_data).map_err(|_| ())?;

let origin = client_data_json["origin"].as_str().ok_or(())?;
let (_, domain) = origin.split_once("//").ok_or(())?;
let (rp_id_subdomain, _) = domain.split_once(".").ok_or(())?;

Decode::decode(&mut TrailingZeroInput::new(rp_id_subdomain.as_bytes()))
.map_err(|_| ())?
}()
.unwrap_or_default()
}

fn device_id(&self) -> &DeviceId {
Expand Down
54 changes: 46 additions & 8 deletions pass-webauthn/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
use frame_support::{
assert_noop, assert_ok, derive_impl, parameter_types,
sp_runtime::{str_array as s, traits::Hash},
traits::{ConstU64, Get},
traits::ConstU64,
PalletId,
};
use frame_system::{pallet_prelude::BlockNumberFor, Config, EnsureRootWithSuccess};
use traits_authn::{util::AuthorityFromPalletId, Challenger, HashedUserId};

use crate::{Attestation, Authenticator};
use crate::{Attestation, Authenticator, Credential};

#[frame_support::runtime]
pub mod runtime {
Expand Down Expand Up @@ -50,7 +50,7 @@ impl pallet_balances::Config for Test {
}

parameter_types! {
pub PassPalletId: PalletId = PalletId(*b"pass/web");
pub PassPalletId: PalletId = PalletId(*b"pass_web");
pub NeverPays: Option<pallet_pass::DepositInformation<Test>> = None;
}

Expand All @@ -76,6 +76,27 @@ impl pallet_pass::Config for Test {
type MaxSessionDuration = ConstU64<10>;
type RegisterOrigin = EnsureRootWithSuccess<Self::AccountId, NeverPays>;
type WeightInfo = ();
#[cfg(feature = "runtime-benchmarks")]
type BenchmarkHelper = Helper;
}

#[cfg(feature = "runtime-benchmarks")]
pub struct Helper;
#[cfg(feature = "runtime-benchmarks")]
impl pallet_pass::BenchmarkHelper<Test> for Helper {
fn register_origin() -> frame_system::pallet_prelude::OriginFor<Test> {
RuntimeOrigin::root()
}

fn device_attestation(_: traits_authn::DeviceId) -> pallet_pass::DeviceAttestationOf<Test, ()> {
let (a, b, c, d) = build_attesttation_fields(&System::block_number());
Attestation::new(a, b, c, d)
}

fn credential(_: HashedUserId) -> pallet_pass::CredentialOf<Test, ()> {
let (a, b, c, d) = build_attesttation_fields(&System::block_number());
Credential::new(a, b, c, d)
}
}

fn new_test_ext() -> sp_io::TestExternalities {
Expand Down Expand Up @@ -108,18 +129,19 @@ fn build_attesttation_fields(ctx: &BlockNumberFor<Test>) -> (Vec<u8>, Vec<u8>, V
let rp_id = String::from_utf8(PassPalletId::get().0.to_vec())
.expect("converting from ascii to utf-8 is guaranteed; qed");
let origin =
Url::parse(&format!("urn://blockchain/{rp_id}")).expect("urn parses as a valid URL");
let key = Passkey::mock(rp_id.clone()).build();
Url::parse(&format!("https://{rp_id}.pallet-pass.int")).expect("urn parses as a valid URL");
let key = Passkey::mock(rp_id).build();
let store = Some(key.clone());

let authenticator = Authenticator::new(aaguid, store, MockUserValidationMethod::new());
let authenticator =
Authenticator::new(aaguid, store, MockUserValidationMethod::verified_user(1));
let mut client = Client::new(authenticator);

let request = CredentialRequestOptions {
public_key: PublicKeyCredentialRequestOptions {
challenge: BlockChallenger::generate(ctx).as_slice().into(),
timeout: None,
rp_id: Some(rp_id),
rp_id: None,
allow_credentials: None,
user_verification: UserVerificationRequirement::default(),
hints: None,
Expand Down Expand Up @@ -151,7 +173,23 @@ fn registration_fails_if_attestation_is_invalid() {
let (authenticator_data, client_data, public_key, signature) =
build_attesttation_fields(&System::block_number());
let signature = [signature, b"Whoops!".to_vec()].concat();
let attestation = Attestation::new(authenticator_data, client_data, public_key, signature);

use passkey_types::ctap2::AuthenticatorData;
let raw_authenticator_data = AuthenticatorData::from_slice(&authenticator_data)
.expect("this conversion works both ways");

println!(
"authenticator_data = {:?}\nclient_data_json = {}",
&raw_authenticator_data,
&String::from_utf8(client_data.clone()).expect("converting json works")
);

let attestation = Attestation::new(
authenticator_data.to_vec(),
client_data,
public_key,
signature,
);

assert_noop!(
Pass::register(RuntimeOrigin::root(), USER, attestation),
Expand Down

0 comments on commit abb1917

Please sign in to comment.