diff --git a/Cargo.lock b/Cargo.lock index 8cd384b0d..4c9ffa7d2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2152,6 +2152,7 @@ dependencies = [ "async-trait", "bioauth-flow", "futures 0.3.15", + "hex-literal", "humanode-rpc", "humanode-runtime", "parity-scale-codec", @@ -2207,6 +2208,7 @@ dependencies = [ "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", "parity-scale-codec", + "robonode-crypto", "serde", "sp-api", "sp-block-builder", @@ -3934,6 +3936,7 @@ dependencies = [ "parity-scale-codec", "primitives-auth-ticket", "serde", + "serde-nostd", "sp-core", "sp-io", "sp-runtime", @@ -4878,18 +4881,23 @@ dependencies = [ name = "robonode-crypto" version = "0.1.0" dependencies = [ + "ed25519-dalek", "hex-literal", - "sp-application-crypto", + "rand 0.7.3", ] [[package]] name = "robonode-server" version = "0.1.0" dependencies = [ + "async-trait", "facetec-api-client", + "hex", "primitives-auth-ticket", "primitives-liveness-data", + "rand 0.7.3", "reqwest", + "robonode-crypto", "serde", "tokio 1.6.2", "warp", @@ -6019,6 +6027,13 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-nostd" +version = "0.1.0" +dependencies = [ + "serde", +] + [[package]] name = "serde_derive" version = "1.0.126" diff --git a/crates/bioauth-flow/src/flow.rs b/crates/bioauth-flow/src/flow.rs index d9c3ef0c9..733decb3b 100644 --- a/crates/bioauth-flow/src/flow.rs +++ b/crates/bioauth-flow/src/flow.rs @@ -21,9 +21,16 @@ pub trait LivenessDataProvider { } /// Signer provides signatures for the data. -pub trait Signer { - /// Sign the provided data and return the signature. - fn sign>(&self, data: &D) -> Vec; +#[async_trait::async_trait] +pub trait Signer { + /// Signature error. + /// Error may originate from communicating with HSM, or from a thread pool failure, etc. + type Error; + + /// Sign the provided data and return the signature, or an error if the siging fails. + async fn sign<'a, D>(&self, data: D) -> Result + where + D: AsRef<[u8]> + Send + 'a; } /// The necessary components for the bioauth flow. @@ -76,7 +83,8 @@ where impl Flow where - PK: Signer, + PK: Signer>, + >>::Error: Send + Sync + std::error::Error + 'static, LDP: LivenessDataProvider, ::Error: Send + Sync + std::error::Error + 'static, { @@ -89,7 +97,7 @@ where ) -> Result { let opaque_liveness_data = self.obtain_opaque_liveness_data().await?; - let signature = public_key.sign(&opaque_liveness_data); + let signature = public_key.sign(&opaque_liveness_data).await?; let response = self .robonode_client diff --git a/crates/humanode-peer/Cargo.toml b/crates/humanode-peer/Cargo.toml index 62fc4bb47..99d3363ea 100644 --- a/crates/humanode-peer/Cargo.toml +++ b/crates/humanode-peer/Cargo.toml @@ -14,6 +14,7 @@ robonode-client = { version = "0.1", path = "../robonode-client" } async-trait = "0.1" codec = { package = "parity-scale-codec", version = "2.0.0" } futures = "0.3" +hex-literal = "0.3" reqwest = "0.11" sc-basic-authorship = { git = "https://github.com/humanode-network/substrate", branch = "master" } sc-consensus = { git = "https://github.com/humanode-network/substrate", branch = "master" } diff --git a/crates/humanode-peer/src/chain_spec.rs b/crates/humanode-peer/src/chain_spec.rs index 574763ecf..558d50d3b 100644 --- a/crates/humanode-peer/src/chain_spec.rs +++ b/crates/humanode-peer/src/chain_spec.rs @@ -1,8 +1,9 @@ //! Provides the [`ChainSpec`] portion of the config. +use hex_literal::hex; use humanode_runtime::{ - AccountId, BalancesConfig, GenesisConfig, PalletBioauthConfig, Signature, SudoConfig, - SystemConfig, WASM_BINARY, + AccountId, BalancesConfig, GenesisConfig, PalletBioauthConfig, RobonodePublicKeyWrapper, + Signature, SudoConfig, SystemConfig, WASM_BINARY, }; use sc_service::ChainType; use sp_runtime::{ @@ -36,6 +37,11 @@ pub fn local_testnet_config() -> Result { let wasm_binary = WASM_BINARY.ok_or_else(|| "Development wasm binary not available".to_string())?; + let robonode_public_key = RobonodePublicKeyWrapper::from_bytes( + &hex!("d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a")[..], + ) + .map_err(|err| format!("{:?}", err))?; + Ok(ChainSpec::from_genesis( // Name "Local Testnet", @@ -68,6 +74,7 @@ pub fn local_testnet_config() -> Result { }, pallet_bioauth: PalletBioauthConfig { stored_auth_tickets: Vec::new(), + robonode_public_key, }, } }, diff --git a/crates/humanode-runtime/Cargo.toml b/crates/humanode-runtime/Cargo.toml index b72dbbac9..938628297 100644 --- a/crates/humanode-runtime/Cargo.toml +++ b/crates/humanode-runtime/Cargo.toml @@ -10,6 +10,7 @@ substrate-wasm-builder = { git = "https://github.com/humanode-network/substrate" [dependencies] pallet-bioauth = { version = "0.1", path = "../pallet-bioauth", default-features = false } +robonode-crypto = { version = "0.1", path = "../robonode-crypto", default-features = false } codec = { package = "parity-scale-codec", version = "2", default-features = false, features = ["derive"] } frame-benchmarking = { default-features = false, optional = true, git = "https://github.com/humanode-network/substrate", branch = "master" } @@ -60,6 +61,7 @@ std = [ "pallet-sudo/std", "pallet-transaction-payment/std", "pallet-transaction-payment-rpc-runtime-api/std", + "robonode-crypto/std", "sp-api/std", "sp-block-builder/std", "sp-core/std", diff --git a/crates/humanode-runtime/src/lib.rs b/crates/humanode-runtime/src/lib.rs index 1384f4e53..8e2bb5ddf 100644 --- a/crates/humanode-runtime/src/lib.rs +++ b/crates/humanode-runtime/src/lib.rs @@ -11,6 +11,8 @@ #[cfg(feature = "std")] include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); +#[cfg(feature = "std")] +use serde::{Deserialize, Serialize}; use sp_api::impl_runtime_apis; use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; use sp_runtime::traits::{AccountIdLookup, BlakeTwo256, Block as BlockT, IdentifyAccount, Verify}; @@ -25,7 +27,7 @@ use sp_version::NativeVersion; use sp_version::RuntimeVersion; // A few exports that help ease life for downstream crates. -use codec::Encode; +use codec::{Decode, Encode}; pub use frame_support::{ construct_runtime, parameter_types, traits::{KeyOwnerProofSystem, Randomness}, @@ -181,19 +183,56 @@ impl frame_system::Config for Runtime { type OnSetCode = (); } -#[derive(Encode)] -pub struct RobonodeVerifier; +/// The wrapper for the robonode public key, that enables ssotring it in the state. +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Encode, Decode)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct RobonodePublicKeyWrapper([u8; 32]); + +impl RobonodePublicKeyWrapper { + pub fn from_bytes( + bytes: &[u8], + ) -> Result { + let actual_key = robonode_crypto::PublicKey::from_bytes(bytes)?; + Ok(Self(actual_key.to_bytes())) + } +} + +/// The error that can occur during robonode signature validation. +pub enum RobonodePublicKeyWrapperError { + UnableToParseKey, + UnableToParseSignature, + UnableToValidateSignature(robonode_crypto::ed25519_dalek::ed25519::Error), +} + +impl pallet_bioauth::Verifier> for RobonodePublicKeyWrapper { + type Error = RobonodePublicKeyWrapperError; + + fn verify<'a, D>(&self, data: D, signature: Vec) -> Result + where + D: AsRef<[u8]> + Send + 'a, + { + use core::convert::TryInto; + use robonode_crypto::Verifier; + + let actual_key = robonode_crypto::PublicKey::from_bytes(&self.0) + .map_err(|_| RobonodePublicKeyWrapperError::UnableToParseKey)?; + + let signature: robonode_crypto::Signature = signature + .as_slice() + .try_into() + .map_err(|_| RobonodePublicKeyWrapperError::UnableToParseSignature)?; + + actual_key + .verify(data.as_ref(), &signature) + .map_err(RobonodePublicKeyWrapperError::UnableToValidateSignature)?; -impl pallet_bioauth::Verifier for RobonodeVerifier { - fn verify, S: AsRef<[u8]>>(&self, _data: &D, _signature: &S) -> bool { - todo!(); + Ok(true) } } parameter_types! { pub const ExistentialDeposit: u128 = 500; pub const MaxLocks: u32 = 50; - pub const RobonodeSignatureVerifierInstance: RobonodeVerifier = RobonodeVerifier; } impl pallet_balances::Config for Runtime { @@ -226,8 +265,7 @@ impl pallet_sudo::Config for Runtime { impl pallet_bioauth::Config for Runtime { type Event = Event; - type RobonodeSignatureVerifier = RobonodeVerifier; - type RobonodeSignatureVerifierInstance = RobonodeSignatureVerifierInstance; + type RobonodePublicKey = RobonodePublicKeyWrapper; } // Create the runtime by composing the FRAME pallets that were previously @@ -243,7 +281,7 @@ construct_runtime!( Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, TransactionPayment: pallet_transaction_payment::{Pallet, Storage}, Sudo: pallet_sudo::{Pallet, Call, Config, Storage, Event}, - PalletBioauth: pallet_bioauth::{Pallet, Config, Call, Storage, Event}, + PalletBioauth: pallet_bioauth::{Pallet, Config, Call, Storage, Event}, } ); diff --git a/crates/pallet-bioauth/Cargo.toml b/crates/pallet-bioauth/Cargo.toml index 4de08831e..2b78b93c8 100644 --- a/crates/pallet-bioauth/Cargo.toml +++ b/crates/pallet-bioauth/Cargo.toml @@ -7,6 +7,7 @@ publish = false [dependencies] primitives-auth-ticket = { version = "0.1", path = "../primitives-auth-ticket", default-features = false } +serde-nostd = { version = "0.1", path = "../serde-nostd", default-features = false } codec = { package = "parity-scale-codec", version = "2", default-features = false, features = ["derive"] } frame-benchmarking = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "master", optional = true } @@ -25,6 +26,7 @@ default = ["std"] std = [ "serde", "primitives-auth-ticket/std", + "serde-nostd/std", "codec/std", "frame-support/std", "frame-system/std", diff --git a/crates/pallet-bioauth/src/lib.rs b/crates/pallet-bioauth/src/lib.rs index 20cac7502..1218b197d 100644 --- a/crates/pallet-bioauth/src/lib.rs +++ b/crates/pallet-bioauth/src/lib.rs @@ -60,10 +60,17 @@ impl From for StoredAuthTicket { /// Verifier provides the verification of the data accompanied with the /// signature or proof data. -pub trait Verifier { +/// A non-async (blocking) variant, for use at runtime. +pub trait Verifier { + /// Verification error. + /// Error may originate from communicating with HSM, or from a thread pool failure, etc. + type Error; + /// Verify that provided data is indeed correctly signed with the provided /// signature. - fn verify, S: AsRef<[u8]>>(&self, data: &D, signature: &S) -> bool; + fn verify<'a, D>(&self, data: D, signature: S) -> Result + where + D: AsRef<[u8]> + Send + 'a; } // We have to temporarily allow some clippy lints. Later on we'll send patches to substrate to @@ -89,10 +96,8 @@ pub mod pallet { /// Because this pallet emits events, it depends on the runtime's definition of an event. type Event: From> + IsType<::Event>; - type RobonodeSignatureVerifier: Verifier + Encode; - - #[pallet::constant] - type RobonodeSignatureVerifierInstance: Get; + /// The public key of the robonode. + type RobonodePublicKey: Verifier> + codec::FullCodec + Default + serde_nostd::SerDe; } #[pallet::pallet] @@ -104,26 +109,34 @@ pub mod pallet { #[pallet::getter(fn stored_auth_tickets)] pub type StoredAuthTickets = StorageValue<_, Vec, ValueQuery>; + /// The public key of the robonode. + #[pallet::storage] + #[pallet::getter(fn robonode_public_key)] + pub type RobonodePublicKey = StorageValue<_, ::RobonodePublicKey>; + #[pallet::genesis_config] - pub struct GenesisConfig { + pub struct GenesisConfig { pub stored_auth_tickets: Vec, + pub robonode_public_key: ::RobonodePublicKey, } // The default value for the genesis config type. #[cfg(feature = "std")] - impl Default for GenesisConfig { + impl Default for GenesisConfig { fn default() -> Self { Self { stored_auth_tickets: Default::default(), + robonode_public_key: Default::default(), } } } // The build of genesis for the pallet. #[pallet::genesis_build] - impl GenesisBuild for GenesisConfig { + impl GenesisBuild for GenesisConfig { fn build(&self) { >::put(&self.stored_auth_tickets); + >::put(&self.robonode_public_key); } } @@ -140,7 +153,12 @@ pub mod pallet { /// Possible error conditions during `authenticate` call processing. #[pallet::error] pub enum Error { - /// The signature for the auth ticket did not validate. + /// The robonode public key is not at the chain state. + RobonodePublicKeyIsAbsent, + /// We were unable to validate the signature, i.e. it is uknclear whether it is valid or + /// not. + UnableToValidateAuthTicketSignature, + /// The signature for the auth ticket is invalid. AuthTicketSignatureInvalid, /// Unable to parse the auth ticket. UnableToParseAuthTicket, @@ -218,8 +236,12 @@ pub mod pallet { pub fn extract_auth_ticket_checked( req: Authenticate, ) -> Result> { - let robonode_public_key = T::RobonodeSignatureVerifierInstance::get(); - if !robonode_public_key.verify(&req.ticket, &req.ticket_signature) { + let robonode_public_key = + RobonodePublicKey::::get().ok_or(Error::::RobonodePublicKeyIsAbsent)?; + let signature_valid = robonode_public_key + .verify(&req.ticket, req.ticket_signature.clone()) + .map_err(|_| Error::::UnableToValidateAuthTicketSignature)?; + if !signature_valid { return Err(Error::::AuthTicketSignatureInvalid); } diff --git a/crates/pallet-bioauth/src/mock.rs b/crates/pallet-bioauth/src/mock.rs index 405c4a16a..edd0688f2 100644 --- a/crates/pallet-bioauth/src/mock.rs +++ b/crates/pallet-bioauth/src/mock.rs @@ -1,8 +1,10 @@ use crate as pallet_bioauth; -use codec::Encode; -use frame_support::parameter_types; +use codec::{Decode, Encode}; +use frame_support::{parameter_types, traits::GenesisBuild}; use frame_system as system; -use sp_core::H256; +#[cfg(feature = "std")] +use serde::{Deserialize, Serialize}; +use sp_core::{crypto::Infallible, H256}; use sp_runtime::{ testing::Header, traits::{BlakeTwo256, IdentityLookup}, @@ -23,19 +25,24 @@ frame_support::construct_runtime!( } ); -#[derive(PartialEq, Eq, PartialOrd, Ord, Default, Clone, Encode, Hash, Debug)] +#[derive(PartialEq, Eq, PartialOrd, Ord, Default, Clone, Encode, Decode, Hash, Debug)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub struct MockVerifier; -impl super::Verifier for MockVerifier { - fn verify, S: AsRef<[u8]>>(&self, _data: &D, signature: &S) -> bool { - signature.as_ref().starts_with(b"should_be_valid") +impl super::Verifier> for MockVerifier { + type Error = Infallible; + + fn verify<'a, D>(&self, _data: D, signature: Vec) -> Result + where + D: AsRef<[u8]> + Send + 'a, + { + Ok(signature.starts_with(b"should_be_valid")) } } parameter_types! { pub const BlockHashCount: u64 = 250; pub const SS58Prefix: u8 = 42; - pub const RobonodeSignatureVerifierInstance: MockVerifier = MockVerifier; } impl system::Config for Test { @@ -66,14 +73,17 @@ impl system::Config for Test { impl pallet_bioauth::Config for Test { type Event = Event; - type RobonodeSignatureVerifier = MockVerifier; - type RobonodeSignatureVerifierInstance = RobonodeSignatureVerifierInstance; + type RobonodePublicKey = MockVerifier; } // Build genesis storage according to the mock runtime. pub fn new_test_ext() -> sp_io::TestExternalities { - system::GenesisConfig::default() + let mut storage = system::GenesisConfig::default() .build_storage::() - .unwrap() - .into() + .unwrap(); + + let config = pallet_bioauth::GenesisConfig::::default(); + config.assimilate_storage(&mut storage).unwrap(); + + storage.into() } diff --git a/crates/robonode-crypto/Cargo.toml b/crates/robonode-crypto/Cargo.toml index 975102f7b..4ab4f9315 100644 --- a/crates/robonode-crypto/Cargo.toml +++ b/crates/robonode-crypto/Cargo.toml @@ -6,13 +6,12 @@ authors = ["Humanode Team "] publish = false [dependencies] -sp-application-crypto = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "master" } +ed25519-dalek = { version = "1", default-features = false, features = ["rand", "u64_backend"] } [dev-dependencies] hex-literal = "0.3.1" +rand = "0.7" [features] default = ["std"] -std = [ - "sp-application-crypto/std", -] +std = ["ed25519-dalek/std"] diff --git a/crates/robonode-crypto/src/lib.rs b/crates/robonode-crypto/src/lib.rs index 07ed5d96b..559161ef3 100644 --- a/crates/robonode-crypto/src/lib.rs +++ b/crates/robonode-crypto/src/lib.rs @@ -7,57 +7,57 @@ )] #![cfg_attr(not(feature = "std"), no_std)] -/// Edwards 25519 curve cryptography. -pub mod ed25519 { +pub use ed25519_dalek::{self, Signer, Verifier}; - /// An Robonode keypair using Ed25519 as its crypto. - pub type RobonodePair = sp_application_crypto::ed25519::Pair; +/// Robonode keypair. +pub type Keypair = ed25519_dalek::Keypair; - /// An Robonode signature using Ed25519 as its crypto. - pub type RobonodeSignature = sp_application_crypto::ed25519::Signature; +/// Robonode signature. +pub type Signature = ed25519_dalek::Signature; - /// An Robonode identifier using Ed25519 as its crypto. - pub type RobonodePublicKey = sp_application_crypto::ed25519::Public; -} +/// Robonode public key. +pub type PublicKey = ed25519_dalek::PublicKey; + +/// Robonode secret key. +pub type SecretKey = ed25519_dalek::SecretKey; #[cfg(test)] mod tests { - use crate::ed25519::{RobonodePair, RobonodePublicKey, RobonodeSignature}; + use super::*; use hex_literal::hex; - use sp_application_crypto::Pair; + use rand::rngs::OsRng; + use std::convert::TryInto; + + // Test vectors. + // See: https://ed25519.cr.yp.to/python/sign.py + // https://ed25519.cr.yp.to/python/sign.input + const SK: [u8; 64] = hex!("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a"); + const PK: [u8; 32] = hex!("d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a"); + const M: [u8; 0] = hex!(""); + const SM: [u8; 64] = hex!("e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b"); #[test] - fn generated_pair_should_work() { - let pair = RobonodePair::generate(); - let public = pair.0.public(); - let message = b"Something important"; - let signature = pair.0.sign(&message[..]); - assert!(RobonodePair::verify(&signature, &message[..], &public)); - assert!(!RobonodePair::verify( - &signature, - b"Something else", - &public - )); + fn test_vectors() { + let pair = Keypair::from_bytes(&SK[..]).unwrap(); + let public = PublicKey::from_bytes(&PK[..]).unwrap(); + assert_eq!(pair.public, public); + + let signature: Signature = SM.try_into().unwrap(); + + println!("{:#02X?}", pair.sign(&M[..]).to_bytes()); + assert!(pair.sign(&M[..]) == signature); + assert!(public.verify(&M[..], &signature).is_ok()); } #[test] - fn test_vector_by_string_should_work() { - let pair = RobonodePair::from_string( - "0x9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60", - None, - ) - .unwrap(); - let public = pair.public(); - assert_eq!( - public, - RobonodePublicKey::from_raw(hex!( - "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a" - )) - ); - let message = b""; - let signature = hex!("e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b"); - let signature = RobonodeSignature::from_raw(signature); - assert!(pair.sign(&message[..]) == signature); - assert!(RobonodePair::verify(&signature, &message[..], &public)); + fn generated_pair() { + let mut csprng = OsRng {}; + let pair = Keypair::generate(&mut csprng); + + let message = b"Something important"; + let signature = pair.sign(&message[..]); + + assert!(pair.verify(&message[..], &signature).is_ok()); + assert!(pair.verify(b"Something else", &signature).is_err()); } } diff --git a/crates/robonode-server/Cargo.toml b/crates/robonode-server/Cargo.toml index 4f6ec7c2d..1e407451a 100644 --- a/crates/robonode-server/Cargo.toml +++ b/crates/robonode-server/Cargo.toml @@ -9,7 +9,11 @@ publish = false facetec-api-client = { version = "0.1", path = "../facetec-api-client" } primitives-auth-ticket = { version = "0.1", path = "../primitives-auth-ticket" } primitives-liveness-data = { version = "0.1", path = "../primitives-liveness-data" } +robonode-crypto = { version = "0.1", path = "../robonode-crypto" } +async-trait = "0.1" +hex = "0.4" +rand = "0.7" reqwest = "0.11" serde = { version = "1", features = ["derive"] } tokio = { version = "1", features = ["full"] } diff --git a/crates/robonode-server/src/bin/keygen.rs b/crates/robonode-server/src/bin/keygen.rs new file mode 100644 index 000000000..0e438767b --- /dev/null +++ b/crates/robonode-server/src/bin/keygen.rs @@ -0,0 +1,7 @@ +use rand::rngs::OsRng; + +fn main() { + let mut csprng = OsRng {}; + let keypair = robonode_crypto::Keypair::generate(&mut csprng); + println!("{}", hex::encode(keypair.to_bytes())); +} diff --git a/crates/robonode-server/src/http/filters.rs b/crates/robonode-server/src/http/filters.rs index 7549ee26e..5b7dc7a75 100644 --- a/crates/robonode-server/src/http/filters.rs +++ b/crates/robonode-server/src/http/filters.rs @@ -34,8 +34,8 @@ pub fn root( logic: Arc>, ) -> impl Filter + Clone where - S: Signer + Send + 'static, - PK: Send + for<'a> TryFrom<&'a str> + Verifier + Into>, + S: Signer> + Send + Sync + 'static, + PK: Send + Sync + for<'a> TryFrom<&'a str> + Verifier> + Into>, { enroll(Arc::clone(&logic)) .or(authenticate(Arc::clone(&logic))) @@ -48,7 +48,7 @@ fn enroll( logic: Arc>, ) -> impl Filter + Clone where - S: Signer + Send + 'static, + S: Signer> + Send + 'static, PK: Send + for<'a> TryFrom<&'a str>, { warp::path!("enroll") @@ -63,8 +63,8 @@ fn authenticate( logic: Arc>, ) -> impl Filter + Clone where - S: Signer + Send + 'static, - PK: Send + for<'a> TryFrom<&'a str> + Verifier + Into>, + S: Signer> + Send + Sync + 'static, + PK: Send + Sync + for<'a> TryFrom<&'a str> + Verifier> + Into>, { warp::path!("authenticate") .and(warp::post()) @@ -78,8 +78,8 @@ fn get_facetec_session_token( logic: Arc>, ) -> impl Filter + Clone where - S: Signer + Send + 'static, - PK: Send + for<'a> TryFrom<&'a str> + Verifier + Into>, + S: Signer> + Send + 'static, + PK: Send + for<'a> TryFrom<&'a str>, { warp::path!("facetec-session-token") .and(warp::get()) @@ -92,8 +92,8 @@ fn get_facetec_device_sdk_params( logic: Arc>, ) -> impl Filter + Clone where - S: Signer + Send + 'static, - PK: Send + for<'a> TryFrom<&'a str> + Verifier + Into>, + S: Signer> + Send + 'static, + PK: Send + for<'a> TryFrom<&'a str>, { warp::path!("facetec-device-sdk-params") .and(warp::get()) diff --git a/crates/robonode-server/src/http/handlers.rs b/crates/robonode-server/src/http/handlers.rs index 8576e55c5..68d99ec67 100644 --- a/crates/robonode-server/src/http/handlers.rs +++ b/crates/robonode-server/src/http/handlers.rs @@ -13,7 +13,7 @@ pub async fn enroll( input: EnrollRequest, ) -> Result where - S: Signer + Send + 'static, + S: Signer> + Send + 'static, PK: Send + for<'a> TryFrom<&'a str>, { match logic.enroll(input).await { @@ -28,8 +28,8 @@ pub async fn authenticate( input: AuthenticateRequest, ) -> Result where - S: Signer + Send + 'static, - PK: Send + for<'a> TryFrom<&'a str> + Verifier + Into>, + S: Signer> + Send + 'static, + PK: Send + Sync + for<'a> TryFrom<&'a str> + Verifier> + Into>, { match logic.authenticate(input).await { Ok(res) => { @@ -44,8 +44,8 @@ pub async fn get_facetec_session_token( logic: Arc>, ) -> Result where - S: Signer + Send + 'static, - PK: Send + for<'a> TryFrom<&'a str> + Verifier + Into>, + S: Signer> + Send + 'static, + PK: Send + for<'a> TryFrom<&'a str>, { match logic.get_facetec_session_token().await { Ok(res) => { @@ -60,8 +60,8 @@ pub async fn get_facetec_device_sdk_params( logic: Arc>, ) -> Result where - S: Signer + Send + 'static, - PK: Send + for<'a> TryFrom<&'a str> + Verifier + Into>, + S: Signer> + Send + 'static, + PK: Send + for<'a> TryFrom<&'a str>, { match logic.get_facetec_device_sdk_params().await { Ok(res) => { diff --git a/crates/robonode-server/src/lib.rs b/crates/robonode-server/src/lib.rs index a38f4d5d2..c2f446309 100644 --- a/crates/robonode-server/src/lib.rs +++ b/crates/robonode-server/src/lib.rs @@ -6,7 +6,7 @@ clippy::clone_on_ref_ptr )] -use std::{marker::PhantomData, sync::Arc}; +use std::{convert::Infallible, marker::PhantomData, sync::Arc}; use http::root; use tokio::sync::Mutex; @@ -23,13 +23,14 @@ pub use logic::FacetecDeviceSdkParams; pub fn init( facetec_api_client: facetec_api_client::Client, facetec_device_sdk_params: FacetecDeviceSdkParams, + robonode_keypair: robonode_crypto::Keypair, ) -> impl Filter + Clone { let logic = logic::Logic { locked: Mutex::new(logic::Locked { sequence: sequence::Sequence::new(0), facetec: facetec_api_client, - signer: (), - public_key_type: PhantomData::, + signer: robonode_keypair, + public_key_type: PhantomData::, }), facetec_device_sdk_params, }; @@ -37,16 +38,46 @@ pub fn init( root(Arc::new(logic)).with(log) } -// TODO! -impl logic::Signer for () { - fn sign>(&self, _data: &D) -> Vec { - todo!() +#[async_trait::async_trait] +impl logic::Signer> for robonode_crypto::Keypair { + type Error = Infallible; + + async fn sign<'a, D>(&self, data: D) -> Result, Self::Error> + where + D: AsRef<[u8]> + Send + 'a, + { + use robonode_crypto::ed25519_dalek::Signer; + let sig = Signer::sign(self, data.as_ref()); + Ok(sig.as_ref().to_owned()) + } +} + +/// A temporary validator key mock, that accepts any byte sequences as keys, and consideres any +/// signatures valid. +struct ValidatorPublicKeyToDo(Vec); + +#[async_trait::async_trait] +impl logic::Verifier> for ValidatorPublicKeyToDo { + type Error = Infallible; + + async fn verify<'a, D>(&self, _data: D, _signature: Vec) -> Result + where + D: AsRef<[u8]> + Send + 'a, + { + Ok(true) + } +} + +impl std::convert::TryFrom<&str> for ValidatorPublicKeyToDo { + type Error = (); + + fn try_from(val: &str) -> Result { + Ok(Self(val.into())) } } -// TODO! -impl logic::Verifier for String { - fn verify, S: AsRef<[u8]>>(&self, _data: &D, _signature: &S) -> bool { - todo!() +impl From for Vec { + fn from(val: ValidatorPublicKeyToDo) -> Self { + val.0 } } diff --git a/crates/robonode-server/src/logic.rs b/crates/robonode-server/src/logic.rs index 82e99db08..babe3f69b 100644 --- a/crates/robonode-server/src/logic.rs +++ b/crates/robonode-server/src/logic.rs @@ -15,17 +15,31 @@ use crate::sequence::Sequence; use serde::{Deserialize, Serialize}; /// Signer provides signatures for the data. -pub trait Signer { - /// Sign the provided data and return the signature. - fn sign>(&self, data: &D) -> Vec; +#[async_trait::async_trait] +pub trait Signer { + /// Signature error. + /// Error may originate from communicating with HSM, or from a thread pool failure, etc. + type Error; + + /// Sign the provided data and return the signature, or an error if the siging fails. + async fn sign<'a, D>(&self, data: D) -> Result + where + D: AsRef<[u8]> + Send + 'a; } /// Verifier provides the verification of the data accompanied with the /// signature or proof data. -pub trait Verifier { +#[async_trait::async_trait] +pub trait Verifier { + /// Verification error. + /// Error may originate from communicating with HSM, or from a thread pool failure, etc. + type Error; + /// Verify that provided data is indeed correctly signed with the provided /// signature. - fn verify, S: AsRef<[u8]>>(&self, data: &D, signature: &S) -> bool; + async fn verify<'a, D>(&self, data: D, signature: S) -> Result + where + D: AsRef<[u8]> + Send + 'a; } /// The FaceTec Device SDK params. @@ -41,7 +55,7 @@ pub struct FacetecDeviceSdkParams { /// access to it unless we lock the mutex. pub struct Locked where - S: Signer + 'static, + S: Signer> + 'static, PK: Send + for<'a> TryFrom<&'a str>, { /// The sequence number. @@ -57,7 +71,7 @@ where /// The overall generic logic. pub struct Logic where - S: Signer + Send + 'static, + S: Signer> + Send + 'static, PK: Send + for<'a> TryFrom<&'a str>, { /// The mutex over the locked portions of the logic. @@ -124,7 +138,7 @@ const MATCH_LEVEL: i64 = 10; impl Logic where - S: Signer + Send + 'static, + S: Signer> + Send + 'static, PK: Send + for<'a> TryFrom<&'a str>, { /// An enroll invocation handler. @@ -241,7 +255,7 @@ pub enum AuthenticateError { /// The liveness data signature validation failed. /// This means that the user might've provided a signature using different /// keypair from what was used for the original enrollment. - SignatureValidationFailed, + SignatureInvalid, /// Internal error at server-level enrollment due to the underlying request /// error at the API level. InternalErrorEnrollment(FacetecError), @@ -259,12 +273,16 @@ pub enum AuthenticateError { InternalErrorDbSearchMatchLevelMismatch, /// Internal error at public key loading due to invalid public key. InternalErrorInvalidPublicKey, + /// Internal error at signature verification. + InternalErrorSignatureVerificationFailed, + /// Internal error when signing auth ticket. + InternalErrorAuthTicketSigningFailed, } impl Logic where - S: Signer + Send + 'static, - PK: Send + for<'a> TryFrom<&'a str> + Verifier + Into>, + S: Signer> + Send + 'static, + PK: Send + Sync + for<'a> TryFrom<&'a str> + Verifier> + Into>, { /// An authenticate invocation handler. pub async fn authenticate( @@ -332,8 +350,13 @@ where let public_key = PK::try_from(&found.identifier) .map_err(|_| AuthenticateError::InternalErrorInvalidPublicKey)?; - if !public_key.verify(&req.liveness_data, &req.liveness_data_signature) { - return Err(AuthenticateError::SignatureValidationFailed); + let signature_valid = public_key + .verify(&req.liveness_data, req.liveness_data_signature) + .await + .map_err(|_| AuthenticateError::InternalErrorSignatureVerificationFailed)?; + + if !signature_valid { + return Err(AuthenticateError::SignatureInvalid); } // Prepare an authentication nonce from the sequence number. @@ -352,7 +375,11 @@ where // Sign the auth ticket with our private key, so that later on it's possible to validate // this ticket was issues by us. - let auth_ticket_signature = unlocked.signer.sign(&opaque_auth_ticket); + let auth_ticket_signature = unlocked + .signer + .sign(&opaque_auth_ticket) + .await + .map_err(|_| AuthenticateError::InternalErrorAuthTicketSigningFailed)?; Ok(AuthenticateResponse { auth_ticket: opaque_auth_ticket, @@ -380,7 +407,7 @@ pub enum GetFacetecSessionTokenError { impl Logic where - S: Signer + Send + 'static, + S: Signer> + Send + 'static, PK: Send + for<'a> TryFrom<&'a str>, { /// Get a FaceTec Session Token. @@ -420,7 +447,7 @@ pub enum GetFacetecDeviceSdkParamsError {} impl Logic where - S: Signer + Send + 'static, + S: Signer> + Send + 'static, PK: Send + for<'a> TryFrom<&'a str>, { /// Get the FaceTec Device SDK params . diff --git a/crates/robonode-server/src/main.rs b/crates/robonode-server/src/main.rs index 77844b5d9..4412e3671 100644 --- a/crates/robonode-server/src/main.rs +++ b/crates/robonode-server/src/main.rs @@ -13,6 +13,9 @@ async fn main() -> Result<(), Box> { let facetec_device_key_identifier: String = parse_env_var("FACETEC_DEVICE_KEY_IDENTIFIER")?; let facetec_public_face_map_encryption_key = parse_env_var("FACETEC_PUBLIC_FACE_MAP_ENCRYPTION_KEY")?; + let robonode_keypair_string: String = parse_env_var("ROBONODE_KEYPAIR")?; + let robonode_keypair_bytes = hex::decode(robonode_keypair_string)?; + let robonode_keypair = robonode_crypto::Keypair::from_bytes(robonode_keypair_bytes.as_slice())?; let facetec_api_client = facetec_api_client::Client { base_url: facetec_server_url, @@ -24,7 +27,11 @@ async fn main() -> Result<(), Box> { public_face_map_encryption_key: facetec_public_face_map_encryption_key, }; - let root_filter = robonode_server::init(facetec_api_client, face_tec_device_sdk_params); + let root_filter = robonode_server::init( + facetec_api_client, + face_tec_device_sdk_params, + robonode_keypair, + ); let (addr, server) = warp::serve(root_filter).bind_with_graceful_shutdown(addr, shutdown_signal()); println!("Listening on http://{}", addr); diff --git a/crates/serde-nostd/Cargo.toml b/crates/serde-nostd/Cargo.toml new file mode 100644 index 000000000..dda9b7c4a --- /dev/null +++ b/crates/serde-nostd/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "serde-nostd" +version = "0.1.0" +edition = "2018" +authors = ["Humanode Team "] +publish = false + +[dependencies] +serde = { version = "1", features = ["derive"], optional = true } + +[features] +default = ["std"] +std = ["serde"] diff --git a/crates/serde-nostd/src/lib.rs b/crates/serde-nostd/src/lib.rs new file mode 100644 index 000000000..8f6d89ee7 --- /dev/null +++ b/crates/serde-nostd/src/lib.rs @@ -0,0 +1,26 @@ +//! A utility crate that provides traits (usable in trait bounds) that resolve to serde in `std`, +//! and to nothing at `no_std`. + +#![warn( + missing_docs, + clippy::missing_docs_in_private_items, + clippy::clone_on_ref_ptr +)] +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(feature = "std")] +use serde::{Deserialize, Serialize}; + +/// When at std, Serialize and Deserialize. +#[cfg(feature = "std")] +pub trait SerDe: Serialize + for<'de> Deserialize<'de> {} + +#[cfg(feature = "std")] +impl SerDe for T where T: Serialize + for<'de> Deserialize<'de> {} + +/// When at no_std, empty. +#[cfg(not(feature = "std"))] +pub trait SerDe {} + +#[cfg(not(feature = "std"))] +impl SerDe for T {}