From 84bd8384c13804f29887ad18235c0797ec678ca8 Mon Sep 17 00:00:00 2001 From: Mathieu Amiot Date: Tue, 12 Dec 2023 17:59:56 +0100 Subject: [PATCH] WIP --- crypto/src/e2e_identity/mod.rs | 7 +- crypto/src/e2e_identity/refresh_token.rs | 12 +- .../migrations/V11__e2ei_revocation.sql | 1 + keystore/src/entities/mls.rs | 45 ++++++- .../platform/generic/mls/e2ei_acme_ca.rs | 110 ++++++++++++++++++ .../platform/generic/mls/refresh_token.rs | 10 +- .../platform/wasm/mls/e2ei_acme_ca.rs | 92 +++++++++++++++ .../platform/wasm/mls/refresh_token.rs | 8 +- 8 files changed, 262 insertions(+), 23 deletions(-) create mode 100644 keystore/src/entities/platform/generic/mls/e2ei_acme_ca.rs create mode 100644 keystore/src/entities/platform/wasm/mls/e2ei_acme_ca.rs diff --git a/crypto/src/e2e_identity/mod.rs b/crypto/src/e2e_identity/mod.rs index 3d17e2da58..532f6495fa 100644 --- a/crypto/src/e2e_identity/mod.rs +++ b/crypto/src/e2e_identity/mod.rs @@ -1,5 +1,6 @@ use std::collections::HashMap; +use openmls_traits::OpenMlsCryptoProvider; use wire_e2e_identity::prelude::RustyE2eIdentity; use error::*; @@ -85,6 +86,8 @@ impl MlsCentral { } pub async fn e2ei_register_acme_ca(&self, _trust_anchor_pem: String) -> CryptoResult<()> { + use core_crypto_keystore::CryptoKeystoreMls as _; + todo!() } @@ -521,7 +524,7 @@ impl E2eiEnrollment { #[cfg(test)] pub mod tests { - use core_crypto_keystore::entities::{MlsRefreshTokenExt, RefreshTokenEntity}; + use core_crypto_keystore::entities::{E2eiRefreshToken, UniqueEntity}; use itertools::Itertools; use openmls_traits::OpenMlsCryptoProvider; use serde_json::json; @@ -618,7 +621,7 @@ pub mod tests { ) -> E2eIdentityResult<(MlsCentral, E2eiEnrollment, String)> { if is_renewal { let initial_refresh_token = RefreshToken::from("initial-refresh-token".to_string()); - let initial_refresh_token = RefreshTokenEntity::from(initial_refresh_token); + let initial_refresh_token = E2eiRefreshToken::from(initial_refresh_token); let mut conn = cc.mls_backend.key_store().borrow_conn().await?; initial_refresh_token.replace(&mut conn).await.unwrap(); } diff --git a/crypto/src/e2e_identity/refresh_token.rs b/crypto/src/e2e_identity/refresh_token.rs index 421352e180..53779c67dc 100644 --- a/crypto/src/e2e_identity/refresh_token.rs +++ b/crypto/src/e2e_identity/refresh_token.rs @@ -4,7 +4,7 @@ use crate::{ CryptoError, CryptoResult, }; use core_crypto_keystore::{ - entities::{MlsRefreshTokenExt, RefreshTokenEntity}, + entities::{E2eiRefreshToken, UniqueEntity}, CryptoKeystoreResult, }; use mls_crypto_provider::MlsCryptoProvider; @@ -35,7 +35,7 @@ impl E2eiEnrollment { rt: RefreshToken, ) -> CryptoKeystoreResult<()> { let mut conn = backend.key_store().borrow_conn().await?; - let rt = RefreshTokenEntity::from(rt); + let rt = E2eiRefreshToken::from(rt); rt.replace(&mut conn).await } } @@ -43,20 +43,20 @@ impl E2eiEnrollment { impl MlsCentral { pub(crate) async fn find_refresh_token(&self) -> CryptoResult { let mut conn = self.mls_backend.key_store().borrow_conn().await?; - RefreshTokenEntity::find_unique(&mut conn).await?.try_into() + E2eiRefreshToken::find_unique(&mut conn).await?.try_into() } } -impl TryFrom for RefreshToken { +impl TryFrom for RefreshToken { type Error = CryptoError; - fn try_from(mut entity: RefreshTokenEntity) -> CryptoResult { + fn try_from(mut entity: E2eiRefreshToken) -> CryptoResult { let content = std::mem::take(&mut entity.content); Ok(Self(String::from_utf8(content)?)) } } -impl From for RefreshTokenEntity { +impl From for E2eiRefreshToken { fn from(mut rt: RefreshToken) -> Self { let content = std::mem::take(&mut rt.0); Self { diff --git a/keystore/src/connection/platform/generic/migrations/V11__e2ei_revocation.sql b/keystore/src/connection/platform/generic/migrations/V11__e2ei_revocation.sql index b171c9ab34..035b200bec 100644 --- a/keystore/src/connection/platform/generic/migrations/V11__e2ei_revocation.sql +++ b/keystore/src/connection/platform/generic/migrations/V11__e2ei_revocation.sql @@ -4,6 +4,7 @@ CREATE TABLE e2ei_acme_ca ( ); CREATE TABLE e2ei_intermediate_certs ( + ski_aki_pair TEXT UNIQUE, content BLOB ); diff --git a/keystore/src/entities/mls.rs b/keystore/src/entities/mls.rs index 7c17096f4a..be1aaad3cf 100644 --- a/keystore/src/entities/mls.rs +++ b/keystore/src/entities/mls.rs @@ -203,6 +203,13 @@ pub struct E2eiEnrollment { pub content: Vec, } +#[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))] +#[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)] +pub trait UniqueEntity: Entity { + async fn find_unique(conn: &mut Self::ConnectionType) -> CryptoKeystoreResult; + async fn replace(&self, conn: &mut Self::ConnectionType) -> CryptoKeystoreResult<()>; +} + /// OIDC refresh token used in E2EI #[derive(Debug, Clone, PartialEq, Eq, Zeroize)] #[zeroize(drop)] @@ -210,13 +217,39 @@ pub struct E2eiEnrollment { any(target_family = "wasm", feature = "serde"), derive(serde::Serialize, serde::Deserialize) )] -pub struct RefreshTokenEntity { +pub struct E2eiRefreshToken { pub content: Vec, } -#[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))] -#[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)] -pub trait MlsRefreshTokenExt: Entity { - async fn find_unique(conn: &mut Self::ConnectionType) -> CryptoKeystoreResult; - async fn replace(&self, conn: &mut Self::ConnectionType) -> CryptoKeystoreResult<()>; +#[derive(Debug, Clone, PartialEq, Eq, Zeroize)] +#[zeroize(drop)] +#[cfg_attr( + any(target_family = "wasm", feature = "serde"), + derive(serde::Serialize, serde::Deserialize) +)] +pub struct E2eiAcmeCA { + pub content: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq, Zeroize)] +#[zeroize(drop)] +#[cfg_attr( + any(target_family = "wasm", feature = "serde"), + derive(serde::Serialize, serde::Deserialize) +)] +pub struct E2eiIntermediateCert { + // TODO: add a key to identify the CA cert; Using a combination of SKI & AKI extensions concatenated like so is suitable: `SKI[+AKI]` + pub ski_aki_pair: String, + pub content: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq, Zeroize)] +#[zeroize(drop)] +#[cfg_attr( + any(target_family = "wasm", feature = "serde"), + derive(serde::Serialize, serde::Deserialize) +)] +pub struct E2eiCrl { + pub distribution_point: String, + pub content: Vec, } diff --git a/keystore/src/entities/platform/generic/mls/e2ei_acme_ca.rs b/keystore/src/entities/platform/generic/mls/e2ei_acme_ca.rs new file mode 100644 index 0000000000..4993e58127 --- /dev/null +++ b/keystore/src/entities/platform/generic/mls/e2ei_acme_ca.rs @@ -0,0 +1,110 @@ +// Wire +// Copyright (C) 2022 Wire Swiss GmbH + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. + +use std::io::Write; + +use rusqlite::ToSql; + +use crate::{ + connection::{DatabaseConnection, KeystoreDatabaseConnection}, + entities::UniqueEntity, + entities::{E2eiAcmeCA, Entity, EntityBase, EntityFindParams, StringEntityId}, + CryptoKeystoreError, CryptoKeystoreResult, MissingKeyErrorKind, +}; + +const ID: usize = 0; + +impl Entity for E2eiAcmeCA { + fn id_raw(&self) -> &[u8] { + &[0] + } +} + +#[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))] +#[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)] +impl UniqueEntity for E2eiAcmeCA { + async fn find_unique(conn: &mut Self::ConnectionType) -> CryptoKeystoreResult { + let transaction = conn.transaction()?; + use rusqlite::OptionalExtension as _; + + let maybe_content = transaction + .query_row("SELECT content FROM e2ei_acme_ca WHERE id = ?", [ID], |r| { + r.get::<_, Vec>(0) + }) + .optional()?; + + if let Some(content) = maybe_content { + Ok(Self { content }) + } else { + Err(CryptoKeystoreError::NotFound("E2EI ACME root CA", "".to_string())) + } + } + + async fn replace(&self, conn: &mut Self::ConnectionType) -> CryptoKeystoreResult<()> { + Self::ConnectionType::check_buffer_size(self.content.len())?; + let zb_content = rusqlite::blob::ZeroBlob(self.content.len() as i32); + + let transaction = conn.transaction()?; + + let params: [rusqlite::types::ToSqlOutput; 2] = [ID.to_sql()?, zb_content.to_sql()?]; + + transaction.execute( + "INSERT OR REPLACE INTO e2ei_acme_ca (id, content) VALUES (?, ?)", + params, + )?; + let row_id = transaction.last_insert_rowid(); + + let mut blob = transaction.blob_open(rusqlite::DatabaseName::Main, "e2ei_acme_ca", "content", row_id, false)?; + + blob.write_all(&self.content)?; + blob.close()?; + + transaction.commit()?; + + Ok(()) + } +} + +#[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))] +#[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)] +impl EntityBase for E2eiAcmeCA { + type ConnectionType = KeystoreDatabaseConnection; + type AutoGeneratedFields = (); + + fn to_missing_key_err_kind() -> MissingKeyErrorKind { + MissingKeyErrorKind::RefreshToken + } + + async fn find_all(_conn: &mut Self::ConnectionType, _params: EntityFindParams) -> CryptoKeystoreResult> { + return Err(CryptoKeystoreError::NotImplemented); + } + + async fn save(&self, _conn: &mut Self::ConnectionType) -> CryptoKeystoreResult<()> { + return Err(CryptoKeystoreError::NotImplemented); + } + + async fn find_one(_conn: &mut Self::ConnectionType, _id: &StringEntityId) -> CryptoKeystoreResult> { + return Err(CryptoKeystoreError::NotImplemented); + } + + async fn count(_conn: &mut Self::ConnectionType) -> CryptoKeystoreResult { + return Err(CryptoKeystoreError::NotImplemented); + } + + async fn delete(_conn: &mut Self::ConnectionType, _ids: &[StringEntityId]) -> CryptoKeystoreResult<()> { + return Err(CryptoKeystoreError::NotImplemented); + } +} diff --git a/keystore/src/entities/platform/generic/mls/refresh_token.rs b/keystore/src/entities/platform/generic/mls/refresh_token.rs index a81a3e5005..7174e31f39 100644 --- a/keystore/src/entities/platform/generic/mls/refresh_token.rs +++ b/keystore/src/entities/platform/generic/mls/refresh_token.rs @@ -20,14 +20,14 @@ use rusqlite::ToSql; use crate::{ connection::{DatabaseConnection, KeystoreDatabaseConnection}, - entities::MlsRefreshTokenExt, - entities::{Entity, EntityBase, EntityFindParams, RefreshTokenEntity, StringEntityId}, + entities::UniqueEntity, + entities::{E2eiRefreshToken, Entity, EntityBase, EntityFindParams, StringEntityId}, CryptoKeystoreError, CryptoKeystoreResult, MissingKeyErrorKind, }; const ID: usize = 0; -impl Entity for RefreshTokenEntity { +impl Entity for E2eiRefreshToken { fn id_raw(&self) -> &[u8] { &[0] } @@ -35,7 +35,7 @@ impl Entity for RefreshTokenEntity { #[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))] #[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)] -impl MlsRefreshTokenExt for RefreshTokenEntity { +impl UniqueEntity for E2eiRefreshToken { async fn find_unique(conn: &mut Self::ConnectionType) -> CryptoKeystoreResult { let transaction = conn.transaction()?; use rusqlite::OptionalExtension as _; @@ -86,7 +86,7 @@ impl MlsRefreshTokenExt for RefreshTokenEntity { #[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))] #[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)] -impl EntityBase for RefreshTokenEntity { +impl EntityBase for E2eiRefreshToken { type ConnectionType = KeystoreDatabaseConnection; type AutoGeneratedFields = (); diff --git a/keystore/src/entities/platform/wasm/mls/e2ei_acme_ca.rs b/keystore/src/entities/platform/wasm/mls/e2ei_acme_ca.rs new file mode 100644 index 0000000000..3d7cabb1c0 --- /dev/null +++ b/keystore/src/entities/platform/wasm/mls/e2ei_acme_ca.rs @@ -0,0 +1,92 @@ +// Wire +// Copyright (C) 2022 Wire Swiss GmbH + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. + +use crate::{ + connection::{DatabaseConnection, KeystoreDatabaseConnection}, + entities::{E2eiAcmeCA, Entity, EntityBase, EntityFindParams, StringEntityId, UniqueEntity}, + CryptoKeystoreError, CryptoKeystoreResult, MissingKeyErrorKind, +}; + +const ID: [u8; 1] = [0]; + +#[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))] +#[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)] +impl EntityBase for E2eiAcmeCA { + type ConnectionType = KeystoreDatabaseConnection; + type AutoGeneratedFields = (); + + fn to_missing_key_err_kind() -> MissingKeyErrorKind { + MissingKeyErrorKind::RefreshToken + } + + async fn find_all(_conn: &mut Self::ConnectionType, _params: EntityFindParams) -> CryptoKeystoreResult> { + return Err(CryptoKeystoreError::NotImplemented); + } + + async fn save(&self, _conn: &mut Self::ConnectionType) -> crate::CryptoKeystoreResult<()> { + return Err(CryptoKeystoreError::NotImplemented); + } + + async fn find_one( + _conn: &mut Self::ConnectionType, + _id: &StringEntityId, + ) -> crate::CryptoKeystoreResult> { + return Err(CryptoKeystoreError::NotImplemented); + } + + async fn count(_conn: &mut Self::ConnectionType) -> crate::CryptoKeystoreResult { + return Err(CryptoKeystoreError::NotImplemented); + } + + async fn delete(_conn: &mut Self::ConnectionType, _ids: &[StringEntityId]) -> crate::CryptoKeystoreResult<()> { + return Err(CryptoKeystoreError::NotImplemented); + } +} + +impl Entity for E2eiAcmeCA { + fn id_raw(&self) -> &[u8] { + &[0] + } + + fn encrypt(&mut self, cipher: &aes_gcm::Aes256Gcm) -> CryptoKeystoreResult<()> { + self.content = Self::encrypt_data(cipher, self.content.as_slice(), self.aad())?; + Self::ConnectionType::check_buffer_size(self.content.len())?; + Ok(()) + } + + fn decrypt(&mut self, cipher: &aes_gcm::Aes256Gcm) -> CryptoKeystoreResult<()> { + self.content = Self::decrypt_data(cipher, self.content.as_slice(), self.aad())?; + Ok(()) + } +} + +#[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))] +#[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)] +impl UniqueEntity for E2eiAcmeCA { + async fn find_unique(conn: &mut Self::ConnectionType) -> CryptoKeystoreResult { + Ok(conn + .storage() + .get("e2ei_acme_ca", &ID) + .await? + .ok_or(CryptoKeystoreError::NotFound("E2EI ACME root CA", "".to_string()))?) + } + + async fn replace(&self, conn: &mut Self::ConnectionType) -> CryptoKeystoreResult<()> { + let storage = conn.storage_mut(); + storage.save("e2ei_acme_ca", &mut [self.clone()]).await?; + Ok(()) + } +} diff --git a/keystore/src/entities/platform/wasm/mls/refresh_token.rs b/keystore/src/entities/platform/wasm/mls/refresh_token.rs index c5e9141368..e6ecd01f64 100644 --- a/keystore/src/entities/platform/wasm/mls/refresh_token.rs +++ b/keystore/src/entities/platform/wasm/mls/refresh_token.rs @@ -16,7 +16,7 @@ use crate::{ connection::{DatabaseConnection, KeystoreDatabaseConnection}, - entities::{Entity, EntityBase, EntityFindParams, MlsRefreshTokenExt, RefreshTokenEntity, StringEntityId}, + entities::{E2eiRefreshToken, Entity, EntityBase, EntityFindParams, StringEntityId, UniqueEntity}, CryptoKeystoreError, CryptoKeystoreResult, MissingKeyErrorKind, }; @@ -24,7 +24,7 @@ const ID: [u8; 1] = [0]; #[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))] #[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)] -impl EntityBase for RefreshTokenEntity { +impl EntityBase for E2eiRefreshToken { type ConnectionType = KeystoreDatabaseConnection; type AutoGeneratedFields = (); @@ -56,7 +56,7 @@ impl EntityBase for RefreshTokenEntity { } } -impl Entity for RefreshTokenEntity { +impl Entity for E2eiRefreshToken { fn id_raw(&self) -> &[u8] { &[0] } @@ -75,7 +75,7 @@ impl Entity for RefreshTokenEntity { #[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))] #[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)] -impl MlsRefreshTokenExt for RefreshTokenEntity { +impl UniqueEntity for E2eiRefreshToken { async fn find_unique(conn: &mut Self::ConnectionType) -> CryptoKeystoreResult { Ok(conn .storage()