diff --git a/backend/migrations/20231124135844_create_connection_packages_table.sql b/backend/migrations/20231124135844_create_connection_packages_table.sql new file mode 100644 index 00000000..bcd82237 --- /dev/null +++ b/backend/migrations/20231124135844_create_connection_packages_table.sql @@ -0,0 +1,15 @@ +-- SPDX-FileCopyrightText: 2023 Phoenix R&D GmbH +-- +-- SPDX-License-Identifier: AGPL-3.0-or-later + +-- migrations/{timestamp}_create_connection_packages_table.sql +-- Create ConnectionPackages Table +CREATE TABLE connection_packages( +id uuid NOT NULL, +PRIMARY KEY (id), +client_id uuid NOT NULL, +connection_package BYTEA NOT NULL, +FOREIGN KEY (client_id) REFERENCES as_client_records(client_id) ON DELETE CASCADE +); + +CREATE INDEX idx_connection_package_client_id ON connection_packages(client_id); \ No newline at end of file diff --git a/backend/src/auth_service/client_api/anonymous.rs b/backend/src/auth_service/client_api/anonymous.rs index c1ee2e3f..bad39d3a 100644 --- a/backend/src/auth_service/client_api/anonymous.rs +++ b/backend/src/auth_service/client_api/anonymous.rs @@ -13,7 +13,10 @@ use phnxtypes::{ }; use crate::auth_service::{ - client_record::ClientRecord, storage_provider_trait::AsStorageProvider, AuthService, + client_record::ClientRecord, + credentials::{intermediate_signing_key::IntermediateCredential, signing_key::Credential}, + storage_provider_trait::AsStorageProvider, + AuthService, }; impl AuthService { @@ -108,18 +111,25 @@ impl AuthService { Ok(()) } - pub(crate) async fn as_credentials( - storage_provider: &S, - params: AsCredentialsParams, + pub(crate) async fn as_credentials( + &self, + _params: AsCredentialsParams, ) -> Result { - let (as_credentials, as_intermediate_credentials, revoked_credentials) = storage_provider - .load_as_credentials() + let as_credentials = Credential::load_all(&self.db_pool).await.map_err(|e| { + tracing::error!("Error loading AS credentials: {:?}", e); + AsCredentialsError::StorageError + })?; + let as_intermediate_credentials = IntermediateCredential::load_all(&self.db_pool) .await - .map_err(|_| AsCredentialsError::StorageError)?; + .map_err(|e| { + tracing::error!("Error loading intermediate credentials: {:?}", e); + AsCredentialsError::StorageError + })?; Ok(AsCredentialsResponse { as_credentials, as_intermediate_credentials, - revoked_credentials, + // We don't support revocation yet + revoked_credentials: vec![], }) } } diff --git a/backend/src/auth_service/client_api/client.rs b/backend/src/auth_service/client_api/client.rs index ceb87d4f..57af4662 100644 --- a/backend/src/auth_service/client_api/client.rs +++ b/backend/src/auth_service/client_api/client.rs @@ -11,8 +11,9 @@ use phnxtypes::{ }, messages::{ client_as::{ - DeleteClientParamsTbs, DequeueMessagesParamsTbs, FinishClientAdditionParamsTbs, - InitClientAdditionResponse, InitiateClientAdditionParams, + ConnectionPackage, DeleteClientParamsTbs, DequeueMessagesParamsTbs, + FinishClientAdditionParamsTbs, InitClientAdditionResponse, + InitiateClientAdditionParams, }, client_qs::DequeueMessagesResponse, }, @@ -21,8 +22,12 @@ use phnxtypes::{ use tls_codec::Serialize; use crate::auth_service::{ - client_record::ClientRecord, credentials::intermediate_signing_key::IntermediateSigningKey, - storage_provider_trait::AsStorageProvider, user_record::UserRecord, AuthService, + client_record::ClientRecord, + connection_package::StorableConnectionPackage, + credentials::intermediate_signing_key::{IntermediateCredential, IntermediateSigningKey}, + storage_provider_trait::AsStorageProvider, + user_record::UserRecord, + AuthService, }; impl AuthService { @@ -108,7 +113,7 @@ impl AuthService { // Sign the credential let client_credential: ClientCredential = client_credential_payload - .sign(&*signing_key) + .sign(&signing_key) .map_err(|_| InitClientAdditionError::LibraryError)?; // Store the client_credential in the ephemeral DB @@ -126,16 +131,15 @@ impl AuthService { Ok(response) } - pub(crate) async fn as_finish_client_addition( + pub(crate) async fn as_finish_client_addition( &self, - storage_provider: &S, params: FinishClientAdditionParamsTbs, ) -> Result<(), FinishClientAdditionError> { let FinishClientAdditionParamsTbs { client_id, queue_encryption_key, initial_ratchet_secret: initial_ratchet_key, - connection_package: connection_key_package, + connection_package, } = params; // Look up the initial client's ClientCredentialn the ephemeral DB based @@ -154,7 +158,7 @@ impl AuthService { .try_into() // Hiding the LibraryError here behind a StorageError .map_err(|_| FinishClientAdditionError::StorageError)?; - let client_record = ClientRecord::new_and_store( + ClientRecord::new_and_store( &mut connection, queue_encryption_key, ratchet_key, @@ -166,6 +170,33 @@ impl AuthService { FinishClientAdditionError::StorageError })?; + // Verify and store connection packages + let as_intermediate_credentials = IntermediateCredential::load_all(&self.db_pool) + .await + .map_err(|e| { + tracing::error!("Error loading intermediate credentials: {:?}", e); + FinishClientAdditionError::StorageError + })?; + let cp = connection_package; + let verifying_credential = as_intermediate_credentials + .iter() + .find(|aic| aic.fingerprint() == cp.client_credential_signer_fingerprint()) + .ok_or(FinishClientAdditionError::InvalidConnectionPackage)?; + let verified_connection_package: ConnectionPackage = cp + .verify(verifying_credential.verifying_key()) + .map_err(|_| FinishClientAdditionError::InvalidConnectionPackage)?; + + StorableConnectionPackage::store_multiple( + &mut connection, + vec![verified_connection_package].into_iter(), + &client_id, + ) + .await + .map_err(|e| { + tracing::error!("Error storing connection package: {:?}", e); + FinishClientAdditionError::StorageError + })?; + // Delete the entry in the ephemeral OPAQUE DB let mut client_login_states = self.ephemeral_client_logins.lock().await; client_login_states.remove(&client_id); @@ -173,9 +204,8 @@ impl AuthService { Ok(()) } - pub(crate) async fn as_delete_client( + pub(crate) async fn as_delete_client( &self, - storage_provider: &S, params: DeleteClientParamsTbs, ) -> Result<(), DeleteClientError> { let client_id = params.0; diff --git a/backend/src/auth_service/client_api/key_packages.rs b/backend/src/auth_service/client_api/key_packages.rs index b803a248..33e3e26d 100644 --- a/backend/src/auth_service/client_api/key_packages.rs +++ b/backend/src/auth_service/client_api/key_packages.rs @@ -10,10 +10,14 @@ use phnxtypes::{ }, }; -use crate::auth_service::{storage_provider_trait::AsStorageProvider, AuthService}; +use crate::auth_service::{ + credentials::intermediate_signing_key::IntermediateCredential, + storage_provider_trait::AsStorageProvider, AuthService, +}; impl AuthService { pub(crate) async fn as_publish_connection_packages( + &self, storage_provider: &S, params: AsPublishConnectionPackagesParamsTbs, ) -> Result<(), PublishConnectionPackageError> { @@ -22,10 +26,12 @@ impl AuthService { connection_packages, } = params; - let (_, as_intermediate_credentials, _) = storage_provider - .load_as_credentials() + let as_intermediate_credentials = IntermediateCredential::load_all(&self.db_pool) .await - .map_err(|_| PublishConnectionPackageError::StorageError)?; + .map_err(|e| { + tracing::error!("Error loading intermediate credentials: {:?}", e); + PublishConnectionPackageError::StorageError + })?; // TODO: Last resort key package let connection_packages = connection_packages diff --git a/backend/src/auth_service/client_api/user.rs b/backend/src/auth_service/client_api/user.rs index 445a2513..8e7d3b6b 100644 --- a/backend/src/auth_service/client_api/user.rs +++ b/backend/src/auth_service/client_api/user.rs @@ -20,8 +20,10 @@ use phnxtypes::{ use tls_codec::Serialize; use crate::auth_service::{ - client_record::ClientRecord, credentials::intermediate_signing_key::IntermediateSigningKey, - user_record::UserRecord, AsStorageProvider, AuthService, + client_record::ClientRecord, + credentials::intermediate_signing_key::{IntermediateCredential, IntermediateSigningKey}, + user_record::UserRecord, + AsStorageProvider, AuthService, }; impl AuthService { @@ -71,7 +73,7 @@ impl AuthService { // Sign the credential let client_credential: ClientCredential = client_payload - .sign(&*signing_key) + .sign(&signing_key) .map_err(|_| InitUserRegistrationError::LibraryError)?; // Store the client_credential in the ephemeral DB @@ -145,9 +147,10 @@ impl AuthService { })?; // Verify and store connection packages - let (_as_credentials, as_intermediate_credentials, _revoked_fingerprints) = - storage_provider.load_as_credentials().await.map_err(|e| { - tracing::error!("Storage provider error: {:?}", e); + let as_intermediate_credentials = IntermediateCredential::load_all(&self.db_pool) + .await + .map_err(|e| { + tracing::error!("Error loading intermediate credentials: {:?}", e); FinishUserRegistrationError::StorageError })?; let verified_connection_packages = connection_packages @@ -163,7 +166,6 @@ impl AuthService { .collect::, FinishUserRegistrationError>>()?; // Create the initial client entry - let ratchet_key = initial_ratchet_key .try_into() // Hiding the LibraryError here behind a StorageError @@ -172,7 +174,7 @@ impl AuthService { tracing::error!("Error acquiring connection: {:?}", e); FinishUserRegistrationError::StorageError })?; - let client_record = ClientRecord::new_and_store( + ClientRecord::new_and_store( &mut connection, queue_encryption_key, ratchet_key, @@ -204,7 +206,7 @@ impl AuthService { ) -> Result<(), DeleteUserError> { let DeleteUserParamsTbs { user_name, - client_id, + client_id: _, opaque_finish: _, } = params; diff --git a/backend/src/auth_service/client_record.rs b/backend/src/auth_service/client_record.rs index 641cd5cc..d3fb8a98 100644 --- a/backend/src/auth_service/client_record.rs +++ b/backend/src/auth_service/client_record.rs @@ -16,10 +16,10 @@ use crate::persistence::StorageError; #[derive(Debug, Clone)] pub(super) struct ClientRecord { - pub queue_encryption_key: RatchetEncryptionKey, - pub ratchet_key: QueueRatchet, - pub activity_time: TimeStamp, - pub credential: ClientCredential, + pub(super) queue_encryption_key: RatchetEncryptionKey, + pub(super) ratchet_key: QueueRatchet, + pub(super) activity_time: TimeStamp, + pub(super) credential: ClientCredential, } impl ClientRecord { @@ -38,9 +38,8 @@ impl ClientRecord { // Initialize the client's queue. let mut transaction = connection.begin().await?; - let queue_data = - ClientQueueData::new_and_store(record.client_id(), &mut transaction).await?; record.store(&mut transaction).await?; + ClientQueueData::new_and_store(record.client_id(), &mut transaction).await?; transaction.commit().await?; Ok(record) diff --git a/backend/src/auth_service/connection_package/mod.rs b/backend/src/auth_service/connection_package/mod.rs new file mode 100644 index 00000000..debfe3e9 --- /dev/null +++ b/backend/src/auth_service/connection_package/mod.rs @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: 2023 Phoenix R&D GmbH +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +use phnxtypes::messages::client_as::ConnectionPackage; +use serde::{Deserialize, Serialize}; + +mod persistence; + +#[derive(Serialize, Deserialize)] +pub(in crate::auth_service) enum StorableConnectionPackage { + V1(ConnectionPackage), +} + +impl From for ConnectionPackage { + fn from(connection_package: StorableConnectionPackage) -> Self { + match connection_package { + StorableConnectionPackage::V1(connection_package) => connection_package, + } + } +} + +impl From for StorableConnectionPackage { + fn from(connection_package: ConnectionPackage) -> Self { + StorableConnectionPackage::V1(connection_package) + } +} diff --git a/backend/src/auth_service/connection_package/persistence.rs b/backend/src/auth_service/connection_package/persistence.rs new file mode 100644 index 00000000..fefa9db1 --- /dev/null +++ b/backend/src/auth_service/connection_package/persistence.rs @@ -0,0 +1,156 @@ +// SPDX-FileCopyrightText: 2023 Phoenix R&D GmbH +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +use phnxtypes::{ + codec::PhnxCodec, identifiers::AsClientId, messages::client_as::ConnectionPackage, +}; +use sqlx::{postgres::PgArguments, Arguments, Connection, PgConnection}; +use uuid::Uuid; + +use crate::persistence::StorageError; + +use super::StorableConnectionPackage; + +impl StorableConnectionPackage { + pub(in crate::auth_service) async fn store_multiple( + connection: &mut PgConnection, + connection_packages: impl Iterator>, + client_id: &AsClientId, + ) -> Result<(), StorageError> { + let mut query_args = PgArguments::default(); + let mut query_string = String::from( + "INSERT INTO connection_packages (id, client_id, connection_package) VALUES", + ); + + for (i, connection_package) in connection_packages.enumerate() { + let connection_package: StorableConnectionPackage = connection_package.into(); + let id = Uuid::new_v4(); + let connection_package_bytes = PhnxCodec::to_vec(&connection_package)?; + + // Add values to the query arguments. None of these should throw an error. + let _ = query_args.add(id)?; + let _ = query_args.add(client_id.client_id())?; + let _ = query_args.add(connection_package_bytes)?; + + if i > 0 { + query_string.push(','); + } + + // Add placeholders for each value + query_string.push_str(&format!( + " (${}, ${}, ${})", + i * 3 + 1, + i * 3 + 2, + i * 3 + 3 + )); + } + + // Finalize the query string + query_string.push(';'); + + // Execute the query + sqlx::query_with(&query_string, query_args) + .execute(connection) + .await?; + + Ok(()) + } + + async fn load(connection: &mut PgConnection, client_id: Uuid) -> Result, StorageError> { + let mut transaction = connection.begin().await?; + + // TODO: Set the isolation level to SERIALIZABLE. This is necessary + // because we're counting the number of packages and then deleting one. + // We should do this once we're moving to a proper state-machine model + // for server storage and networking. + + // This is to ensure that counting and deletion happen atomically. If we + // don't do this, two concurrent queries might both count 2 and delete, + // leaving us with 0 packages. + //sqlx::query("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE") + // .execute(&mut *savepoint) + // .await?; + + let connection_package_bytes_record = sqlx::query!( + "WITH next_connection_package AS ( + SELECT id, connection_package + FROM connection_packages + WHERE client_id = $1 LIMIT 1 + ), + remaining_packages AS ( + SELECT COUNT(*) as count + FROM connection_packages + WHERE client_id = $1 + ), + deleted_package AS ( + DELETE FROM connection_packages + WHERE id = ( + SELECT id + FROM next_connection_package + ) + AND (SELECT count FROM remaining_packages) > 1 + RETURNING connection_package + ) + SELECT id, connection_package FROM next_connection_package", + client_id, + ) + .fetch_one(&mut *transaction) + .await?; + + transaction.commit().await?; + + Ok(connection_package_bytes_record.connection_package) + } + + /// TODO: Last resort key package + async fn client_connection_package( + connection: &mut PgConnection, + client_id: &AsClientId, + ) -> Result { + let connection_package_bytes = + Self::load_connection(connection, client_id.client_id()).await?; + + let connection_package = PhnxCodec::from_slice(&connection_package_bytes)?; + + Ok(connection_package) + } + + /// Return a connection package for each client of a user referenced by a + /// user name. + async fn load_user_connection_packages( + &self, + user_name: &QualifiedUserName, + ) -> Result, Self::StorageError> { + // Start the transaction + let mut transaction = self.pool.begin().await?; + + // Collect all client ids associated with that user. + let client_ids_record = sqlx::query!( + "SELECT client_id FROM as_client_records WHERE user_name = $1", + user_name.to_string(), + ) + .fetch_all(&mut *transaction) + .await?; + + // First fetch all connection package records from the DB. + let mut connection_packages_bytes = Vec::new(); + for client_id in client_ids_record { + let connection_package_bytes = + Self::load_connection_package_internal(&mut transaction, client_id.client_id) + .await?; + connection_packages_bytes.push(connection_package_bytes); + } + + // End the transaction. + transaction.commit().await?; + + // Deserialize the connection packages. + let connection_packages = connection_packages_bytes + .into_iter() + .map(|connection_package_bytes| PhnxCodec::from_slice(&connection_package_bytes)) + .collect::, _>>()?; + + Ok(connection_packages) + } +} diff --git a/backend/src/auth_service/credentials/intermediate_signing_key.rs b/backend/src/auth_service/credentials/intermediate_signing_key.rs index bc408276..10b511c4 100644 --- a/backend/src/auth_service/credentials/intermediate_signing_key.rs +++ b/backend/src/auth_service/credentials/intermediate_signing_key.rs @@ -6,7 +6,10 @@ use std::ops::Deref; use mls_assist::openmls::prelude::SignatureScheme; use phnxtypes::{ - credentials::{keys::AsIntermediateSigningKey, AsIntermediateCredentialCsr}, + credentials::{ + keys::AsIntermediateSigningKey, AsIntermediateCredential, AsIntermediateCredentialCsr, + CredentialFingerprint, + }, identifiers::Fqdn, }; use serde::{Deserialize, Serialize}; @@ -17,11 +20,28 @@ use crate::persistence::StorageError; use super::{signing_key::SigningKey, CredentialGenerationError}; #[derive(Serialize, Deserialize)] -#[serde(transparent)] -pub(in crate::auth_service) struct IntermediateSigningKey(AsIntermediateSigningKey); +pub(in crate::auth_service) enum IntermediateSigningKey { + V1(AsIntermediateSigningKey), +} + +impl From for AsIntermediateSigningKey { + fn from(signing_key: IntermediateSigningKey) -> Self { + match signing_key { + IntermediateSigningKey::V1(signing_key) => signing_key, + } + } +} -impl Deref for IntermediateSigningKey { - type Target = AsIntermediateSigningKey; +impl From for IntermediateSigningKey { + fn from(signing_key: AsIntermediateSigningKey) -> Self { + IntermediateSigningKey::V1(signing_key) + } +} + +pub(in crate::auth_service) struct IntermediateCredential(AsIntermediateCredential); + +impl Deref for IntermediateCredential { + type Target = AsIntermediateCredential; fn deref(&self) -> &Self::Target { &self.0 @@ -35,10 +55,7 @@ impl IntermediateSigningKey { signature_scheme: SignatureScheme, ) -> Result { // Start the transaction - let mut transaction = connection - .begin() - .await - .map_err(StorageError::DatabaseError)?; + let mut transaction = connection.begin().await.map_err(StorageError::from)?; // Load the currently active (root) signing key let signing_key = SigningKey::load(&mut *transaction) @@ -47,7 +64,7 @@ impl IntermediateSigningKey { // Generate an intermediate credential CSR and sign it let (csr, prelim_signing_key) = AsIntermediateCredentialCsr::new(signature_scheme, domain)?; - let as_intermediate_credential = csr.sign(&*signing_key, None).map_err(|e| { + let as_intermediate_credential = csr.sign(&signing_key, None).map_err(|e| { tracing::error!("Failed to sign intermediate credential: {:?}", e); CredentialGenerationError::SigningError })?; @@ -57,7 +74,7 @@ impl IntermediateSigningKey { as_intermediate_credential, ) .unwrap(); - let intermediate_signing_key = IntermediateSigningKey(as_intermediate_signing_key); + let intermediate_signing_key = IntermediateSigningKey::from(as_intermediate_signing_key); // Store the intermediate signing key intermediate_signing_key.store(&mut *transaction).await?; @@ -66,22 +83,29 @@ impl IntermediateSigningKey { intermediate_signing_key.activate(&mut *transaction).await?; // Commit the transaction - transaction - .commit() - .await - .map_err(StorageError::DatabaseError)?; + transaction.commit().await.map_err(StorageError::from)?; Ok(intermediate_signing_key) } + + fn fingerprint(&self) -> &CredentialFingerprint { + match self { + IntermediateSigningKey::V1(signing_key) => signing_key.credential().fingerprint(), + } + } } mod persistence { - use phnxtypes::codec::PhnxCodec; + use phnxtypes::{ + codec::PhnxCodec, + credentials::{keys::AsIntermediateSigningKey, AsIntermediateCredential}, + }; use sqlx::PgExecutor; + use uuid::Uuid; use crate::{auth_service::credentials::CredentialType, persistence::StorageError}; - use super::IntermediateSigningKey; + use super::{IntermediateCredential, IntermediateSigningKey}; impl IntermediateSigningKey { pub(super) async fn store( @@ -91,11 +115,12 @@ mod persistence { sqlx::query!( "INSERT INTO as_signing_keys - (cred_type, credential_fingerprint, signing_key, currently_active) + (id, cred_type, credential_fingerprint, signing_key, currently_active) VALUES - ($1, $2, $3, $4)", + ($1, $2, $3, $4, $5)", + Uuid::new_v4(), CredentialType::Intermediate as _, - self.0.credential().fingerprint().as_bytes(), + self.fingerprint().as_bytes(), PhnxCodec::to_vec(&self)?, false, ) @@ -106,12 +131,12 @@ mod persistence { pub(in crate::auth_service) async fn load( connection: impl PgExecutor<'_>, - ) -> Result, StorageError> { + ) -> Result, StorageError> { sqlx::query!("SELECT signing_key FROM as_signing_keys WHERE currently_active = true AND cred_type = 'intermediate'") .fetch_optional(connection) .await?.map(|record| { - let signing_key = PhnxCodec::from_slice(&record.signing_key)?; - Ok(IntermediateSigningKey(signing_key)) + let signing_key: IntermediateSigningKey = PhnxCodec::from_slice(&record.signing_key)?; + Ok(signing_key.into()) }).transpose() } @@ -126,11 +151,34 @@ mod persistence { ELSE false END WHERE cred_type = 'intermediate'", - self.0.credential().fingerprint().as_bytes(), + self.fingerprint().as_bytes(), ) .execute(connection) .await?; Ok(()) } } + + impl IntermediateCredential { + pub(in crate::auth_service) async fn load_all( + connection: impl PgExecutor<'_>, + ) -> Result, StorageError> { + let records = sqlx::query!( + "SELECT signing_key FROM as_signing_keys WHERE cred_type = $1", + CredentialType::Intermediate as _, + ) + .fetch_all(connection) + .await?; + let credentials = records + .into_iter() + .map(|record| { + let signing_key: IntermediateSigningKey = + PhnxCodec::from_slice(&record.signing_key)?; + let as_signing_key = AsIntermediateSigningKey::from(signing_key); + Ok(as_signing_key.credential().clone()) + }) + .collect::, StorageError>>()?; + Ok(credentials) + } + } } diff --git a/backend/src/auth_service/credentials/signing_key.rs b/backend/src/auth_service/credentials/signing_key.rs index c193baae..199022c6 100644 --- a/backend/src/auth_service/credentials/signing_key.rs +++ b/backend/src/auth_service/credentials/signing_key.rs @@ -6,7 +6,7 @@ use std::ops::Deref; use mls_assist::openmls::prelude::SignatureScheme; use phnxtypes::{ - credentials::{keys::AsSigningKey, AsCredential}, + credentials::{keys::AsSigningKey, AsCredential, CredentialFingerprint}, identifiers::Fqdn, }; use serde::{Deserialize, Serialize}; @@ -15,11 +15,28 @@ use sqlx::{Connection, PgConnection, PgExecutor}; use super::CredentialGenerationError; #[derive(Debug, Serialize, Deserialize)] -#[serde(transparent)] -pub(in crate::auth_service) struct SigningKey(AsSigningKey); +pub(in crate::auth_service) enum SigningKey { + V1(AsSigningKey), +} -impl Deref for SigningKey { - type Target = AsSigningKey; +impl From for AsSigningKey { + fn from(signing_key: SigningKey) -> Self { + match signing_key { + SigningKey::V1(signing_key) => signing_key, + } + } +} + +impl From for SigningKey { + fn from(signing_key: AsSigningKey) -> Self { + SigningKey::V1(signing_key) + } +} + +pub(in crate::auth_service) struct Credential(AsCredential); + +impl Deref for Credential { + type Target = AsCredential; fn deref(&self) -> &Self::Target { &self.0 @@ -33,17 +50,24 @@ impl SigningKey { scheme: SignatureScheme, ) -> Result { let (_, signing_key) = AsCredential::new(scheme, domain, None)?; - let signing_key = SigningKey(signing_key); + let signing_key = SigningKey::V1(signing_key); let mut transaction = connection.begin().await?; signing_key.store(&mut *transaction).await?; signing_key.activate(&mut *transaction).await?; transaction.commit().await?; Ok(signing_key) } + + fn fingerprint(&self) -> &CredentialFingerprint { + match self { + SigningKey::V1(signing_key) => signing_key.credential().fingerprint(), + } + } } mod persistence { use phnxtypes::codec::PhnxCodec; + use uuid::Uuid; use crate::{auth_service::credentials::CredentialType, persistence::StorageError}; @@ -57,11 +81,12 @@ mod persistence { sqlx::query!( "INSERT INTO as_signing_keys - (cred_type, credential_fingerprint, signing_key, currently_active) + (id, cred_type, credential_fingerprint, signing_key, currently_active) VALUES - ($1, $2, $3, $4)", + ($1, $2, $3, $4, $5)", + Uuid::new_v4(), CredentialType::As as _, - self.0.credential().fingerprint().as_bytes(), + self.fingerprint().as_bytes(), PhnxCodec::to_vec(&self)?, false, ) @@ -72,7 +97,7 @@ mod persistence { pub(in crate::auth_service) async fn load( connection: impl PgExecutor<'_>, - ) -> Result, StorageError> { + ) -> Result, StorageError> { sqlx::query!( "SELECT signing_key FROM as_signing_keys WHERE currently_active = true AND cred_type = $1", CredentialType::As as _ @@ -80,8 +105,8 @@ mod persistence { .fetch_optional(connection) .await? .map(|record| { - let signing_key = PhnxCodec::from_slice(&record.signing_key)?; - Ok(SigningKey(signing_key)) + let signing_key: SigningKey = PhnxCodec::from_slice(&record.signing_key)?; + Ok(signing_key.into()) }) .transpose() } @@ -97,11 +122,35 @@ mod persistence { ELSE false END WHERE currently_active = true OR credential_fingerprint = $1", - self.0.credential().fingerprint().as_bytes() + self.fingerprint().as_bytes() ) .execute(connection) .await?; Ok(()) } } + + impl Credential { + pub(in crate::auth_service) async fn load_all( + connection: impl PgExecutor<'_>, + ) -> Result, StorageError> { + let records = sqlx::query!( + "SELECT signing_key FROM as_signing_keys WHERE cred_type = $1", + CredentialType::As as _ + ) + .fetch_all(connection) + .await?; + + let credentials = records + .into_iter() + .map(|record| { + let signing_key: SigningKey = PhnxCodec::from_slice(&record.signing_key)?; + let as_signing_key = AsSigningKey::from(signing_key); + Ok(as_signing_key.credential().clone()) + }) + .collect::, StorageError>>()?; + + Ok(credentials) + } + } } diff --git a/backend/src/auth_service/mod.rs b/backend/src/auth_service/mod.rs index c1cecd7a..85331b74 100644 --- a/backend/src/auth_service/mod.rs +++ b/backend/src/auth_service/mod.rs @@ -2,8 +2,6 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later -#![allow(unused_variables)] - use std::{collections::HashMap, sync::Arc}; use credentials::{ @@ -36,6 +34,7 @@ use self::{storage_provider_trait::AsStorageProvider, verification::VerifiableCl pub mod client_api; mod client_record; +mod connection_package; mod credentials; pub mod devices; pub mod invitations; @@ -88,7 +87,7 @@ pub enum AuthServiceCreationError { impl> From for AuthServiceCreationError { fn from(e: T) -> Self { - Self::Storage(StorageError::DatabaseError(e.into())) + Self::Storage(StorageError::from(e.into())) } } @@ -167,7 +166,7 @@ impl AuthService { storage_provider: &Asp, message: VerifiableClientToAsMessage, ) -> Result { - let verified_params = self.verify(storage_provider, message).await?; + let verified_params = self.verify(message).await?; let response: AsProcessResponse = match verified_params { VerifiedAsRequestParams::Initiate2FaAuthentication(params) => self @@ -184,12 +183,11 @@ impl AuthService { AsProcessResponse::Ok } VerifiedAsRequestParams::FinishClientAddition(params) => { - self.as_finish_client_addition(storage_provider, params) - .await?; + self.as_finish_client_addition(params).await?; AsProcessResponse::Ok } VerifiedAsRequestParams::DeleteClient(params) => { - self.as_delete_client(storage_provider, params).await?; + self.as_delete_client(params).await?; AsProcessResponse::Ok } VerifiedAsRequestParams::DequeueMessages(params) => { @@ -198,7 +196,8 @@ impl AuthService { .map(AsProcessResponse::DequeueMessages)? } VerifiedAsRequestParams::PublishConnectionPackages(params) => { - AuthService::as_publish_connection_packages(storage_provider, params).await?; + self.as_publish_connection_packages(storage_provider, params) + .await?; AsProcessResponse::Ok } VerifiedAsRequestParams::ClientConnectionPackage(params) => { @@ -225,11 +224,10 @@ impl AuthService { .await .map(AsProcessResponse::UserClients)? } - VerifiedAsRequestParams::AsCredentials(params) => { - AuthService::as_credentials(storage_provider, params) - .await - .map(AsProcessResponse::AsCredentials)? - } + VerifiedAsRequestParams::AsCredentials(params) => self + .as_credentials(params) + .await + .map(AsProcessResponse::AsCredentials)?, VerifiedAsRequestParams::EnqueueMessage(params) => { self.as_enqueue_message(storage_provider, params).await?; AsProcessResponse::Ok diff --git a/backend/src/auth_service/storage_provider_trait.rs b/backend/src/auth_service/storage_provider_trait.rs index 6e87e503..1b31594b 100644 --- a/backend/src/auth_service/storage_provider_trait.rs +++ b/backend/src/auth_service/storage_provider_trait.rs @@ -7,9 +7,7 @@ use std::{error::Error, fmt::Debug}; use async_trait::async_trait; use opaque_ke::ServerSetup; use phnxtypes::{ - credentials::{ - AsCredential, AsIntermediateCredential, ClientCredential, CredentialFingerprint, - }, + credentials::ClientCredential, crypto::OpaqueCiphersuite, identifiers::{AsClientId, QualifiedUserName}, messages::{client_as::ConnectionPackage, QueueMessage}, @@ -85,19 +83,6 @@ pub trait AsStorageProvider: Sync + Send + 'static { number_of_messages: u64, ) -> Result<(Vec, u64), Self::ReadAndDeleteError>; - /// Load all currently active [`AsCredential`]s and - /// [`AsIntermediateCredential`]s. - async fn load_as_credentials( - &self, - ) -> Result< - ( - Vec, - Vec, - Vec, - ), - Self::LoadAsCredentialsError, - >; - /// Load the OPAQUE [`ServerSetup`]. async fn load_opaque_setup( &self, diff --git a/backend/src/auth_service/verification.rs b/backend/src/auth_service/verification.rs index cfde860e..ae7fb4a6 100644 --- a/backend/src/auth_service/verification.rs +++ b/backend/src/auth_service/verification.rs @@ -9,9 +9,7 @@ use phnxtypes::{ }; use tls_codec::TlsDeserializeBytes; -use super::{ - client_record::ClientRecord, AsStorageProvider, AuthService, TlsSize, VerifiedAsRequestParams, -}; +use super::{client_record::ClientRecord, AuthService, TlsSize, VerifiedAsRequestParams}; /// Wrapper struct around a message from a client to the AS. It does not /// implement the [`Verifiable`] trait, but instead is verified depending on the @@ -26,9 +24,8 @@ impl VerifiableClientToAsMessage { } impl AuthService { - pub(crate) async fn verify( + pub(crate) async fn verify( &self, - as_storage_provider: &Asp, message: VerifiableClientToAsMessage, ) -> Result { let parameters = match message.into_auth_method() { diff --git a/backend/src/persistence.rs b/backend/src/persistence.rs index 00b4f62a..4f2352b8 100644 --- a/backend/src/persistence.rs +++ b/backend/src/persistence.rs @@ -7,7 +7,27 @@ use thiserror::Error; #[derive(Debug, Error)] pub enum StorageError { #[error(transparent)] - DatabaseError(#[from] sqlx::Error), + DatabaseError(#[from] DatabaseError), #[error("Error deserializing column: {0}")] Serde(#[from] phnxtypes::codec::Error), } + +impl From for StorageError { + fn from(e: sqlx::Error) -> Self { + Self::DatabaseError(e.into()) + } +} + +impl From> for StorageError { + fn from(e: Box) -> Self { + Self::DatabaseError(e.into()) + } +} + +#[derive(Debug, Error)] +pub enum DatabaseError { + #[error(transparent)] + Sqlx(#[from] sqlx::Error), + #[error(transparent)] + Dynamic(#[from] Box), +} diff --git a/server/phnx.db b/server/phnx.db index 8840ca1a..8b12b54c 100644 Binary files a/server/phnx.db and b/server/phnx.db differ diff --git a/server/src/storage_provider/memory/auth_service.rs b/server/src/storage_provider/memory/auth_service.rs index 0e5a71c8..edeff60f 100644 --- a/server/src/storage_provider/memory/auth_service.rs +++ b/server/src/storage_provider/memory/auth_service.rs @@ -12,11 +12,7 @@ use mls_assist::openmls_traits::types::SignatureScheme; use opaque_ke::{rand::rngs::OsRng, ServerSetup}; use phnxbackend::auth_service::storage_provider_trait::AsStorageProvider; use phnxtypes::{ - credentials::{ - keys::{AsIntermediateSigningKey, AsSigningKey}, - AsCredential, AsIntermediateCredential, AsIntermediateCredentialCsr, ClientCredential, - CredentialFingerprint, - }, + credentials::ClientCredential, crypto::OpaqueCiphersuite, identifiers::{AsClientId, Fqdn, QualifiedUserName}, messages::{client_as::ConnectionPackage, QueueMessage}, @@ -29,8 +25,6 @@ use super::qs::QueueData; pub struct MemoryAsStorage { connection_packages: RwLock>>, queues: RwLock>, - as_intermediate_signing_key: RwLock, - as_signing_key: RwLock, opaque_server_setup: RwLock>, // No RwLock needed, as MemoryKeyStore is already thread-safe. privacy_pass_key_store: MemoryKeyStore, @@ -46,35 +40,16 @@ pub enum StorageInitError { impl MemoryAsStorage { pub fn new( - as_domain: Fqdn, - signature_scheme: SignatureScheme, + _as_domain: Fqdn, + _signature_scheme: SignatureScheme, ) -> Result { let mut rng = OsRng; let opaque_server_setup = RwLock::new(ServerSetup::::new(&mut rng)); - let (_credential, as_signing_key) = - AsCredential::new(signature_scheme, as_domain.clone(), None) - .map_err(|_| StorageInitError::CredentialGenerationError)?; - let (csr, prelim_signing_key) = - AsIntermediateCredentialCsr::new(signature_scheme, as_domain) - .map_err(|_| StorageInitError::CredentialGenerationError)?; - let as_intermediate_credential = csr - .sign(&as_signing_key, None) - .map_err(|_| StorageInitError::CredentialGenerationError)?; - let as_intermediate_signing_key = RwLock::new( - AsIntermediateSigningKey::from_prelim_key( - prelim_signing_key, - as_intermediate_credential, - ) - .map_err(|_| StorageInitError::CredentialGenerationError)?, - ); let privacy_pass_key_store = MemoryKeyStore::default(); - let as_signing_key = RwLock::new(as_signing_key); let storage_provider = Self { connection_packages: RwLock::new(HashMap::new()), queues: RwLock::new(HashMap::new()), - as_intermediate_signing_key, - as_signing_key, opaque_server_setup, privacy_pass_key_store, remaining_tokens: RwLock::new(HashMap::new()), @@ -284,31 +259,6 @@ impl AsStorageProvider for MemoryAsStorage { Ok((return_messages, queue.queue.len() as u64)) } - /// Load all currently active [`AsCredential`]s and - /// [`AsIntermediateCredential`]s. - async fn load_as_credentials( - &self, - ) -> Result< - ( - Vec, - Vec, - Vec, - ), - Self::LoadAsCredentialsError, - > { - let as_credentials = vec![self - .as_signing_key - .read() - .map_err(|_| AsStorageError::PoisonedLock) - .map(|key| key.credential().clone())?]; - let as_intermediate_credentials = vec![self - .as_intermediate_signing_key - .read() - .map_err(|_| AsStorageError::PoisonedLock) - .map(|key| key.credential().clone())?]; - Ok((as_credentials, as_intermediate_credentials, vec![])) - } - /// Load the OPAQUE [`ServerSetup`]. async fn load_opaque_setup( &self, diff --git a/server/src/storage_provider/postgres/auth_service.rs b/server/src/storage_provider/postgres/auth_service.rs index e6d16df0..e348c5ab 100644 --- a/server/src/storage_provider/postgres/auth_service.rs +++ b/server/src/storage_provider/postgres/auth_service.rs @@ -9,11 +9,7 @@ use opaque_ke::{rand::rngs::OsRng, ServerSetup}; use phnxbackend::auth_service::storage_provider_trait::AsStorageProvider; use phnxtypes::{ codec::PhnxCodec, - credentials::{ - keys::{AsIntermediateSigningKey, AsSigningKey}, - AsCredential, AsIntermediateCredential, AsIntermediateCredentialCsr, ClientCredential, - CredentialFingerprint, - }, + credentials::ClientCredential, crypto::OpaqueCiphersuite, identifiers::{AsClientId, Fqdn, QualifiedUserName}, messages::{client_as::ConnectionPackage, QueueMessage}, @@ -33,8 +29,8 @@ pub struct PostgresAsStorage { impl PostgresAsStorage { pub async fn new( - as_domain: Fqdn, - signature_scheme: SignatureScheme, + _as_domain: Fqdn, + _signature_scheme: SignatureScheme, settings: &DatabaseSettings, ) -> Result { let pool = connect_to_database(settings).await?; @@ -42,31 +38,6 @@ impl PostgresAsStorage { let provider = Self { pool }; // Check if the database has been initialized. - let (as_creds, _as_inter_creds, _) = provider.load_as_credentials().await?; - if as_creds.is_empty() { - let (as_signing_key, as_inter_signing_key) = - generate_fresh_credentials(as_domain, signature_scheme)?; - let _ = sqlx::query!( - r#"INSERT INTO as_signing_keys (id, cred_type, credential_fingerprint, signing_key, currently_active) VALUES ($1, $2, $3, $4, $5)"#, - Uuid::new_v4(), - CredentialType::As as _, - as_signing_key.credential().fingerprint().as_bytes(), - PhnxCodec::to_vec(&as_signing_key)?, - true, - ) - .execute(&provider.pool) - .await?; - let _ = sqlx::query!( - r#"INSERT INTO as_signing_keys (id, cred_type, credential_fingerprint, signing_key, currently_active) VALUES ($1, $2, $3, $4, $5)"#, - Uuid::new_v4(), - CredentialType::Intermediate as _, - as_inter_signing_key.credential().fingerprint().as_bytes(), - PhnxCodec::to_vec(&as_inter_signing_key)?, - true, - ) - .execute(&provider.pool) - .await?; - } if provider.load_opaque_setup().await.is_err() { let mut rng = OsRng; let opaque_setup = ServerSetup::::new(&mut rng); @@ -131,24 +102,6 @@ impl PostgresAsStorage { } } -pub(crate) fn generate_fresh_credentials( - as_domain: Fqdn, - signature_scheme: SignatureScheme, -) -> Result<(AsSigningKey, AsIntermediateSigningKey), CreateAsStorageError> { - let (_credential, as_signing_key) = - AsCredential::new(signature_scheme, as_domain.clone(), None) - .map_err(|_| CreateAsStorageError::CredentialGenerationError)?; - let (csr, prelim_signing_key) = AsIntermediateCredentialCsr::new(signature_scheme, as_domain) - .map_err(|_| CreateAsStorageError::CredentialGenerationError)?; - let as_intermediate_credential = csr - .sign(&as_signing_key, None) - .map_err(|_| CreateAsStorageError::CredentialGenerationError)?; - let as_intermediate_signing_key = - AsIntermediateSigningKey::from_prelim_key(prelim_signing_key, as_intermediate_credential) - .map_err(|_| CreateAsStorageError::CredentialGenerationError)?; - Ok((as_signing_key, as_intermediate_signing_key)) -} - #[async_trait] impl BatchedKeyStore for PostgresAsStorage { /// Inserts a keypair with a given `token_key_id` into the key store. @@ -442,43 +395,6 @@ impl AsStorageProvider for PostgresAsStorage { return Ok((messages, remaining_messages as u64)); } - /// Load all currently active [`AsCredential`]s and - /// [`AsIntermediateCredential`]s. - async fn load_as_credentials( - &self, - ) -> Result< - ( - Vec, - Vec, - Vec, - ), - Self::LoadAsCredentialsError, - > { - // TODO: The postgres provider currently does not yet support revoked credentials. - let revoked_fingerprints = vec![]; - let signing_keys_bytes_record = sqlx::query!( - r#"SELECT signing_key, cred_type AS "cred_type: CredentialType" FROM as_signing_keys WHERE currently_active = true"# - ) - .fetch_all(&self.pool) - .await?; - let mut intermed_creds = vec![]; - let mut as_creds = vec![]; - for record in signing_keys_bytes_record { - match record.cred_type { - CredentialType::As => { - let as_cred: AsSigningKey = PhnxCodec::from_slice(&record.signing_key)?; - as_creds.push(as_cred.credential().clone()); - } - CredentialType::Intermediate => { - let intermed_cred: AsIntermediateSigningKey = - PhnxCodec::from_slice(&record.signing_key)?; - intermed_creds.push(intermed_cred.credential().clone()); - } - } - } - Ok((as_creds, intermed_creds, revoked_fingerprints)) - } - /// Load the OPAQUE [`ServerSetup`]. async fn load_opaque_setup( &self, diff --git a/test_harness/src/utils/mod.rs b/test_harness/src/utils/mod.rs index ae45a7a7..850a4974 100644 --- a/test_harness/src/utils/mod.rs +++ b/test_harness/src/utils/mod.rs @@ -81,6 +81,7 @@ pub async fn spawn_app( let auth_service = AuthService::new( &configuration.database.connection_string_without_database(), &configuration.database.name, + domain.clone(), ) .await .expect("Failed to connect to database."); diff --git a/types/src/errors/auth_service.rs b/types/src/errors/auth_service.rs index 175fd5af..bfa8585b 100644 --- a/types/src/errors/auth_service.rs +++ b/types/src/errors/auth_service.rs @@ -107,6 +107,9 @@ pub enum FinishClientAdditionError { /// Client credential not found #[error("Client credential not found")] ClientCredentialNotFound, + /// Invalid connection package + #[error("Invalid connection package")] + InvalidConnectionPackage, } #[derive(Error, Debug, Clone, TlsSerialize, TlsSize, TlsDeserializeBytes)]