From 2f5f4fce974845bea6efe2edb35da2deef097775 Mon Sep 17 00:00:00 2001 From: TimeEngineer Date: Tue, 9 May 2023 12:40:28 +0200 Subject: [PATCH 1/2] Remove handshake --- .../libparsec/crates/protocol/src/error.rs | 39 - .../crates/protocol/src/handshake.rs | 779 --------------- .../libparsec/crates/protocol/src/lib.rs | 105 +- .../libparsec/crates/protocol/tests/mod.rs | 1 - .../crates/protocol/tests/test_handshake.rs | 901 ------------------ 5 files changed, 90 insertions(+), 1735 deletions(-) delete mode 100644 oxidation/libparsec/crates/protocol/src/handshake.rs delete mode 100644 oxidation/libparsec/crates/protocol/tests/test_handshake.rs diff --git a/oxidation/libparsec/crates/protocol/src/error.rs b/oxidation/libparsec/crates/protocol/src/error.rs index 7d4b722cd08..fe9699d40dc 100644 --- a/oxidation/libparsec/crates/protocol/src/error.rs +++ b/oxidation/libparsec/crates/protocol/src/error.rs @@ -2,35 +2,6 @@ use thiserror::Error; -use libparsec_types::prelude::*; - -use crate::{ApiVersion, HANDSHAKE_CHALLENGE_SIZE}; - -#[derive(Error, Debug, Clone, PartialEq)] -pub enum HandshakeError { - #[error("Handshake invalid answer (bad signature or challenge value)")] - FailedChallenge, - #[error("Handshake bad administration token")] - BadAdministrationToken, - #[error("Handshake bad identity")] - BadIdentity, - #[error("Handshake organization expired")] - OrganizationExpired, - #[error("Handshake rvk mismatch")] - RVKMismatch, - #[error("Handshake revoked device")] - RevokedDevice, - #[error("Handshake out of ballpark {0:x?}")] - OutOfBallpark(ChallengeDataReport), - #[error("No overlap between client API versions {client_versions:?} and backend API versions {backend_versions:?}")] - APIVersion { - client_versions: Vec, - backend_versions: Vec, - }, - #[error("Handshake invalid message: {0}")] - InvalidMessage(&'static str), -} - #[derive(Error, Debug, Clone, PartialEq, Eq)] pub enum ProtocolError { #[error("Encoding error: {exc}")] @@ -48,13 +19,3 @@ pub enum ProtocolError { #[error("{exc}")] BadRequest { exc: String }, } - -#[derive(Debug, Clone, PartialEq)] -pub struct ChallengeDataReport { - pub challenge: [u8; HANDSHAKE_CHALLENGE_SIZE], - pub supported_api_versions: Vec, - pub backend_timestamp: DateTime, - pub client_timestamp: DateTime, - pub ballpark_client_early_offset: f64, - pub ballpark_client_late_offset: f64, -} diff --git a/oxidation/libparsec/crates/protocol/src/handshake.rs b/oxidation/libparsec/crates/protocol/src/handshake.rs deleted file mode 100644 index 05fff70e814..00000000000 --- a/oxidation/libparsec/crates/protocol/src/handshake.rs +++ /dev/null @@ -1,779 +0,0 @@ -// Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 (eventually AGPL-3.0) 2016-present Scille SAS - -use std::{cmp::Ordering, fmt::Display}; - -use rand::{thread_rng, Rng}; -use serde::{Deserialize, Serialize}; -use serde_with::{serde_as, Bytes}; - -use libparsec_types::prelude::*; - -use crate::{impl_dump_load, ChallengeDataReport, HandshakeError}; - -pub const HANDSHAKE_CHALLENGE_SIZE: usize = 48; - -pub const API_V1_VERSION: ApiVersion = ApiVersion { - version: 1, - revision: 3, -}; -pub const API_V2_VERSION: ApiVersion = ApiVersion { - version: 2, - revision: 5, -}; -pub const API_VERSION: ApiVersion = API_V2_VERSION; -pub const BALLPARK_CLIENT_EARLY_OFFSET: f64 = 300.0; // seconds -pub const BALLPARK_CLIENT_LATE_OFFSET: f64 = 320.0; // seconds -const BALLPARK_CLIENT_TOLERANCE: f64 = 0.8; // 80% - -pub fn timestamps_in_the_ballpark( - client: DateTime, - backend: DateTime, - ballpark_client_early_offset: f64, - ballpark_client_late_offset: f64, -) -> bool { - // Useful to compare signed message timestamp with the one stored by the - // backend. - let seconds = (backend - client).num_seconds() as f64; - -ballpark_client_early_offset < seconds && seconds < ballpark_client_late_offset -} - -#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] -#[serde(from = "(u32, u32)", into = "(u32, u32)")] -pub struct ApiVersion { - pub version: u32, - pub revision: u32, -} - -impl ApiVersion { - pub fn dump(&self) -> Result, rmp_serde::encode::Error> { - rmp_serde::to_vec(self) - } - - pub fn load(buf: &[u8]) -> Result { - rmp_serde::from_slice(buf) - } -} - -impl PartialOrd for ApiVersion { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for ApiVersion { - fn cmp(&self, other: &Self) -> Ordering { - match self.version.cmp(&other.version) { - Ordering::Equal => self.revision.cmp(&other.revision), - order => order, - } - } -} - -impl From<(u32, u32)> for ApiVersion { - fn from(tuple: (u32, u32)) -> Self { - Self { - version: tuple.0, - revision: tuple.1, - } - } -} - -impl From for (u32, u32) { - fn from(api_version: ApiVersion) -> Self { - (api_version.version, api_version.revision) - } -} - -impl Display for ApiVersion { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}.{}", self.version, self.revision) - } -} - -impl TryFrom<&str> for ApiVersion { - type Error = &'static str; - - fn try_from(value: &str) -> Result { - if value.split('.').count() != 2 { - return Err( - "Wrong number of `.` version string must be follow this pattern `.`" - ); - } - - let (version_str, revision_str) = value - .split_once('.') - .ok_or("Api version string must be follow this pattern `.`")?; - - let version = version_str.parse::(); - let revision = revision_str.parse::(); - match (version, revision) { - (Ok(a), Ok(b)) => Ok(ApiVersion { - version: a, - revision: b, - }), - _ => Err("Failed to parse version number (.)"), - } - } -} - -#[serde_as] -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -#[serde(tag = "type")] -pub enum Answer { - #[serde(rename = "AUTHENTICATED")] - Authenticated { - client_api_version: ApiVersion, - organization_id: OrganizationID, - device_id: DeviceID, - // RustCrypto cache additional points on the Edward curve so total size is around ~200bytes - // That's why VerifyKey is boxed to keep approximately the same size as InvitedAnswer - rvk: Box, - #[serde_as(as = "Bytes")] - answer: Vec, - }, - #[serde(rename = "INVITED")] - Invited { - client_api_version: ApiVersion, - organization_id: OrganizationID, - invitation_type: InvitationType, - token: InvitationToken, - }, -} - -impl_dump_load!(Answer); - -#[serde_as] -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -#[serde(tag = "type", rename = "signed_answer")] -pub struct SignedAnswer { - #[serde_as(as = "Bytes")] - pub answer: [u8; HANDSHAKE_CHALLENGE_SIZE], -} - -impl_dump_load!(SignedAnswer); - -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "snake_case")] -pub enum HandshakeResult { - BadAdminToken, - BadIdentity, - BadProtocol, - Ok, - OrganizationExpired, - RevokedDevice, - RvkMismatch, -} - -#[serde_as] -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -#[serde(tag = "handshake", rename_all = "snake_case")] -pub enum Handshake { - Challenge { - #[serde_as(as = "Bytes")] - challenge: [u8; HANDSHAKE_CHALLENGE_SIZE], - supported_api_versions: Vec, - // Those fields have been added to API version 2.4 (Parsec 2.7.0) - // They are provided to the client in order to allow them to detect whether - // their system clock is out of sync and let them close the connection. - // They will be missing for older backend so they cannot be strictly required. - // TODO: This backward compatibility should be removed once Parsec < 2.4 support is dropped - #[serde(default, deserialize_with = "maybe_field::deserialize_some")] - ballpark_client_early_offset: Option, - #[serde(default, deserialize_with = "maybe_field::deserialize_some")] - ballpark_client_late_offset: Option, - #[serde(default, deserialize_with = "maybe_field::deserialize_some")] - backend_timestamp: Option, - }, - Answer(Answer), - #[serde(rename = "answer")] - SignedAnswer(SignedAnswer), - Result { - result: HandshakeResult, - #[serde(default, deserialize_with = "maybe_field::deserialize_some")] - help: Option, - }, -} - -impl_dump_load!(Handshake); - -#[derive(Debug)] -pub struct ServerHandshakeStalled { - pub supported_api_version: Vec, -} - -impl Default for ServerHandshakeStalled { - fn default() -> Self { - Self { - supported_api_version: vec![API_V2_VERSION, API_V1_VERSION], - } - } -} - -impl ServerHandshakeStalled { - fn _build_challenge_req_with_challenge( - self, - challenge: [u8; HANDSHAKE_CHALLENGE_SIZE], - timestamp: DateTime, - ) -> Result { - let raw = Handshake::Challenge { - challenge, - supported_api_versions: self.supported_api_version.clone(), - ballpark_client_early_offset: Some(BALLPARK_CLIENT_EARLY_OFFSET), - ballpark_client_late_offset: Some(BALLPARK_CLIENT_LATE_OFFSET), - backend_timestamp: Some(timestamp), - } - .dump() - .map_err(HandshakeError::InvalidMessage)?; - - Ok(ServerHandshakeChallenge { - supported_api_version: self.supported_api_version, - challenge, - raw, - }) - } - - pub fn build_challenge_req( - self, - timestamp: DateTime, - ) -> Result { - let mut challenge = [0; HANDSHAKE_CHALLENGE_SIZE]; - thread_rng().fill(&mut challenge[..]); - self._build_challenge_req_with_challenge(challenge, timestamp) - } - - #[cfg(feature = "test")] - pub fn build_challenge_req_with_challenge( - self, - challenge: [u8; HANDSHAKE_CHALLENGE_SIZE], - timestamp: DateTime, - ) -> Result { - self._build_challenge_req_with_challenge(challenge, timestamp) - } -} - -#[derive(Debug)] -pub struct ServerHandshakeChallenge { - pub supported_api_version: Vec, - pub challenge: [u8; HANDSHAKE_CHALLENGE_SIZE], - pub raw: Vec, -} - -impl ServerHandshakeChallenge { - pub fn process_answer_req(self, req: &[u8]) -> Result { - if let Handshake::Answer(data) = - Handshake::load(req).map_err(HandshakeError::InvalidMessage)? - { - let client_api_version = match data { - Answer::Authenticated { - client_api_version, .. - } => client_api_version, - Answer::Invited { - client_api_version, .. - } => client_api_version, - }; - if client_api_version.version == 1 || client_api_version.version == 2 { - return Ok(ServerHandshakeAnswer { - client_api_version, - challenge: self.challenge, - data, - }); - } else { - return Err(HandshakeError::APIVersion { - backend_versions: self.supported_api_version, - client_versions: vec![client_api_version], - }); - } - } - Err(HandshakeError::InvalidMessage("Invalid data")) - } - - pub fn build_bad_protocol_result_req( - self, - help: Option, - ) -> Result { - let help = match help { - None => Some("Invalid params".into()), - _ => help, - }; - - Ok(ServerHandshakeResult { - client_api_version: ApiVersion::default(), - raw: Handshake::Result { - result: HandshakeResult::BadProtocol, - help, - } - .dump() - .map_err(HandshakeError::InvalidMessage)?, - }) - } -} - -#[derive(Debug)] -pub struct ServerHandshakeAnswer { - client_api_version: ApiVersion, - challenge: [u8; HANDSHAKE_CHALLENGE_SIZE], - pub data: Answer, -} - -impl ServerHandshakeAnswer { - const VERSION: ApiVersion = ApiVersion { - version: 2, - revision: 5, - }; - - pub fn build_result_req( - self, - verify_key: Option, - ) -> Result { - if let Answer::Authenticated { - client_api_version, - answer, - .. - } = self.data - { - match verify_key { - Some(verify_key) => { - let returned_challenge = if client_api_version >= Self::VERSION { - let answer = verify_key - .verify(&answer) - .map_err(|_| HandshakeError::FailedChallenge)?; - SignedAnswer::load(&answer) - .map_err(HandshakeError::InvalidMessage)? - .answer - } else { - verify_key - .verify(&answer) - .map_err(|_| HandshakeError::FailedChallenge)? - .try_into() - .unwrap() - }; - - if returned_challenge != self.challenge { - return Err(HandshakeError::FailedChallenge); - } - } - _ => { - return Err(HandshakeError::InvalidMessage( - "`verify_key` param must be provided for authenticated handshake", - )) - } - } - } - - Ok(ServerHandshakeResult { - client_api_version: self.client_api_version, - raw: Handshake::Result { - result: HandshakeResult::Ok, - help: None, - } - .dump() - .map_err(HandshakeError::InvalidMessage)?, - }) - } - - pub fn build_bad_protocol_result_req( - self, - help: Option, - ) -> Result { - let help = match help { - None => Some("Invalid params".into()), - _ => help, - }; - - Ok(ServerHandshakeResult { - client_api_version: ApiVersion::default(), - raw: Handshake::Result { - result: HandshakeResult::BadProtocol, - help, - } - .dump() - .map_err(HandshakeError::InvalidMessage)?, - }) - } - - pub fn build_bad_administration_token_result_req( - self, - help: Option, - ) -> Result { - let help = match help { - None => Some("Invalid administration token".into()), - _ => help, - }; - - Ok(ServerHandshakeResult { - client_api_version: ApiVersion::default(), - raw: Handshake::Result { - result: HandshakeResult::BadAdminToken, - help, - } - .dump() - .map_err(HandshakeError::InvalidMessage)?, - }) - } - - pub fn build_bad_identity_result_req( - self, - help: Option, - ) -> Result { - let help = match help { - None => Some("Invalid handshake information".into()), - _ => help, - }; - - Ok(ServerHandshakeResult { - client_api_version: ApiVersion::default(), - raw: Handshake::Result { - result: HandshakeResult::BadIdentity, - help, - } - .dump() - .map_err(HandshakeError::InvalidMessage)?, - }) - } - - pub fn build_organization_expired_result_req( - self, - help: Option, - ) -> Result { - let help = match help { - None => Some("Trial organization has expired".into()), - _ => help, - }; - - Ok(ServerHandshakeResult { - client_api_version: ApiVersion::default(), - raw: Handshake::Result { - result: HandshakeResult::OrganizationExpired, - help, - } - .dump() - .map_err(HandshakeError::InvalidMessage)?, - }) - } - - pub fn build_rvk_mismatch_result_req( - self, - help: Option, - ) -> Result { - let help = match help { - None => { - Some("Root verify key for organization differs between client and server".into()) - } - _ => help, - }; - - Ok(ServerHandshakeResult { - client_api_version: ApiVersion::default(), - raw: Handshake::Result { - result: HandshakeResult::RvkMismatch, - help, - } - .dump() - .map_err(HandshakeError::InvalidMessage)?, - }) - } - - pub fn build_revoked_device_result_req( - self, - help: Option, - ) -> Result { - let help = match help { - None => Some("Device has been revoked".into()), - _ => help, - }; - - Ok(ServerHandshakeResult { - client_api_version: ApiVersion::default(), - raw: Handshake::Result { - result: HandshakeResult::RevokedDevice, - help, - } - .dump() - .map_err(HandshakeError::InvalidMessage)?, - }) - } -} - -#[derive(Debug)] -pub struct ServerHandshakeResult { - pub client_api_version: ApiVersion, - pub raw: Vec, -} - -#[derive(Debug)] -pub struct AuthenticatedClientHandshakeStalled { - pub organization_id: OrganizationID, - pub device_id: DeviceID, - pub user_signkey: SigningKey, - pub root_verify_key: VerifyKey, - pub supported_api_versions: Vec, - pub client_timestamp: DateTime, -} - -fn _settle_compatible_versions( - backend_versions: &[ApiVersion], - client_versions: &[ApiVersion], -) -> Result<(ApiVersion, ApiVersion), HandshakeError> { - // Try to use the newest version first - for cv in client_versions.iter().rev() { - // No need to compare `revision` because only `version` field breaks compatibility - if let Some(bv) = backend_versions.iter().find(|bv| bv.version == cv.version) { - return Ok((*bv, *cv)); - } - } - Err(HandshakeError::APIVersion { - backend_versions: backend_versions.to_vec(), - client_versions: client_versions.to_vec(), - }) -} - -fn load_challenge_req( - req: &[u8], - _supported_api_versions: &[ApiVersion], - client_timestamp: DateTime, -) -> Result< - ( - [u8; HANDSHAKE_CHALLENGE_SIZE], - ApiVersion, - ApiVersion, - Vec, - ), - HandshakeError, -> { - let challenge_data = Handshake::load(req).map_err(HandshakeError::InvalidMessage)?; - - if let Handshake::Challenge { - challenge, - supported_api_versions, - backend_timestamp, - ballpark_client_early_offset, - ballpark_client_late_offset, - } = challenge_data - { - // API version matching - let (backend_api_version, client_api_version) = - _settle_compatible_versions(&supported_api_versions, _supported_api_versions)?; - - // Those fields are missing with parsec API 2.3 and lower - if let ( - Some(backend_timestamp), - Some(ballpark_client_early_offset), - Some(ballpark_client_late_offset), - ) = ( - backend_timestamp, - ballpark_client_early_offset, - ballpark_client_late_offset, - ) { - // Check whether our system clock is in sync with the backend - if !timestamps_in_the_ballpark( - client_timestamp, - backend_timestamp, - // The client is a bit less tolerant than the backend - ballpark_client_early_offset * BALLPARK_CLIENT_TOLERANCE, - ballpark_client_late_offset * BALLPARK_CLIENT_TOLERANCE, - ) { - // Add `client_timestamp` to challenge data - // so the dictionary exposes the same fields as `TimestampOutOfBallparkRepSchema` - return Err(HandshakeError::OutOfBallpark(ChallengeDataReport { - challenge, - supported_api_versions, - backend_timestamp, - client_timestamp, - ballpark_client_early_offset, - ballpark_client_late_offset, - })); - } - } - return Ok(( - challenge, - backend_api_version, - client_api_version, - supported_api_versions, - )); - } - Err(HandshakeError::InvalidMessage("Invalid data")) -} - -fn process_result_req(req: &[u8]) -> Result<(), HandshakeError> { - if let Handshake::Result { result, .. } = - Handshake::load(req).map_err(HandshakeError::InvalidMessage)? - { - match result { - HandshakeResult::BadIdentity => Err(HandshakeError::BadIdentity), - HandshakeResult::OrganizationExpired => Err(HandshakeError::OrganizationExpired), - HandshakeResult::RvkMismatch => Err(HandshakeError::RVKMismatch), - HandshakeResult::RevokedDevice => Err(HandshakeError::RevokedDevice), - HandshakeResult::BadAdminToken => Err(HandshakeError::BadAdministrationToken), - HandshakeResult::BadProtocol => Err(HandshakeError::InvalidMessage( - "Bad protocol replied by peer", - )), - HandshakeResult::Ok => Ok(()), - } - } else { - Err(HandshakeError::InvalidMessage("Invalid data")) - } -} - -impl AuthenticatedClientHandshakeStalled { - const VERSION: ApiVersion = ApiVersion { - version: 2, - revision: 5, - }; - - pub fn new( - organization_id: OrganizationID, - device_id: DeviceID, - user_signkey: SigningKey, - root_verify_key: VerifyKey, - client_timestamp: DateTime, - ) -> Self { - let mut supported_api_versions = vec![API_V2_VERSION, API_V1_VERSION]; - supported_api_versions.sort(); - - Self { - organization_id, - device_id, - user_signkey, - root_verify_key, - supported_api_versions, - client_timestamp, - } - } - - pub fn process_challenge_req( - self, - req: &[u8], - ) -> Result { - let (challenge, backend_api_version, client_api_version, supported_api_versions) = - load_challenge_req(req, &self.supported_api_versions, self.client_timestamp)?; - - // TO-DO remove the else for the next release - let answer = if backend_api_version >= Self::VERSION { - // TO-DO Need to use "BaseSignedData" ? - self.user_signkey.sign( - &SignedAnswer { answer: challenge } - .dump() - .map_err(HandshakeError::InvalidMessage)?, - ) - } else { - self.user_signkey.sign(&challenge) - }; - - Ok(AuthenticatedClientHandshakeChallenge { - backend_api_version, - client_api_version, - supported_api_versions, - raw: Handshake::Answer(Answer::Authenticated { - client_api_version, - organization_id: self.organization_id, - device_id: self.device_id, - rvk: Box::new(self.root_verify_key), - answer, - }) - .dump() - .map_err(HandshakeError::InvalidMessage)?, - }) - } - - pub fn process_result_req(self, req: &[u8]) -> Result<(), HandshakeError> { - process_result_req(req) - } -} - -#[derive(Debug)] -pub struct AuthenticatedClientHandshakeChallenge { - pub backend_api_version: ApiVersion, - pub client_api_version: ApiVersion, - pub supported_api_versions: Vec, - pub raw: Vec, -} - -#[derive(Debug)] -pub struct InvitedClientHandshakeStalled { - pub organization_id: OrganizationID, - pub invitation_type: InvitationType, - pub token: InvitationToken, - pub supported_api_versions: Vec, - pub client_timestamp: DateTime, -} - -impl InvitedClientHandshakeStalled { - pub fn new( - organization_id: OrganizationID, - invitation_type: InvitationType, - token: InvitationToken, - client_timestamp: DateTime, - ) -> Self { - let mut supported_api_versions = vec![API_V2_VERSION, API_V1_VERSION]; - supported_api_versions.sort(); - - Self { - organization_id, - invitation_type, - token, - supported_api_versions, - client_timestamp, - } - } - - pub fn process_challenge_req( - self, - req: &[u8], - ) -> Result { - let (_, backend_api_version, client_api_version, _) = - load_challenge_req(req, &self.supported_api_versions, self.client_timestamp)?; - - Ok(InvitedClientHandshakeChallenge { - backend_api_version, - client_api_version, - raw: Handshake::Answer(Answer::Invited { - client_api_version, - organization_id: self.organization_id, - invitation_type: self.invitation_type, - token: self.token, - }) - .dump() - .map_err(HandshakeError::InvalidMessage)?, - }) - } - - pub fn process_result_req(self, req: &[u8]) -> Result<(), HandshakeError> { - process_result_req(req) - } -} - -#[derive(Debug)] -pub struct InvitedClientHandshakeChallenge { - pub backend_api_version: ApiVersion, - pub client_api_version: ApiVersion, - pub raw: Vec, -} - -#[cfg(test)] -mod test { - use libparsec_tests_fixtures::{parsec_test, rstest}; - - use crate::ApiVersion; - - #[parsec_test] - #[case::valid_version_one("1.0", ApiVersion { version: 1, revision: 0 })] - #[case::valid_version_two("2.5", ApiVersion { version: 2, revision: 5 })] - fn test_valid_version_string_parse( - #[case] version_str: &str, - #[case] expected_version: ApiVersion, - ) { - let parsed_version = - ApiVersion::try_from(version_str).expect("Should not fail to parse version string"); - assert_eq!(parsed_version, expected_version); - } - - #[parsec_test] - #[case::invalid_missing_version(".0")] - #[case::invalid_missing_minor("2.")] - #[case::no_separator("25")] - #[case::non_digit_chars("2.5dksjdskdjs")] - fn test_invalid_version_string_parse(#[case] version_str: &str) { - let parsed_version = ApiVersion::try_from(version_str); - assert!(parsed_version.is_err()); - } -} diff --git a/oxidation/libparsec/crates/protocol/src/lib.rs b/oxidation/libparsec/crates/protocol/src/lib.rs index 43c5fc98d6c..1ea6d4bee87 100644 --- a/oxidation/libparsec/crates/protocol/src/lib.rs +++ b/oxidation/libparsec/crates/protocol/src/lib.rs @@ -1,27 +1,102 @@ // Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 (eventually AGPL-3.0) 2016-present Scille SAS mod error; -mod handshake; + +use serde::{Deserialize, Serialize}; +use std::{cmp::Ordering, fmt::Display}; use libparsec_serialization_format::parsec_protocol_cmds_family; pub use error::*; -pub use handshake::*; - -macro_rules! impl_dump_load { - ($name:ident) => { - impl $name { - pub fn dump(&self) -> Result, &'static str> { - ::rmp_serde::to_vec_named(self).map_err(|_| "Serialization failed") - } - - pub fn load(buf: &[u8]) -> Result { - ::rmp_serde::from_slice(buf).map_err(|_| "Deserialization failed") - } + +pub const API_V1_VERSION: ApiVersion = ApiVersion { + version: 1, + revision: 3, +}; +pub const API_V2_VERSION: ApiVersion = ApiVersion { + version: 2, + revision: 5, +}; +pub const API_VERSION: ApiVersion = API_V2_VERSION; + +#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[serde(from = "(u32, u32)", into = "(u32, u32)")] +pub struct ApiVersion { + pub version: u32, + pub revision: u32, +} + +impl ApiVersion { + pub fn dump(&self) -> Result, rmp_serde::encode::Error> { + rmp_serde::to_vec(self) + } + + pub fn load(buf: &[u8]) -> Result { + rmp_serde::from_slice(buf) + } +} + +impl PartialOrd for ApiVersion { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for ApiVersion { + fn cmp(&self, other: &Self) -> Ordering { + match self.version.cmp(&other.version) { + Ordering::Equal => self.revision.cmp(&other.revision), + order => order, + } + } +} + +impl From<(u32, u32)> for ApiVersion { + fn from(tuple: (u32, u32)) -> Self { + Self { + version: tuple.0, + revision: tuple.1, + } + } +} + +impl From for (u32, u32) { + fn from(api_version: ApiVersion) -> Self { + (api_version.version, api_version.revision) + } +} + +impl Display for ApiVersion { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}.{}", self.version, self.revision) + } +} + +impl TryFrom<&str> for ApiVersion { + type Error = &'static str; + + fn try_from(value: &str) -> Result { + if value.split('.').count() != 2 { + return Err( + "Wrong number of `.` version string must be follow this pattern `.`" + ); + } + + let (version_str, revision_str) = value + .split_once('.') + .ok_or("Api version string must be follow this pattern `.`")?; + + let version = version_str.parse::(); + let revision = revision_str.parse::(); + match (version, revision) { + (Ok(a), Ok(b)) => Ok(ApiVersion { + version: a, + revision: b, + }), + _ => Err("Failed to parse version number (.)"), } - }; + } } -pub(crate) use impl_dump_load; // This macro implements dump/load methods for client/server side. // It checks if both Req and Rep are implemented for a specified command diff --git a/oxidation/libparsec/crates/protocol/tests/mod.rs b/oxidation/libparsec/crates/protocol/tests/mod.rs index ca2f8bde470..4b802c91ab0 100644 --- a/oxidation/libparsec/crates/protocol/tests/mod.rs +++ b/oxidation/libparsec/crates/protocol/tests/mod.rs @@ -3,7 +3,6 @@ mod test_block; mod test_common; mod test_events; -mod test_handshake; mod test_invite; mod test_message; mod test_organization; diff --git a/oxidation/libparsec/crates/protocol/tests/test_handshake.rs b/oxidation/libparsec/crates/protocol/tests/test_handshake.rs deleted file mode 100644 index f519b873e1a..00000000000 --- a/oxidation/libparsec/crates/protocol/tests/test_handshake.rs +++ /dev/null @@ -1,901 +0,0 @@ -// Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 (eventually AGPL-3.0) 2016-present Scille SAS - -use core::panic; -use hex_literal::hex; -use serde_json::{json, Value}; -use std::str::FromStr; - -use libparsec_protocol::{ - Answer, ApiVersion, AuthenticatedClientHandshakeStalled, Handshake, HandshakeError, - HandshakeResult, InvitedClientHandshakeStalled, ServerHandshakeAnswer, ServerHandshakeResult, - ServerHandshakeStalled, SignedAnswer, API_V2_VERSION, BALLPARK_CLIENT_EARLY_OFFSET, - BALLPARK_CLIENT_LATE_OFFSET, -}; -use libparsec_tests_fixtures::{alice, bob, parsec_test, timestamp, Device}; -use libparsec_types::prelude::*; - -#[cfg(feature = "test")] -#[parsec_test] -fn test_good_authenticated_handshake_server(alice: &Device, timestamp: DateTime) { - let challenge = hex!( - "bbf9777bdc479d6c3d6a77b93aa26370f88ee51f81f3983a074ab32fbfcd33ef6a1b6e3f2e9718e2" - "27d0b59e8a749078" - ); - - let sh = ServerHandshakeStalled::default() - .build_challenge_req_with_challenge(challenge, timestamp) - .unwrap(); - - // Generated from Python implementation (Parsec v2.6.0+dev) - // Content: - // type: "AUTHENTICATED" - // answer: hex!( - // "aea26853c5d2b206eb3fc532d035164f1b16dea5268b32a3d068fd998853e85a67285b0f81c2045b" - // "e19b7be1c3baad937c5426d68e030ddb46434f2e4d0a560d82a6616e73776572c430bbf9777bdc47" - // "9d6c3d6a77b93aa26370f88ee51f81f3983a074ab32fbfcd33ef6a1b6e3f2e9718e227d0b59e8a74" - // "9078a474797065ad7369676e65645f616e73776572" - // ) - // client_api_version: [2, 5] - // device_id: "alice@dev1" - // handshake: "answer" - // organization_id: "CoolOrg" - // rvk: hex!("be2976732cec8ca94eedcf0aafd413cd159363e0fadc9e68572c77a1e17d9bbd") - let answer_req = hex!( - "87a6616e73776572c48daea26853c5d2b206eb3fc532d035164f1b16dea5268b32a3d068fd" - "998853e85a67285b0f81c2045be19b7be1c3baad937c5426d68e030ddb46434f2e4d0a560d" - "82a6616e73776572c430bbf9777bdc479d6c3d6a77b93aa26370f88ee51f81f3983a074ab3" - "2fbfcd33ef6a1b6e3f2e9718e227d0b59e8a749078a474797065ad7369676e65645f616e73" - "776572b2636c69656e745f6170695f76657273696f6e920205a96465766963655f6964aa61" - "6c6963654064657631a968616e647368616b65a6616e73776572af6f7267616e697a617469" - "6f6e5f6964a7436f6f6c4f7267a372766bc420be2976732cec8ca94eedcf0aafd413cd1593" - "63e0fadc9e68572c77a1e17d9bbda474797065ad41555448454e54494341544544" - ); - - let sh = sh.process_answer_req(&answer_req).unwrap(); - - assert_eq!( - sh.data, - Answer::Authenticated { - answer: hex!( - "aea26853c5d2b206eb3fc532d035164f1b16dea5268b32a3d068fd998853e85a67285b0f81c2045b" - "e19b7be1c3baad937c5426d68e030ddb46434f2e4d0a560d82a6616e73776572c430bbf9777bdc47" - "9d6c3d6a77b93aa26370f88ee51f81f3983a074ab32fbfcd33ef6a1b6e3f2e9718e227d0b59e8a74" - "9078a474797065ad7369676e65645f616e73776572" - ) - .to_vec(), - client_api_version: API_V2_VERSION, - organization_id: alice.organization_id().to_owned(), - device_id: alice.device_id.to_owned(), - rvk: Box::new(alice.root_verify_key().to_owned()), - } - ); - - let sh = sh.build_result_req(Some(alice.verify_key())).unwrap(); - - assert_eq!(sh.client_api_version, API_V2_VERSION); -} - -#[parsec_test] -fn test_good_authenticated_handshake_client(alice: &Device) { - // Generated from Python implementation (Parsec v2.6.0+dev) - // Content: - // backend_timestamp: ext(1, 1647884434.47775) - // ballpark_client_early_offset: 50.0 - // ballpark_client_late_offset: 70.0 - // challenge: hex!( - // "58f7ec2bb24b81a57feee1bad250726a2f7588a3cdd0617a206687adf8fb1274f34b0aebf9fd27a5" - // "d29f56dce902ddcd" - // ) - // handshake: "challenge" - // supported_api_versions: [[2, 5], [1, 3] - let challenge_req = hex!( - "86b16261636b656e645f74696d657374616d70d70141d88e2e249e9375bc62616c6c706172" - "6b5f636c69656e745f6561726c795f6f6666736574cb4049000000000000bb62616c6c7061" - "726b5f636c69656e745f6c6174655f6f6666736574cb4051800000000000a96368616c6c65" - "6e6765c43058f7ec2bb24b81a57feee1bad250726a2f7588a3cdd0617a206687adf8fb1274" - "f34b0aebf9fd27a5d29f56dce902ddcda968616e647368616b65a96368616c6c656e6765b6" - "737570706f727465645f6170695f76657273696f6e7392920205920103" - ); - - let ch = AuthenticatedClientHandshakeStalled::new( - alice.organization_id().clone(), - alice.device_id.clone(), - alice.signing_key.clone(), - alice.root_verify_key().clone(), - "2022-03-21T17:40:34.477750Z".parse().unwrap(), - ); - - ch.process_challenge_req(&challenge_req).unwrap(); -} - -#[parsec_test] -fn test_good_authenticated_handshake(alice: &Device, timestamp: DateTime) { - let t1 = timestamp; - let t2 = t1.add_us(1); - let sh = ServerHandshakeStalled::default() - .build_challenge_req(t1) - .unwrap(); - - let ch = AuthenticatedClientHandshakeStalled::new( - alice.organization_id().clone(), - alice.device_id.clone(), - alice.signing_key.clone(), - alice.root_verify_key().clone(), - t2, - ) - .process_challenge_req(&sh.raw) - .unwrap(); - - let sh = sh.process_answer_req(&ch.raw).unwrap(); - - if let Answer::Authenticated { - client_api_version, - organization_id, - device_id, - rvk, - .. - } = &sh.data - { - assert_eq!(*client_api_version, API_V2_VERSION); - assert_eq!(organization_id, alice.organization_id()); - assert_eq!(*device_id, alice.device_id); - assert_eq!(**rvk, *alice.root_verify_key()); - } else { - panic!("unexpected value `sh.data`") - } - - let sh = sh.build_result_req(Some(alice.verify_key())).unwrap(); - - assert_eq!(sh.client_api_version, API_V2_VERSION); -} - -#[cfg(feature = "test")] -#[parsec_test] -#[case::user(( - // Generated from Python implementation (Parsec v2.6.0+dev) - // Content: - // type: "INVITED" - // client_api_version: [2, 5] - // handshake: "answer" - // invitation_type: "USER" - // organization_id: "Org" - // token: ext(2, hex!("9931e631856a44709c825d7f7d339197") - &hex!( - "86b2636c69656e745f6170695f76657273696f6e920205a968616e647368616b65a6616e73" - "776572af696e7669746174696f6e5f74797065a455534552af6f7267616e697a6174696f6e" - "5f6964a34f7267a5746f6b656ed8029931e631856a44709c825d7f7d339197a474797065a7" - "494e5649544544" - )[..], - InvitationType::User, - "9931e631856a44709c825d7f7d339197".parse().unwrap(), - hex!( - "496749c018fb2d3a6cf52befaab622d9f0c377e6f5be7a5fe9c40dab2845d3bd135d3e589212f3c9" - "f718bf26ca792616" - ) -))] -#[case::device(( - // Generated from Python implementation (Parsec v2.6.0+dev) - // Content: - // type: "INVITED" - // client_api_version: [2, 5] - // handshake: "answer" - // invitation_type: "DEVICE" - // organization_id: "Org" - // token: ext(2, hex!("4e12636f08c840c4bb09404e1e696b09")) - &hex!( - "86b2636c69656e745f6170695f76657273696f6e920205a968616e647368616b65a6616e73" - "776572af696e7669746174696f6e5f74797065a6444556494345af6f7267616e697a617469" - "6f6e5f6964a34f7267a5746f6b656ed8024e12636f08c840c4bb09404e1e696b09a4747970" - "65a7494e5649544544" - )[..], - InvitationType::Device, - "4e12636f08c840c4bb09404e1e696b09".parse().unwrap(), - hex!( - "0d5db6bdf55ea3e54311a2a4608dcaa5af942de37ac201c3c0eb32117dd4941feadde89aac57fa3f" - "5ba7736fbfd11d75" - ) -))] -fn test_good_invited_handshake_server( - timestamp: DateTime, - #[case] input: ( - &[u8], - InvitationType, - InvitationToken, - [u8; HANDSHAKE_CHALLENGE_SIZE], - ), -) { - let _organization_id = OrganizationID::from_str("Org").unwrap(); - let (answer_req, _invitation_type, _token, challenge) = input; - - let sh = ServerHandshakeStalled::default() - .build_challenge_req_with_challenge(challenge, timestamp) - .unwrap(); - - let sh = sh.process_answer_req(&answer_req).unwrap(); - - if let Answer::Invited { - client_api_version, - organization_id, - invitation_type, - token, - .. - } = &sh.data - { - assert_eq!(*client_api_version, API_V2_VERSION); - assert_eq!(*organization_id, _organization_id); - assert_eq!(*invitation_type, _invitation_type); - assert_eq!(*token, _token); - } else { - assert!(false) - } - - let sh = sh.build_result_req(None).unwrap(); - - assert_eq!(sh.client_api_version, API_V2_VERSION); -} - -#[parsec_test] -#[case::user(( - // Generated from Python implementation (Parsec v2.6.0+dev) - // Content: - // backend_timestamp: ext(1, 1647885016.472496) - // ballpark_client_early_offset: 50.0 - // ballpark_client_late_offset: 70.0 - // challenge: hex!( - // "496749c018fb2d3a6cf52befaab622d9f0c377e6f5be7a5fe9c40dab2845d3bd135d3e589212f3c9" - // "f718bf26ca792616" - // ) - // handshake: "challenge" - // supported_api_versions: [[2, 5], [1, 3]] - &hex!( - "86b16261636b656e645f74696d657374616d70d70141d88e2eb61e3d60bc62616c6c706172" - "6b5f636c69656e745f6561726c795f6f6666736574cb4049000000000000bb62616c6c7061" - "726b5f636c69656e745f6c6174655f6f6666736574cb4051800000000000a96368616c6c65" - "6e6765c430496749c018fb2d3a6cf52befaab622d9f0c377e6f5be7a5fe9c40dab2845d3bd" - "135d3e589212f3c9f718bf26ca792616a968616e647368616b65a96368616c6c656e6765b6" - "737570706f727465645f6170695f76657273696f6e7392920205920103" - )[..], - InvitationType::User, - InvitationToken::from_hex("9931e631856a44709c825d7f7d339197").unwrap(), -))] -#[case::device(( - // Generated from Python implementation (Parsec v2.6.0+dev) - // Content: - // backend_timestamp: ext(1, 1647885016.474222) - // ballpark_client_early_offset: 50.0 - // ballpark_client_late_offset: 70.0 - // challenge: hex!( - // "0d5db6bdf55ea3e54311a2a4608dcaa5af942de37ac201c3c0eb32117dd4941feadde89aac57fa3f" - // "5ba7736fbfd11d75" - // ) - // handshake: "challenge" - // supported_api_versions: [[2, 5], [1, 3]] - &hex!( - "86b16261636b656e645f74696d657374616d70d70141d88e2eb61e59a7bc62616c6c706172" - "6b5f636c69656e745f6561726c795f6f6666736574cb4049000000000000bb62616c6c7061" - "726b5f636c69656e745f6c6174655f6f6666736574cb4051800000000000a96368616c6c65" - "6e6765c4300d5db6bdf55ea3e54311a2a4608dcaa5af942de37ac201c3c0eb32117dd4941f" - "eadde89aac57fa3f5ba7736fbfd11d75a968616e647368616b65a96368616c6c656e6765b6" - "737570706f727465645f6170695f76657273696f6e7392920205920103" - )[..], - InvitationType::Device, - InvitationToken::from_hex("4e12636f08c840c4bb09404e1e696b09").unwrap(), -))] -fn test_good_invited_handshake_client(#[case] input: (&[u8], InvitationType, InvitationToken)) { - let organization_id = OrganizationID::from_str("Org").unwrap(); - let (challenge_req, invitation_type, token) = input; - - let ch = InvitedClientHandshakeStalled::new( - organization_id, - invitation_type, - token, - "2022-03-21T17:50:16.474222Z".parse().unwrap(), - ); - - ch.process_challenge_req(challenge_req).unwrap(); -} - -#[parsec_test] -#[case::user(InvitationType::User)] -#[case::device(InvitationType::Device)] -fn test_good_invited_handshake(timestamp: DateTime, #[case] _invitation_type: InvitationType) { - let t1 = timestamp; - let t2 = t1.add_us(1); - let _organization_id = OrganizationID::default(); - let _token = InvitationToken::default(); - - let sh = ServerHandshakeStalled::default() - .build_challenge_req(t1) - .unwrap(); - - let ch = - InvitedClientHandshakeStalled::new(_organization_id.clone(), _invitation_type, _token, t2) - .process_challenge_req(&sh.raw) - .unwrap(); - - let sh = sh.process_answer_req(&ch.raw).unwrap(); - - if let Answer::Invited { - client_api_version, - organization_id, - invitation_type, - token, - .. - } = &sh.data - { - assert_eq!(*client_api_version, API_V2_VERSION); - assert_eq!(*organization_id, _organization_id); - assert_eq!(*invitation_type, _invitation_type); - assert_eq!(*token, _token); - } else { - panic!("unexpected value for `sh.data`") - } - - let sh = sh.build_result_req(None).unwrap(); - - assert_eq!(sh.client_api_version, API_V2_VERSION); -} - -// 1) Server build challenge (nothing more to test...) - -// 2) Client process challenge - -#[parsec_test] -#[case(json!({}))] -#[case(json!({ - "handshake": "foo", - "challenge": b"58f7ec2bb24b81a57feee1bad250726a2f7588a3cdd0617a206687adf8fb1274f34b0aebf9fd27a5d29f56dce902ddcd".to_vec(), - "supported_api_versions": [API_V2_VERSION], -}))] -#[case(json!({ - "handshake": "challenge", - "challenge": b"58f7ec2bb24b81a57feee1bad250726a2f7588a3cdd0617a206687adf8fb1274f34b0aebf9fd27a5d29f56dce902ddcd".to_vec() -}))] -#[case(json!({ - "challenge": b"58f7ec2bb24b81a57feee1bad250726a2f7588a3cdd0617a206687adf8fb1274f34b0aebf9fd27a5d29f56dce902ddcd".to_vec() -}))] -#[case(json!({ - "challenge": b"58f7ec2bb24b81a57feee1bad250726a2f7588a3cdd0617a206687adf8fb1274f34b0aebf9fd27a5d29f56dce902ddcd".to_vec(), - "supported_api_versions": [API_V2_VERSION] -}))] -#[case(json!({ - "handshake": "challenge", -}))] -#[case(json!({ - "handshake": "challenge", - "supported_api_versions": [API_V2_VERSION] -}))] -#[case(json!({ - "handshake": "challenge", - "challenge": 42, - "supported_api_versions": [API_V2_VERSION] -}))] -#[case(json!({ - "handshake": "challenge", - "challenge": b"58f7ec2bb24b81a57feee1bad250726a2f7588a3cdd0617a206687adf8fb1274f34b0aebf9fd27a5d29f56dce902ddcd".to_vec() -}))] -#[case(json!({ - "handshake": "challenge", - "challenge": b"58f7ec2bb24b81a57feee1bad250726a2f7588a3cdd0617a206687adf8fb1274f34b0aebf9fd27a5d29f56dce902ddcd".to_vec(), - "supported_api_versions": "invalid" -}))] -fn test_process_challenge_req_bad_format(alice: &Device, timestamp: DateTime, #[case] req: Value) { - let req = rmp_serde::to_vec_named(&req).unwrap(); - - let err = AuthenticatedClientHandshakeStalled::new( - alice.organization_id().clone(), - alice.device_id.clone(), - alice.signing_key.clone(), - alice.root_verify_key().clone(), - timestamp, - ) - .process_challenge_req(&req) - .unwrap_err(); - - assert!(matches!(err, HandshakeError::InvalidMessage(_))); -} - -// 2-b) Client check API version - -#[parsec_test] -#[case((ApiVersion { version: 2, revision: 22}, ApiVersion { version: 1, revision: 0 }, false))] -#[case((ApiVersion { version: 2, revision: 22}, ApiVersion { version: 1, revision: 111 }, false))] -#[case((ApiVersion { version: 2, revision: 22}, ApiVersion { version: 2, revision: 0 }, true))] -#[case((ApiVersion { version: 2, revision: 22}, ApiVersion { version: 2, revision: 22 }, true))] -#[case((ApiVersion { version: 2, revision: 22}, ApiVersion { version: 2, revision: 222 }, true))] -#[case((ApiVersion { version: 2, revision: 22}, ApiVersion { version: 3, revision: 0 }, false))] -#[case((ApiVersion { version: 2, revision: 22}, ApiVersion { version: 3, revision: 33 }, false))] -#[case((ApiVersion { version: 2, revision: 22}, ApiVersion { version: 3, revision: 333 }, false))] -fn test_process_challenge_req_good_api_version( - alice: &Device, - timestamp: DateTime, - #[case] input: (ApiVersion, ApiVersion, bool), -) { - let (client_version, backend_version, valid) = input; - let t1 = timestamp; - let t2 = t1.add_us(1); - - let req = Handshake::Challenge { - challenge: hex!("58f7ec2bb24b81a57feee1bad250726a2f7588a3cdd0617a206687adf8fb1274f34b0aebf9fd27a5d29f56dce902ddcd"), - supported_api_versions: vec![backend_version], - backend_timestamp: Some(t1), - ballpark_client_early_offset: Some(BALLPARK_CLIENT_EARLY_OFFSET), - ballpark_client_late_offset: Some(BALLPARK_CLIENT_LATE_OFFSET), - } - .dump() - .unwrap(); - - let mut ch = AuthenticatedClientHandshakeStalled::new( - alice.organization_id().clone(), - alice.device_id.clone(), - alice.signing_key.clone(), - alice.root_verify_key().clone(), - t2, - ); - - ch.supported_api_versions = vec![client_version]; - - if !valid { - // Invalid versioning - let err = ch.process_challenge_req(&req).unwrap_err(); - match err { - HandshakeError::APIVersion { - client_versions, - backend_versions, - } => { - assert_eq!(client_versions, vec![client_version]); - assert_eq!(backend_versions, vec![backend_version]); - } - _ => panic!("unexpected value err `{err}`"), - } - } else { - // Valid versioning - let ch = ch.process_challenge_req(&req).unwrap(); - assert_eq!(ch.supported_api_versions, vec![backend_version]); - assert_eq!(ch.backend_api_version, backend_version); - assert_eq!(ch.client_api_version, client_version); - } -} - -#[parsec_test] -#[case(( - vec![ApiVersion { version: 2, revision: 22 }, ApiVersion { version: 3, revision: 33 }], - vec![ApiVersion { version: 0, revision: 000 }, ApiVersion { version: 1, revision: 111 }], - None, - None, -))] -#[case(( - vec![ApiVersion { version: 2, revision: 22 }, ApiVersion { version: 3, revision: 33 }], - vec![ApiVersion { version: 1, revision: 111 }, ApiVersion { version: 2, revision: 222 }], - Some(ApiVersion { version: 2, revision: 22 }), - Some(ApiVersion { version: 2, revision: 222 }), -))] -#[case(( - vec![ApiVersion { version: 2, revision: 22 }, ApiVersion { version: 3, revision: 33 }], - vec![ApiVersion { version: 2, revision: 222 }, ApiVersion { version: 3, revision: 333 }], - Some(ApiVersion { version: 3, revision: 33 }), - Some(ApiVersion { version: 3, revision: 333 }), -))] -#[case(( - vec![ApiVersion { version: 2, revision: 22 }, ApiVersion { version: 3, revision: 33 }], - vec![ApiVersion { version: 3, revision: 333 }, ApiVersion { version: 4, revision: 444 }], - Some(ApiVersion { version: 3, revision: 33 }), - Some(ApiVersion { version: 3, revision: 333 }), -))] -#[case(( - vec![ApiVersion { version: 2, revision: 22 }, ApiVersion { version: 3, revision: 33 }], - vec![ApiVersion { version: 4, revision: 444 }, ApiVersion { version: 5, revision: 555 }], - None, - None, -))] -#[case(( - vec![ApiVersion { version: 2, revision: 22 }, ApiVersion { version: 4, revision: 44 }], - vec![ApiVersion { version: 1, revision: 111 }, ApiVersion { version: 2, revision: 222 }], - Some(ApiVersion { version: 2, revision: 22 }), - Some(ApiVersion { version: 2, revision: 222 }), -))] -#[case(( - vec![ApiVersion { version: 2, revision: 22 }, ApiVersion { version: 4, revision: 44 }], - vec![ApiVersion { version: 1, revision: 111 }, ApiVersion { version: 3, revision: 333 }], - None, - None, -))] -#[case(( - vec![ApiVersion { version: 2, revision: 22 }, ApiVersion { version: 4, revision: 44 }], - vec![ApiVersion { version: 2, revision: 222 }, ApiVersion { version: 3, revision: 333 }], - Some(ApiVersion { version: 2, revision: 22 }), - Some(ApiVersion { version: 2, revision: 222 }), -))] -#[case(( - vec![ApiVersion { version: 2, revision: 22 }, ApiVersion { version: 4, revision: 44 }], - vec![ApiVersion { version: 2, revision: 222 }, ApiVersion { version: 4, revision: 444 }], - Some(ApiVersion { version: 4, revision: 44 }), - Some(ApiVersion { version: 4, revision: 444 }), -))] -#[case(( - vec![ApiVersion { version: 2, revision: 22 }, ApiVersion { version: 4, revision: 44 }], - vec![ApiVersion { version: 3, revision: 333 }, ApiVersion { version: 4, revision: 444 }], - Some(ApiVersion { version: 4, revision: 44 }), - Some(ApiVersion { version: 4, revision: 444 }), -))] -#[case(( - vec![ApiVersion { version: 2, revision: 22 }, ApiVersion { version: 4, revision: 44 }], - vec![ApiVersion { version: 3, revision: 333 }, ApiVersion { version: 5, revision: 555 }], - None, - None, -))] -#[case(( - vec![ApiVersion { version: 2, revision: 22 }, ApiVersion { version: 4, revision: 44 }], - vec![ApiVersion { version: 4, revision: 444 }, ApiVersion { version: 5, revision: 555 }], - Some(ApiVersion { version: 4, revision: 44 }), - Some(ApiVersion { version: 4, revision: 444 }), -))] -#[case(( - vec![ApiVersion { version: 2, revision: 22 }, ApiVersion { version: 4, revision: 44 }], - vec![ApiVersion { version: 4, revision: 444 }, ApiVersion { version: 6, revision: 666 }], - Some(ApiVersion { version: 4, revision: 44 }), - Some(ApiVersion { version: 4, revision: 444 }), -))] -#[case(( - vec![ApiVersion { version: 2, revision: 22 }, ApiVersion { version: 4, revision: 44 }], - vec![ApiVersion { version: 5, revision: 555 }, ApiVersion { version: 6, revision: 666 }], - None, - None, -))] -fn test_process_challenge_req_good_multiple_api_version( - alice: &Device, - timestamp: DateTime, - #[case] input: ( - Vec, - Vec, - Option, - Option, - ), -) { - let (_client_versions, _backend_versions, expected_client_version, expected_backend_version) = - input; - let t1 = timestamp; - let t2 = t1.add_us(1); - - let req = Handshake::Challenge { - challenge: hex!("58f7ec2bb24b81a57feee1bad250726a2f7588a3cdd0617a206687adf8fb1274f34b0aebf9fd27a5d29f56dce902ddcd"), - supported_api_versions: _backend_versions.clone(), - backend_timestamp: Some(t1), - ballpark_client_early_offset: Some(BALLPARK_CLIENT_EARLY_OFFSET), - ballpark_client_late_offset: Some(BALLPARK_CLIENT_LATE_OFFSET), - } - .dump() - .unwrap(); - - let mut ch = AuthenticatedClientHandshakeStalled::new( - alice.organization_id().clone(), - alice.device_id.clone(), - alice.signing_key.clone(), - alice.root_verify_key().clone(), - t2, - ); - - ch.supported_api_versions = _client_versions.clone(); - - if expected_client_version.is_none() { - // Invalid versioning - let err = ch.process_challenge_req(&req).unwrap_err(); - match err { - HandshakeError::APIVersion { - client_versions, - backend_versions, - } => { - assert_eq!(client_versions, _client_versions); - assert_eq!(backend_versions, _backend_versions); - } - _ => panic!("unexpected value err `{err}`"), - } - } else { - // Valid versioning - let ch = ch.process_challenge_req(&req).unwrap(); - assert_eq!(ch.supported_api_versions, _backend_versions); - assert_eq!(Some(ch.backend_api_version), expected_backend_version); - assert_eq!(Some(ch.client_api_version), expected_client_version); - } -} - -// 3) Server process answer - -#[parsec_test] -#[case(json!({}))] -#[case(json!({ - "handshake": "answer", - "type": "dummy", // Invalid type -}))] -// Authenticated answer -#[case(json!({ - "handshake": "answer", - "type": "AUTHENTICATED", - "organization_id": "", - "device_id": "", - // Missing rvk - "answer": b"good answer", -}))] -#[case(json!({ - "handshake": "answer", - "type": "AUTHENTICATED", - "organization_id": "", - // Missing device_id - "rvk": "", - "answer": b"good answer", -}))] -#[case(json!({ - "handshake": "answer", - "type": "AUTHENTICATED", - "organization_id": "", - "device_id": "", - "rvk": "", - // Missing answer -}))] -#[case(json!({ - "handshake": "answer", - "type": "AUTHENTICATED", - "organization_id": "", - "device_id": "", - "rvk": "", - "answer": 42, // Bad type -}))] -#[case(json!({ - "handshake": "answer", - "type": "AUTHENTICATED", - "organization_id": "", - "device_id": "dummy", // Invalid DeviceID - "rvk": "", - "answer": b"good answer", -}))] -#[case(json!({ - "handshake": "answer", - "type": "AUTHENTICATED", - "organization_id": "", - "device_id": "", - "rvk": b"dummy", // Invalid VerifyKey - "answer": b"good answer", -}))] -// Invited answer -#[case(json!({ - "handshake": "answer", - "type": "INVITED", - "invitation_type": "USER", - "organization_id": "d@mmy", // Invalid OrganizationID - "token": "", -}))] -#[case(json!({ - "handshake": "answer", - "type": "INVITED", - "invitation_type": "dummy", // Invalid invitation_type - "organization_id": "", - "token": "", -}))] -#[case(json!({ - "handshake": "answer", - "type": "INVITED", - "invitation_type": "USER", - "organization_id": "", - "token": "abc123", // Invalid token type -}))] -fn test_process_answer_req_bad_format(alice: &Device, timestamp: DateTime, #[case] mut req: Value) { - for (key, good_value) in [ - ("organization_id", json!(alice.organization_id())), - ("device_id", json!(alice.device_id)), - ("rvk", json!(alice.root_verify_key())), - ("token", json!(&InvitationToken::default())), - ] { - if let Some("") = req.get(key).and_then(|v| v.as_str()) { - req[key] = good_value - } - } - req["client_api_version"] = json!(API_V2_VERSION); - let req = &rmp_serde::to_vec_named(&req).unwrap(); - let err = ServerHandshakeStalled::default() - .build_challenge_req(timestamp) - .unwrap() - .process_answer_req(req) - .unwrap_err(); - - assert!(matches!(err, HandshakeError::InvalidMessage(_))); -} - -// 4) Server build result - -#[parsec_test] -fn test_build_result_req_bad_key(alice: &Device, bob: &Device, timestamp: DateTime) { - let sh = ServerHandshakeStalled::default() - .build_challenge_req(timestamp) - .unwrap(); - - let answer = Handshake::Answer(Answer::Authenticated { - client_api_version: API_V2_VERSION, - organization_id: alice.organization_id().clone(), - device_id: alice.device_id.clone(), - rvk: Box::new(alice.root_verify_key().clone()), - answer: alice.signing_key.sign( - &SignedAnswer { - answer: sh.challenge, - } - .dump() - .unwrap(), - ), - }); - - let err = sh - .process_answer_req(&answer.dump().unwrap()) - .unwrap() - .build_result_req(Some(bob.verify_key())) - .unwrap_err(); - - assert!(matches!(err, HandshakeError::FailedChallenge)); -} - -#[parsec_test] -fn test_build_result_req_bad_challenge(alice: &Device, timestamp: DateTime) { - let sh = ServerHandshakeStalled::default() - .build_challenge_req(timestamp) - .unwrap(); - - let mut challenge = sh.challenge; - challenge[0] = 0; - challenge[1] = 0; - - let answer = Handshake::Answer(Answer::Authenticated { - client_api_version: API_V2_VERSION, - organization_id: alice.organization_id().clone(), - device_id: alice.device_id.clone(), - rvk: Box::new(alice.root_verify_key().clone()), - answer: alice - .signing_key - .sign(&SignedAnswer { answer: challenge }.dump().unwrap()), - }); - - let err = sh - .process_answer_req(&answer.dump().unwrap()) - .unwrap() - .build_result_req(Some(alice.verify_key())) - .unwrap_err(); - - assert!(matches!(err, HandshakeError::FailedChallenge)); -} - -#[parsec_test] -#[case( - Box::new(|sh: ServerHandshakeAnswer| { - (sh.build_bad_protocol_result_req(None).unwrap(), HandshakeResult::BadProtocol) - }) -)] -#[case( - Box::new(|sh: ServerHandshakeAnswer| { - (sh.build_bad_identity_result_req(None).unwrap(), HandshakeResult::BadIdentity) - }) -)] -#[case( - Box::new(|sh: ServerHandshakeAnswer| { - (sh.build_organization_expired_result_req(None).unwrap(), HandshakeResult::OrganizationExpired) - }) -)] -#[case( - Box::new(|sh: ServerHandshakeAnswer| { - (sh.build_rvk_mismatch_result_req(None).unwrap(), HandshakeResult::RvkMismatch) - }) -)] -#[case( - Box::new(|sh: ServerHandshakeAnswer| { - (sh.build_revoked_device_result_req(None).unwrap(), HandshakeResult::RevokedDevice) - }) -)] -#[case( - Box::new(|sh: ServerHandshakeAnswer| { - (sh.build_bad_administration_token_result_req(None).unwrap(), HandshakeResult::BadAdminToken) - }) -)] -fn test_build_bad_outcomes( - alice: &Device, - timestamp: DateTime, - #[case] generate_method_and_expected: Box< - dyn FnOnce(ServerHandshakeAnswer) -> (ServerHandshakeResult, HandshakeResult), - >, -) { - let sh = ServerHandshakeStalled::default() - .build_challenge_req(timestamp) - .unwrap(); - - let answer = Handshake::Answer(Answer::Authenticated { - client_api_version: API_V2_VERSION, - organization_id: alice.organization_id().clone(), - device_id: alice.device_id.clone(), - rvk: Box::new(alice.root_verify_key().clone()), - answer: alice.signing_key.sign( - &SignedAnswer { - answer: sh.challenge, - } - .dump() - .unwrap(), - ), - }); - let sh = sh.process_answer_req(&answer.dump().unwrap()).unwrap(); - let (sh, expected) = generate_method_and_expected(sh); - if let Handshake::Result { result, .. } = Handshake::load(&sh.raw).unwrap() { - assert_eq!(result, expected); - } else { - panic!("unexpected value for `Handshake::load`"); - } -} - -// 5) Client process result - -#[parsec_test] -#[case(json!({}))] -#[case(json!({"handshake": "foo", "result": "ok"}))] -#[case(json!({"result": "ok"}))] -#[case(json!({"handshake": "result", "result": "error"}))] -fn test_process_result_req_bad_format(timestamp: DateTime, #[case] req: Value) { - let req = &rmp_serde::to_vec_named(&req).unwrap(); - - let err = InvitedClientHandshakeStalled::new( - OrganizationID::default(), - InvitationType::User, - InvitationToken::default(), - timestamp, - ) - .process_result_req(req) - .unwrap_err(); - - assert!(matches!(err, HandshakeError::InvalidMessage(_))); -} - -#[parsec_test] -#[case(("bad_identity", HandshakeError::BadIdentity))] -#[case(("organization_expired", HandshakeError::OrganizationExpired))] -#[case(("rvk_mismatch", HandshakeError::RVKMismatch))] -#[case(("revoked_device", HandshakeError::RevokedDevice))] -#[case(("bad_admin_token", HandshakeError::BadAdministrationToken))] -#[case(("dummy", HandshakeError::InvalidMessage("Deserialization failed")))] -fn test_process_result_req_bad_outcome( - timestamp: DateTime, - #[case] result_expected: (&str, HandshakeError), -) { - let (result, expected) = result_expected; - - let req = &rmp_serde::to_vec_named(&json!({ - "handshake": "result", "result": result - })) - .unwrap(); - - let err = InvitedClientHandshakeStalled::new( - OrganizationID::default(), - InvitationType::User, - InvitationToken::default(), - timestamp, - ) - .process_result_req(req) - .unwrap_err(); - - assert_eq!(err, expected) -} - -// Generated from Python implementation (Parsec v2.15.0+dev) -// Content: -// version: 2 -// revision: 15 -#[parsec_test] -fn serde_api_version() { - let expected = ApiVersion { - version: 2, - revision: 15, - }; - let bytes = hex!("92020f"); - - let loaded_version = ApiVersion::load(&bytes).unwrap(); - assert_eq!(loaded_version, expected); - - // Roundtrip test ... - let bytes2 = loaded_version.dump().unwrap(); - let loaded_version2 = ApiVersion::load(&bytes2).unwrap(); - assert_eq!(loaded_version2, expected); -} - -// TODO: test with revoked device -// TODO: test with user with all devices revoked From 19403df72862b2a393ffa5b1b0b849806fd48b3e Mon Sep 17 00:00:00 2001 From: TimeEngineer Date: Tue, 9 May 2023 12:42:34 +0200 Subject: [PATCH 2/2] Remove unused dependencies --- Cargo.lock | 2 -- oxidation/libparsec/crates/protocol/Cargo.toml | 2 -- 2 files changed, 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f397baea6f1..808c610de29 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1399,8 +1399,6 @@ dependencies = [ "libparsec_serialization_format", "libparsec_tests_fixtures", "libparsec_types", - "paste", - "rand 0.8.5", "rmp-serde", "serde", "serde_json", diff --git a/oxidation/libparsec/crates/protocol/Cargo.toml b/oxidation/libparsec/crates/protocol/Cargo.toml index fbdad045848..6b26e127b30 100644 --- a/oxidation/libparsec/crates/protocol/Cargo.toml +++ b/oxidation/libparsec/crates/protocol/Cargo.toml @@ -15,8 +15,6 @@ path = "tests/mod.rs" libparsec_types = { path = "../types" } libparsec_serialization_format = { path = "../serialization_format" } -paste = "1.0.12" -rand = "0.8.5" rmp-serde = "1.1.1" serde = { version = "1.0.147", features = ["derive"] } serde_with = "2.3.2"