diff --git a/pallets/pass/src/lib.rs b/pallets/pass/src/lib.rs index 8ddbc21..ac75a8e 100644 --- a/pallets/pass/src/lib.rs +++ b/pallets/pass/src/lib.rs @@ -47,7 +47,7 @@ pub mod pallet { type WeightInfo: WeightInfo; - type Authenticator: Authenticator; + type Authenticator: Authenticator; type PalletsOrigin: From>; diff --git a/traits/authn/src/lib.rs b/traits/authn/src/lib.rs index 31e2665..dd46b82 100644 --- a/traits/authn/src/lib.rs +++ b/traits/authn/src/lib.rs @@ -1,7 +1,9 @@ use codec::{FullCodec, MaxEncodedLen}; -use frame_support::Parameter; +use frame_support::{traits::Get, Parameter}; use scale_info::TypeInfo; +pub mod util; + // A reasonabily sized secure challenge const CHALLENGE_SIZE: usize = 32; pub type Challenge = [u8; CHALLENGE_SIZE]; @@ -25,51 +27,57 @@ pub trait Challenger { /// Authenticator is used to verify authentication devices that in turn are used to verify users pub trait Authenticator { - const AUTHORITY: AuthorityId; + type Authority: Get; type Challenger: Challenger; type DeviceAttestation: DeviceChallengeResponse>; type Device: UserAuthenticator; - fn verify_device(&self, attestation: &Self::DeviceAttestation) -> Option { - attestation.authority().eq(&Self::AUTHORITY).then_some(())?; + fn verify_device(attestation: Self::DeviceAttestation) -> Option { + attestation + .authority() + .eq(&Self::Authority::get()) + .then_some(())?; let (cx, challenge) = attestation.used_challenge(); Self::Challenger::check_challenge(&cx, &challenge)?; attestation.is_valid().then_some(())?; - Some(self.unpack_device(attestation)) + Some(Self::unpack_device(attestation)) } - /// Extract device information from the verification payload - fn unpack_device(&self, verification: &Self::DeviceAttestation) -> Self::Device; + /// Extract device information from the attestation payload + fn unpack_device(attestation: Self::DeviceAttestation) -> Self::Device; } /// A device capable of verifying a user provided credential pub trait UserAuthenticator: FullCodec + MaxEncodedLen + TypeInfo { - const AUTHORITY: AuthorityId; + type Authority: Get; type Challenger: Challenger; type Credential: UserChallengeResponse>; fn verify_user(&self, credential: &Self::Credential) -> Option<()> { - credential.authority().eq(&Self::AUTHORITY).then_some(())?; + credential + .authority() + .eq(&Self::Authority::get()) + .then_some(())?; let (cx, challenge) = credential.used_challenge(); Self::Challenger::check_challenge(&cx, &challenge)?; credential.is_valid().then_some(()) } - fn device_id(&self) -> DeviceId; + fn device_id(&self) -> &DeviceId; } -pub trait ChallengeResponse: Parameter { +/// A response to a challenge for creating a new authentication device +pub trait DeviceChallengeResponse: Parameter { fn is_valid(&self) -> bool; fn used_challenge(&self) -> (Cx, Challenge); fn authority(&self) -> AuthorityId; -} - -/// A response to a challenge for creating a new authentication device -pub trait DeviceChallengeResponse: ChallengeResponse { - fn device_id(&self) -> DeviceId; + fn device_id(&self) -> &DeviceId; } /// A response to a challenge for identifying a user -pub trait UserChallengeResponse: ChallengeResponse { +pub trait UserChallengeResponse: Parameter { + fn is_valid(&self) -> bool; + fn used_challenge(&self) -> (Cx, Challenge); + fn authority(&self) -> AuthorityId; fn user_id(&self) -> HashedUserId; } diff --git a/traits/authn/src/util.rs b/traits/authn/src/util.rs new file mode 100644 index 0000000..e9006c5 --- /dev/null +++ b/traits/authn/src/util.rs @@ -0,0 +1,178 @@ +use std::marker::PhantomData; + +use codec::{Decode, Encode, FullCodec, MaxEncodedLen}; +use frame_support::traits::Get; +use scale_info::TypeInfo; + +use crate::{ + Authenticator, AuthorityId, Challenger, DeviceChallengeResponse, DeviceId, UserAuthenticator, + UserChallengeResponse, +}; + +type ChallengerOf = ::Challenger; +type CxOf = ::Context; + +/// Convenient auto-implemtator of the Authenticator trait +pub struct Auth(PhantomData<(Dev, Att)>); + +impl Authenticator for Auth +where + Att: DeviceChallengeResponse>>, + Dev: UserAuthenticator + From, +{ + type Authority = ::Authority; + type Challenger = ChallengerOf; + type DeviceAttestation = Att; + type Device = Dev; + + fn unpack_device(attestation: Self::DeviceAttestation) -> Self::Device { + attestation.into() + } +} + +/// Convenient auto-implemtator of the UserAuthenticator trait +#[derive(Encode, Decode, TypeInfo)] +#[scale_info(skip_type_params(A, Ch, Cred))] +struct Dev(T, PhantomData<(A, Ch, Cred)>); + +impl UserAuthenticator for Dev +where + T: AsRef + FullCodec + MaxEncodedLen + TypeInfo, + A: Get, + Ch: Challenger, + Cred: UserChallengeResponse, +{ + type Authority = A; + type Challenger = Ch; + type Credential = Cred; + + fn device_id(&self) -> &DeviceId { + self.0.as_ref() + } +} + +impl MaxEncodedLen for Dev { + fn max_encoded_len() -> usize { + T::max_encoded_len() + } +} + +// TODO implement here +mod pass_key { + use codec::{Decode, Encode}; + use scale_info::TypeInfo; + + use super::{Auth, Dev}; + use crate::{DeviceChallengeResponse, DeviceId, UserChallengeResponse}; + + pub type PassKey = Dev<(), A, (), PassKeyAssertion>; + pub type PassKeyManager = Auth, PassKeyAttestation>; + + #[derive(Clone, Debug, Decode, Encode, TypeInfo, PartialEq, Eq)] + pub struct PassKeyAttestation; + + impl DeviceChallengeResponse for PassKeyAttestation { + fn is_valid(&self) -> bool { + todo!() + } + + fn used_challenge(&self) -> (Cx, crate::Challenge) { + todo!() + } + + fn authority(&self) -> crate::AuthorityId { + todo!() + } + + fn device_id(&self) -> &DeviceId { + todo!() + } + } + + #[derive(Clone, Debug, Encode, Decode, TypeInfo, PartialEq, Eq)] + pub struct PassKeyAssertion; + + impl UserChallengeResponse for PassKeyAssertion { + fn is_valid(&self) -> bool { + todo!() + } + + fn used_challenge(&self) -> (Cx, crate::Challenge) { + todo!() + } + + fn authority(&self) -> crate::AuthorityId { + todo!() + } + + fn user_id(&self) -> crate::HashedUserId { + todo!() + } + } +} + +mod dummy { + use frame_support::{parameter_types, sp_runtime::str_array as s}; + + use crate::{ + AuthorityId, Challenger, DeviceChallengeResponse, DeviceId, HashedUserId, + UserChallengeResponse, + }; + + use super::{Auth, Dev}; + + type DummyAttestation = bool; + type DummyCredential = bool; + type DummyChallenger = u8; + type DummyCx = ::Context; + + parameter_types! { + const DummyAuthority: AuthorityId = s("dummy_authority"); + } + pub const DUMMY_DEV: DeviceId = s("dummy_device"); + pub const DUMMY_USER: HashedUserId = s("dummy_user_hash"); + + impl Challenger for DummyChallenger { + type Context = Self; + fn generate(cx: &Self::Context) -> crate::Challenge { + [*cx; 32] + } + } + + pub type DummyDev = Dev; + pub type Dummy = Auth; + + impl DeviceChallengeResponse for DummyAttestation { + fn device_id(&self) -> &DeviceId { + &DUMMY_DEV + } + + fn is_valid(&self) -> bool { + *self + } + fn used_challenge(&self) -> (DummyCx, crate::Challenge) { + (0, [0; 32]) + } + fn authority(&self) -> crate::AuthorityId { + DummyAuthority::get() + } + } + + impl UserChallengeResponse for DummyCredential { + fn user_id(&self) -> crate::HashedUserId { + DUMMY_USER + } + + fn is_valid(&self) -> bool { + *self + } + + fn used_challenge(&self) -> (DummyCx, crate::Challenge) { + (0, [0; 32]) + } + + fn authority(&self) -> crate::AuthorityId { + DummyAuthority::get() + } + } +}