From 797ff754c357483cb2f94bb49c54b5a6b442b2d1 Mon Sep 17 00:00:00 2001 From: SimonThormeyer Date: Tue, 14 Jan 2025 13:56:06 +0100 Subject: [PATCH 1/2] feat: implement basic derive macro for entity trait [WPB-14952] Support for types * `E2eiEnrollment` * `E2eiIntermediateCert` * `E2eiCrl` * `ProteusSession` Also, rename core-crypto-attributes crate to core-crypto-macros since t's no longer just attribute macros in this crate. --- Cargo.lock | 22 +- Cargo.toml | 4 +- crypto-attributes/README.md | 4 - .../Cargo.toml | 3 +- .../src/durable.rs | 0 .../src/entity_derive/derive_impl.rs | 327 ++++++++++++++++++ crypto-macros/src/entity_derive/mod.rs | 93 +++++ crypto-macros/src/entity_derive/parse.rs | 146 ++++++++ .../src/idempotent.rs | 0 .../src/lib.rs | 15 +- crypto/Cargo.toml | 2 +- crypto/src/lib.rs | 2 +- 12 files changed, 598 insertions(+), 20 deletions(-) delete mode 100644 crypto-attributes/README.md rename {crypto-attributes => crypto-macros}/Cargo.toml (87%) rename {crypto-attributes => crypto-macros}/src/durable.rs (100%) create mode 100644 crypto-macros/src/entity_derive/derive_impl.rs create mode 100644 crypto-macros/src/entity_derive/mod.rs create mode 100644 crypto-macros/src/entity_derive/parse.rs rename {crypto-attributes => crypto-macros}/src/idempotent.rs (100%) rename {crypto-attributes => crypto-macros}/src/lib.rs (81%) diff --git a/Cargo.lock b/Cargo.lock index d748f32123..06b7191df1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -916,8 +916,8 @@ dependencies = [ "async-trait", "base64 0.22.1", "cfg-if", - "core-crypto-attributes", "core-crypto-keystore", + "core-crypto-macros", "criterion", "cryptobox", "derive_more", @@ -967,15 +967,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "core-crypto-attributes" -version = "2.0.0" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.93", -] - [[package]] name = "core-crypto-ffi" version = "2.0.0" @@ -1017,6 +1008,7 @@ dependencies = [ "blocking", "cfg-if", "core-crypto-keystore", + "core-crypto-macros", "core-foundation 0.10.0", "criterion", "futures-lite", @@ -1058,6 +1050,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "core-crypto-macros" +version = "2.0.0" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.93", +] + [[package]] name = "core-foundation" version = "0.9.4" diff --git a/Cargo.toml b/Cargo.toml index ef3ac69f86..9d49ff7b8d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ members = [ "crypto", "crypto-ffi", - "crypto-attributes", + "crypto-macros", "keystore", "keystore-dump", "mls-provider", @@ -25,7 +25,7 @@ cfg-if = "1.0" const_format = "0.2" core-crypto = { path = "crypto" } core-crypto-keystore = { path = "keystore" } -core-crypto-attributes = { path = "crypto-attributes" } +core-crypto-macros = { path = "crypto-macros" } derive_more = { version = "0.99", features = ["from", "into", "deref", "deref_mut"] } futures-util = "0.3" hex = "0.4" diff --git a/crypto-attributes/README.md b/crypto-attributes/README.md deleted file mode 100644 index 8c3e05b9f2..0000000000 --- a/crypto-attributes/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# Attributes - -Provides attribute macros to automatically detect missing persistence of the MLS group as well as methods unintendedly -leaking entities. See [lib.rs](src/lib.rs) for further details. \ No newline at end of file diff --git a/crypto-attributes/Cargo.toml b/crypto-macros/Cargo.toml similarity index 87% rename from crypto-attributes/Cargo.toml rename to crypto-macros/Cargo.toml index 1346048c09..fd7fc2f763 100644 --- a/crypto-attributes/Cargo.toml +++ b/crypto-macros/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "core-crypto-attributes" +name = "core-crypto-macros" description = "Macros for core-crypto" repository = "https://github.com/wireapp/core-crypto" version = "2.0.0" @@ -13,3 +13,4 @@ proc-macro = true syn = { version = "2", features = ["full"] } quote = "1" proc-macro2 = "1" +heck = "0.5" diff --git a/crypto-attributes/src/durable.rs b/crypto-macros/src/durable.rs similarity index 100% rename from crypto-attributes/src/durable.rs rename to crypto-macros/src/durable.rs diff --git a/crypto-macros/src/entity_derive/derive_impl.rs b/crypto-macros/src/entity_derive/derive_impl.rs new file mode 100644 index 0000000000..be65cff1ba --- /dev/null +++ b/crypto-macros/src/entity_derive/derive_impl.rs @@ -0,0 +1,327 @@ +use crate::entity_derive::{ColumnType, KeyStoreEntityFlattened}; +use quote::quote; + +impl quote::ToTokens for KeyStoreEntityFlattened { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let entity_base_impl = self.entity_base_impl(); + let entity_generic_impl = self.entity_generic_impl(); + let entity_wasm_impl = self.entity_wasm_impl(); + let entity_transaction_ext_impl = self.entity_transaction_ext_impl(); + tokens.extend(quote! { + #entity_base_impl + #entity_generic_impl + #entity_wasm_impl + #entity_transaction_ext_impl + }); + } +} +impl KeyStoreEntityFlattened { + fn entity_base_impl(&self) -> proc_macro2::TokenStream { + let Self { + collection_name, + struct_name, + .. + } = self; + + // Identical for both wasm and non-wasm + quote! { + #[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))] + #[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)] + impl crate::entities::EntityBase for #struct_name { + type ConnectionType = crate::connection::KeystoreDatabaseConnection; + type AutoGeneratedFields = (); + const COLLECTION_NAME: &'static str = #collection_name; + + fn to_missing_key_err_kind() -> crate::MissingKeyErrorKind { + crate::MissingKeyErrorKind::#struct_name + } + + fn to_transaction_entity(self) -> crate::transaction::dynamic_dispatch::Entity { + crate::transaction::dynamic_dispatch::Entity::#struct_name(self) + } + } + } + } + + fn entity_generic_impl(&self) -> proc_macro2::TokenStream { + let Self { + collection_name, + struct_name, + id, + id_type, + id_name, + blob_columns, + blob_column_names, + all_columns, + .. + } = self; + + let string_id_conversion = matches!(id_type, ColumnType::String).then(|| { + quote! { let #id: String = id.try_into()?; } + }); + + let id_to_byte_slice = match id_type { + ColumnType::String => quote! {self.#id.as_bytes() }, + ColumnType::Bytes => quote! { &self.#id[..] }, + }; + + let id_field_construct_self = match id_type { + ColumnType::String => quote! { #id, }, + ColumnType::Bytes => quote! { #id: id.to_bytes(), }, + }; + + let id_slice = match id_type { + ColumnType::String => quote! { #id.as_str() }, + ColumnType::Bytes => quote! { #id.as_slice() }, + }; + let find_all_query = format!("SELECT rowid, {id_name} FROM {collection_name} "); + + let find_one_query = format!("SELECT rowid FROM {collection_name} WHERE {id_name} = ?"); + + let count_query = format!("SELECT COUNT(*) FROM {collection_name}"); + + quote! { + #[cfg(not(target_family = "wasm"))] + #[async_trait::async_trait] + impl crate::entities::Entity for #struct_name { + fn id_raw(&self) -> &[u8] { + #id_to_byte_slice + } + + async fn find_all( + conn: &mut Self::ConnectionType, + params: crate::entities::EntityFindParams, + ) -> crate::CryptoKeystoreResult> { + let transaction = conn.transaction()?; + let query = #find_all_query.to_string() + ¶ms.to_sql(); + + let mut stmt = transaction.prepare_cached(&query)?; + let mut rows = stmt.query_map([], |r| Ok((r.get(0)?, r.get(1)?)))?; + use std::io::Read as _; + rows.map(|row| { + let (rowid, #id) = row?; + + #( + let mut blob = transaction.blob_open(rusqlite::DatabaseName::Main, #collection_name, #blob_column_names, rowid, false)?; + let mut #blob_columns = vec![]; + blob.read_to_end(&mut #blob_columns)?; + blob.close()?; + )* + + Ok(Self { #id + #( + , #all_columns + )* + }) + }).collect() + } + + async fn find_one( + conn: &mut Self::ConnectionType, + id: &crate::entities::StringEntityId, + ) -> crate::CryptoKeystoreResult> { + let transaction = conn.transaction()?; + use rusqlite::OptionalExtension as _; + + #string_id_conversion + + let mut row_id = transaction + .query_row(&#find_one_query, [#id_slice], |r| { + r.get::<_, i64>(0) + }) + .optional()?; + + if let Some(rowid) = row_id.take() { + #( + let mut blob = transaction.blob_open(rusqlite::DatabaseName::Main, #collection_name, #blob_column_names, rowid, true)?; + use std::io::Read as _; + let mut #blob_columns = Vec::with_capacity(blob.len()); + blob.read_to_end(&mut #blob_columns)?; + blob.close()?; + )* + + Ok(Some(Self { + #id_field_construct_self + #( + #blob_columns, + )* + })) + } else { + Ok(None) + } + } + + async fn count(conn: &mut Self::ConnectionType) -> crate::CryptoKeystoreResult { + Ok(conn.query_row(&#count_query, [], |r| r.get(0))?) + } + } + } + } + + fn entity_wasm_impl(&self) -> proc_macro2::TokenStream { + let Self { + collection_name, + struct_name, + id, + id_type, + blob_columns, + .. + } = self; + + let id_to_byte_slice = match id_type { + ColumnType::String => quote! {self.#id.as_bytes() }, + ColumnType::Bytes => quote! { self.#id.as_slice() }, + }; + + quote! { + #[cfg(target_family = "wasm")] + #[async_trait::async_trait(?Send)] + impl crate::entities::Entity for #struct_name { + fn id_raw(&self) -> &[u8] { + #id_to_byte_slice + } + + async fn find_all(conn: &mut Self::ConnectionType, params: crate::entities::EntityFindParams) -> crate::CryptoKeystoreResult> { + let storage = conn.storage(); + storage.get_all(#collection_name, Some(params)).await + } + + async fn find_one(conn: &mut Self::ConnectionType, id: &crate::entities::StringEntityId) -> crate::CryptoKeystoreResult> { + conn.storage().get(#collection_name, id.as_slice()).await + } + + async fn count(conn: &mut Self::ConnectionType) -> crate::CryptoKeystoreResult { + conn.storage().count(#collection_name).await + } + + fn encrypt(&mut self, cipher: &aes_gcm::Aes256Gcm) -> crate::CryptoKeystoreResult<()> { + use crate::connection::DatabaseConnection as _; + #( + self.#blob_columns = self.encrypt_data(cipher, self.#blob_columns.as_slice())?; + Self::ConnectionType::check_buffer_size(self.#blob_columns.len())?; + )* + Ok(()) + } + + fn decrypt(&mut self, cipher: &aes_gcm::Aes256Gcm) -> crate::CryptoKeystoreResult<()> { + #( + self.#blob_columns = self.decrypt_data(cipher, self.#blob_columns.as_slice())?; + )* + Ok(()) + } + } + } + } + + fn entity_transaction_ext_impl(&self) -> proc_macro2::TokenStream { + let Self { + collection_name, + struct_name, + id, + id_name, + all_columns, + all_column_names, + blob_columns, + blob_column_names, + no_upsert, + id_type, + .. + } = self; + + let upsert_pairs: Vec<_> = all_column_names + .iter() + .map(|col| format! { "{col} = excluded.{col}"}) + .collect(); + let upsert_postfix = (!no_upsert) + // UPSERT (ON CONFLICT DO UPDATE) with RETURNING to get the rowid + .then(|| format!(" ON CONFLICT({id_name}) DO UPDATE SET {}", upsert_pairs.join(", "))) + .unwrap_or_default(); + + let column_list = all_columns + .iter() + .map(ToString::to_string) + .collect::>() + .join(", "); + + let upsert_query = format!( + "INSERT INTO {collection_name} ({id_name}, {column_list}) VALUES (?{}){upsert_postfix} RETURNING rowid", + ", ?".repeat(self.all_columns.len()), + ); + + let delete_query = format!("DELETE FROM {collection_name} WHERE {id_name} = ?"); + + let id_slice_delete = match id_type { + ColumnType::String => quote! { id.try_as_str()? }, + ColumnType::Bytes => quote! { id.as_slice() }, + }; + + quote! { + #[cfg(target_family = "wasm")] + #[async_trait::async_trait(?Send)] + impl crate::entities::EntityTransactionExt for #struct_name {} + + #[cfg(not(target_family = "wasm"))] + #[async_trait::async_trait] + impl crate::entities::EntityTransactionExt for #struct_name { + async fn save(&self, transaction: &crate::connection::TransactionWrapper<'_>) -> crate::CryptoKeystoreResult<()> { + use crate::entities::EntityBase as _; + use rusqlite::ToSql as _; + use crate::connection::DatabaseConnection as _; + + #( + crate::connection::KeystoreDatabaseConnection::check_buffer_size(self.#blob_columns.len())?; + )* + + let sql = #upsert_query; + + let row_id_result: Result = + transaction.query_row(&sql, [ + self.#id.to_sql()? + #( + , + rusqlite::blob::ZeroBlob(self.#blob_columns.len() as i32).to_sql()? + )* + ], |r| r.get(0)); + + use std::io::Write as _; + match row_id_result { + Ok(row_id) => { + #( + let mut blob = transaction.blob_open( + rusqlite::DatabaseName::Main, + #collection_name, + #blob_column_names, + row_id, + false, + )?; + + blob.write_all(&self.#blob_columns)?; + blob.close()?; + )* + + Ok(()) + } + Err(rusqlite::Error::SqliteFailure(e, _)) if e.extended_code == rusqlite::ffi::SQLITE_CONSTRAINT_UNIQUE => { + Err(crate::CryptoKeystoreError::AlreadyExists) + } + Err(e) => Err(e.into()), + } + } + + async fn delete_fail_on_missing_id( + transaction: &crate::connection::TransactionWrapper<'_>, + id: crate::entities::StringEntityId<'_>, + ) -> crate::CryptoKeystoreResult<()> { + use crate::entities::EntityBase as _; + let updated = transaction.execute(&#delete_query, [#id_slice_delete])?; + + if updated > 0 { + Ok(()) + } else { + Err(Self::to_missing_key_err_kind().into()) + } + } + } + } + } +} diff --git a/crypto-macros/src/entity_derive/mod.rs b/crypto-macros/src/entity_derive/mod.rs new file mode 100644 index 0000000000..8abf4db1e3 --- /dev/null +++ b/crypto-macros/src/entity_derive/mod.rs @@ -0,0 +1,93 @@ +mod derive_impl; +mod parse; + +use proc_macro2::Ident; + +/// Representation of a struct annotated with `#[derive(Entity)]`. +pub(super) struct KeyStoreEntity { + /// Name of the type to implement the trait on + struct_name: Ident, + /// Database table name + collection_name: String, + /// The ID column + id: IdColumn, + /// All other columns + columns: Columns, + /// Whether to fail on inserting conflicting ids instead of using upsert semantics (default: false) + no_upsert: bool, +} + +impl KeyStoreEntity { + /// Convert Keystore entity to a flattened version that is more verbose but can be used more easily in `quote!()`. + pub(super) fn flatten(self) -> KeyStoreEntityFlattened { + let all_columns = self + .columns + .0 + .iter() + .map(|column| column.name.clone()) + .collect::>(); + + let blob_columns = self + .columns + .0 + .iter() + .filter(|column| column.column_type == ColumnType::Bytes) + .map(|column| column.name.clone()) + .collect::>(); + + let blob_column_names = blob_columns.iter().map(ToString::to_string).collect(); + let all_column_names = all_columns.iter().map(ToString::to_string).collect(); + + let id = self.id.name; + let id_name = id.to_string(); + let id_type = self.id.column_type; + + KeyStoreEntityFlattened { + struct_name: self.struct_name, + collection_name: self.collection_name, + no_upsert: self.no_upsert, + id, + id_type, + id_name, + all_columns, + all_column_names, + blob_columns, + blob_column_names, + } + } +} + +/// Less abstract version of [KeyStoreEntity] that has all the fields flattened +/// ready for usage in `quote!()`. +pub(super) struct KeyStoreEntityFlattened { + struct_name: Ident, + collection_name: String, + id: Ident, + id_name: String, + id_type: ColumnType, + all_columns: Vec, + all_column_names: Vec, + blob_columns: Vec, + blob_column_names: Vec, + no_upsert: bool, +} + +// Now identical to column, but +// subject to change once more diverse entities are supported. +struct IdColumn { + name: Ident, + column_type: ColumnType, +} + +struct Columns(Vec); + +struct Column { + name: Ident, + column_type: ColumnType, +} + +#[derive(PartialEq, Eq)] +enum ColumnType { + String, + Bytes, +} diff --git a/crypto-macros/src/entity_derive/parse.rs b/crypto-macros/src/entity_derive/parse.rs new file mode 100644 index 0000000000..bafe1cbe93 --- /dev/null +++ b/crypto-macros/src/entity_derive/parse.rs @@ -0,0 +1,146 @@ +use crate::entity_derive::{Column, ColumnType, Columns, IdColumn, KeyStoreEntity}; +use heck::ToSnakeCase; +use proc_macro2::{Ident, Span}; +use quote::ToTokens; +use syn::spanned::Spanned; +use syn::{Attribute, Data, DataStruct, Fields, FieldsNamed, Token, Type}; + +impl syn::parse::Parse for KeyStoreEntity { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let derive_input = input.parse::()?; + let struct_name = derive_input.ident.clone(); + + // #[entity(collection_name = "my_collection", no_upsert)] + let (mut collection_name, no_upsert) = Self::parse_outer_attributes(&derive_input.attrs)?; + if collection_name.is_empty() { + collection_name = struct_name.to_string().to_snake_case() + "s"; + } + + let named_fields = Self::fields_from_data(&derive_input.data, derive_input.span())?; + let id = IdColumn::parse(named_fields)?; + let columns = Columns::parse(named_fields, &id.name)?; + + Ok(KeyStoreEntity { + struct_name, + collection_name, + id, + columns, + no_upsert, + }) + } +} + +impl KeyStoreEntity { + fn parse_outer_attributes(attrs: &[Attribute]) -> Result<(String, bool), syn::Error> { + let mut collection_name = String::new(); + let mut no_upsert = false; + for attr in attrs { + if !attr.path().is_ident("entity") { + continue; + } + let meta = &attr.meta; + let list = meta.require_list()?; + list.parse_nested_meta(|meta| { + let ident = meta.path.require_ident()?; + match ident.to_string().as_str() { + "collection_name" => { + meta.input.parse::()?; + collection_name = meta.input.parse::()?.value(); + Ok(()) + } + "no_upsert" => { + no_upsert = true; + Ok(()) + } + _ => Err(syn::Error::new_spanned(ident, "unknown argument")), + } + })?; + } + Ok((collection_name, no_upsert)) + } + + fn fields_from_data(data: &Data, span: Span) -> syn::Result<&FieldsNamed> { + match data { + Data::Struct(DataStruct { + fields: Fields::Named(named_fields), + .. + }) => Ok(named_fields), + _ => Err(syn::Error::new(span, "Expected a struct with named fields.")), + } + } +} + +impl IdColumn { + fn parse(named_fields: &FieldsNamed) -> syn::Result { + let mut id = None; + let mut implicit_id = None; + for field in named_fields.named.iter() { + let name = field + .ident + .as_ref() + .expect("named fields always have identifiers") + .clone(); + let column_type = ColumnType::parse(&field.ty)?; + + if field.attrs.iter().any(|attr| attr.path().is_ident("id")) { + if id.is_some() { + return Err(syn::Error::new_spanned( + field, + "Ambiguous `#[id] attributes. Provide exactly one.", + )); + } + id = Some(IdColumn { name, column_type }); + } else if name == "id" { + implicit_id = Some(IdColumn { name, column_type }); + } + } + id = id.or(implicit_id); + id.ok_or(syn::Error::new_spanned(named_fields, "No `#[id]` attribute provided.")) + } +} + +impl Columns { + fn parse(named_fields: &FieldsNamed, id_column: &Ident) -> syn::Result { + let columns = named_fields + .named + .iter() + .filter(|field| field.ident.as_ref() != Some(id_column)) + .map(|field| { + let field_name = field + .ident + .as_ref() + .expect("named fields always have identifiers") + .clone(); + let field_type = ColumnType::parse(&field.ty)?; + + Ok(Column { + name: field_name, + column_type: field_type, + }) + }) + .collect::>>()?; + if columns.is_empty() { + return Err(syn::Error::new_spanned( + named_fields, + "Provide at least one field to be used as a table column.", + )); + } + + Ok(Self(columns)) + } +} + +impl ColumnType { + fn parse(ty: &Type) -> Result { + let mut type_string = ty.to_token_stream().to_string(); + type_string.retain(|c| !c.is_whitespace()); + match type_string.as_str() { + "String" | "std::string::String" => Ok(Self::String), + "Vec" | "std::vec::Vec" => Ok(Self::Bytes), + type_string => Err(syn::Error::new_spanned( + ty, + format!("Expected `String` or `Vec`, not `{type_string}`."), + )), + } + } +} diff --git a/crypto-attributes/src/idempotent.rs b/crypto-macros/src/idempotent.rs similarity index 100% rename from crypto-attributes/src/idempotent.rs rename to crypto-macros/src/idempotent.rs diff --git a/crypto-attributes/src/lib.rs b/crypto-macros/src/lib.rs similarity index 81% rename from crypto-attributes/src/lib.rs rename to crypto-macros/src/lib.rs index f04a71e95b..0d8b713109 100644 --- a/crypto-attributes/src/lib.rs +++ b/crypto-macros/src/lib.rs @@ -1,12 +1,25 @@ extern crate proc_macro; +use crate::entity_derive::KeyStoreEntity; use proc_macro::TokenStream; use proc_macro2::Ident; -use syn::{punctuated::Punctuated, token::Comma, Attribute, Block, FnArg, ItemFn, ReturnType, Visibility}; +use quote::quote; +use syn::{ + parse_macro_input, punctuated::Punctuated, token::Comma, Attribute, Block, FnArg, ItemFn, ReturnType, Visibility, +}; mod durable; +mod entity_derive; mod idempotent; +/// Implements the `Entity` trait for the given struct. +/// To be used internally inside the `core-crypto-keystore` crate only. +#[proc_macro_derive(Entity, attributes(entity, id))] +pub fn derive_entity(input: TokenStream) -> TokenStream { + let parsed = parse_macro_input!(input as KeyStoreEntity).flatten(); + TokenStream::from(quote! { #parsed }) +} + /// Will drop current MLS group in memory and replace it with the one in the keystore. /// This simulates an application crash. Once restarted, everything has to be loaded from the /// keystore, memory is lost. diff --git a/crypto/Cargo.toml b/crypto/Cargo.toml index 0eab5ee49c..74dee377eb 100644 --- a/crypto/Cargo.toml +++ b/crypto/Cargo.toml @@ -94,7 +94,7 @@ wire-e2e-identity = { workspace = true, features = ["builder"] } web-time = "1.1.0" time = { version = "0.3", features = ["wasm-bindgen"] } core-crypto-keystore = { workspace = true, features = ["dummy-entity"] } -core-crypto-attributes = { workspace = true } +core-crypto-macros = { workspace = true } [target.'cfg(not(target_family = "wasm"))'.dev-dependencies] cryptobox = { git = "https://github.com/wireapp/cryptobox" } diff --git a/crypto/src/lib.rs b/crypto/src/lib.rs index 0003ae5b5c..674170d735 100644 --- a/crypto/src/lib.rs +++ b/crypto/src/lib.rs @@ -25,7 +25,7 @@ use async_lock::Mutex; #[cfg(test)] -pub use core_crypto_attributes::{dispotent, durable, idempotent}; +pub use core_crypto_macros::{dispotent, durable, idempotent}; use std::sync::Arc; pub use self::error::*; From cdb6bea52dd14b25121f409b37f865ca6d1afa26 Mon Sep 17 00:00:00 2001 From: SimonThormeyer Date: Wed, 15 Jan 2025 15:34:05 +0100 Subject: [PATCH 2/2] refactor: use macro for supported keystore types [WPB-14952] --- keystore/Cargo.toml | 1 + keystore/src/entities/mls.rs | 9 +- .../entities/platform/generic/mls/e2ei_crl.rs | 154 -------------- .../generic/mls/e2ei_intermediate_cert.rs | 173 ---------------- .../platform/generic/mls/enrollment.rs | 158 -------------- .../src/entities/platform/generic/mls/mod.rs | 3 - .../entities/platform/generic/proteus/mod.rs | 1 - .../platform/generic/proteus/session.rs | 192 ------------------ .../entities/platform/wasm/mls/e2ei_crl.rs | 70 ------- .../wasm/mls/e2ei_intermediate_cert.rs | 70 ------- .../entities/platform/wasm/mls/enrollment.rs | 69 ------- .../src/entities/platform/wasm/mls/mod.rs | 3 - .../src/entities/platform/wasm/proteus/mod.rs | 1 - .../entities/platform/wasm/proteus/session.rs | 77 ------- keystore/src/entities/proteus.rs | 2 +- 15 files changed, 8 insertions(+), 975 deletions(-) delete mode 100644 keystore/src/entities/platform/generic/mls/e2ei_crl.rs delete mode 100644 keystore/src/entities/platform/generic/mls/e2ei_intermediate_cert.rs delete mode 100644 keystore/src/entities/platform/generic/mls/enrollment.rs delete mode 100644 keystore/src/entities/platform/generic/proteus/session.rs delete mode 100644 keystore/src/entities/platform/wasm/mls/e2ei_crl.rs delete mode 100644 keystore/src/entities/platform/wasm/mls/e2ei_intermediate_cert.rs delete mode 100644 keystore/src/entities/platform/wasm/mls/enrollment.rs delete mode 100644 keystore/src/entities/platform/wasm/proteus/session.rs diff --git a/keystore/Cargo.toml b/keystore/Cargo.toml index 26b750e033..945d37bb79 100644 --- a/keystore/Cargo.toml +++ b/keystore/Cargo.toml @@ -38,6 +38,7 @@ async-lock.workspace = true postcard = { version = "1.1", default-features = false, features = ["use-std"] } sha2.workspace = true serde_json.workspace = true +core-crypto-macros.workspace = true # iOS specific things security-framework = { version = "3.0", optional = true } diff --git a/keystore/src/entities/mls.rs b/keystore/src/entities/mls.rs index 3fc20f22a4..365dcb3322 100644 --- a/keystore/src/entities/mls.rs +++ b/keystore/src/entities/mls.rs @@ -192,8 +192,9 @@ pub struct MlsKeyPackage { /// Entity representing an enrollment instance used to fetch a x509 certificate and persisted when /// context switches and the memory it lives in is about to be erased -#[derive(Debug, Clone, PartialEq, Eq, Zeroize)] +#[derive(Debug, Clone, PartialEq, Eq, Zeroize, core_crypto_macros::Entity)] #[zeroize(drop)] +#[entity(collection_name = "e2ei_enrollment", no_upsert)] #[cfg_attr( any(target_family = "wasm", feature = "serde"), derive(serde::Serialize, serde::Deserialize) @@ -387,7 +388,7 @@ pub struct E2eiAcmeCA { pub content: Vec, } -#[derive(Debug, Clone, PartialEq, Eq, Zeroize)] +#[derive(Debug, Clone, PartialEq, Eq, Zeroize, core_crypto_macros::Entity)] #[zeroize(drop)] #[cfg_attr( any(target_family = "wasm", feature = "serde"), @@ -395,17 +396,19 @@ pub struct E2eiAcmeCA { )] pub struct E2eiIntermediateCert { // key to identify the CA cert; Using a combination of SKI & AKI extensions concatenated like so is suitable: `SKI[+AKI]` + #[id] pub ski_aki_pair: String, pub content: Vec, } -#[derive(Debug, Clone, PartialEq, Eq, Zeroize)] +#[derive(Debug, Clone, PartialEq, Eq, Zeroize, core_crypto_macros::Entity)] #[zeroize(drop)] #[cfg_attr( any(target_family = "wasm", feature = "serde"), derive(serde::Serialize, serde::Deserialize) )] pub struct E2eiCrl { + #[id] pub distribution_point: String, pub content: Vec, } diff --git a/keystore/src/entities/platform/generic/mls/e2ei_crl.rs b/keystore/src/entities/platform/generic/mls/e2ei_crl.rs deleted file mode 100644 index 92bce3a94a..0000000000 --- a/keystore/src/entities/platform/generic/mls/e2ei_crl.rs +++ /dev/null @@ -1,154 +0,0 @@ -// 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, TransactionWrapper}, - entities::{E2eiCrl, Entity, EntityBase, EntityFindParams, EntityTransactionExt, StringEntityId}, - CryptoKeystoreResult, MissingKeyErrorKind, -}; - -#[async_trait::async_trait] -impl Entity for E2eiCrl { - fn id_raw(&self) -> &[u8] { - self.distribution_point.as_bytes() - } - - async fn find_all( - conn: &mut Self::ConnectionType, - params: EntityFindParams, - ) -> crate::CryptoKeystoreResult> { - let transaction = conn.transaction()?; - let query: String = format!("SELECT rowid, distribution_point FROM e2ei_crls {}", params.to_sql()); - - let mut stmt = transaction.prepare_cached(&query)?; - let mut rows = stmt.query_map([], |r| Ok((r.get(0)?, r.get(1)?)))?; - let entities = rows.try_fold(Vec::new(), |mut acc, row_res| { - use std::io::Read as _; - let (rowid, distribution_point) = row_res?; - - let mut blob = transaction.blob_open(rusqlite::DatabaseName::Main, "e2ei_crls", "content", rowid, false)?; - - let mut content = vec![]; - blob.read_to_end(&mut content)?; - blob.close()?; - - acc.push(Self { - distribution_point, - content, - }); - - crate::CryptoKeystoreResult::Ok(acc) - })?; - - Ok(entities) - } - - async fn find_one( - conn: &mut Self::ConnectionType, - id: &StringEntityId, - ) -> crate::CryptoKeystoreResult> { - let transaction = conn.transaction()?; - use rusqlite::OptionalExtension as _; - let distribution_point: String = id.try_into()?; - let mut row_id = transaction - .query_row( - "SELECT rowid FROM e2ei_crls WHERE distribution_point = ?", - [distribution_point.as_str()], - |r| r.get::<_, i64>(0), - ) - .optional()?; - - if let Some(rowid) = row_id.take() { - let mut blob = transaction.blob_open(rusqlite::DatabaseName::Main, "e2ei_crls", "content", rowid, true)?; - use std::io::Read as _; - let mut buf = Vec::with_capacity(blob.len()); - blob.read_to_end(&mut buf)?; - blob.close()?; - - transaction.commit()?; - - Ok(Some(Self { - distribution_point, - content: buf, - })) - } else { - Ok(None) - } - } - - async fn count(conn: &mut Self::ConnectionType) -> crate::CryptoKeystoreResult { - Ok(conn.query_row("SELECT COUNT(*) FROM e2ei_crls", [], |r| r.get(0))?) - } -} - -#[async_trait::async_trait] -impl EntityBase for E2eiCrl { - type ConnectionType = KeystoreDatabaseConnection; - type AutoGeneratedFields = (); - const COLLECTION_NAME: &'static str = "e2ei_crls"; - - fn to_missing_key_err_kind() -> MissingKeyErrorKind { - MissingKeyErrorKind::E2eiCrl - } - - fn to_transaction_entity(self) -> crate::transaction::dynamic_dispatch::Entity { - crate::transaction::dynamic_dispatch::Entity::E2eiCrl(self) - } -} - -#[async_trait::async_trait] -impl EntityTransactionExt for E2eiCrl { - async fn save(&self, transaction: &TransactionWrapper<'_>) -> CryptoKeystoreResult<()> { - use rusqlite::ToSql as _; - - Self::ConnectionType::check_buffer_size(self.content.len())?; - - let zb = rusqlite::blob::ZeroBlob(self.content.len() as i32); - - // Use UPSERT (ON CONFLICT DO UPDATE) - let sql = " - INSERT INTO e2ei_crls (distribution_point, content) - VALUES (?, ?) - ON CONFLICT(distribution_point) DO UPDATE SET content = excluded.content - RETURNING rowid"; - - // Execute the UPSERT and get the row_id of the affected row - let row_id: i64 = - transaction.query_row(sql, [self.distribution_point.to_sql()?, zb.to_sql()?], |r| r.get(0))?; - - // Open a blob to write the content data - let mut blob = transaction.blob_open(rusqlite::DatabaseName::Main, "e2ei_crls", "content", row_id, false)?; - - use std::io::Write as _; - blob.write_all(&self.content)?; - blob.close()?; - - Ok(()) - } - - async fn delete_fail_on_missing_id( - transaction: &TransactionWrapper<'_>, - id: StringEntityId<'_>, - ) -> CryptoKeystoreResult<()> { - let updated = transaction.execute("DELETE FROM e2ei_crls WHERE distribution_point = ?", [id.try_as_str()?])?; - - if updated > 0 { - Ok(()) - } else { - Err(Self::to_missing_key_err_kind().into()) - } - } -} diff --git a/keystore/src/entities/platform/generic/mls/e2ei_intermediate_cert.rs b/keystore/src/entities/platform/generic/mls/e2ei_intermediate_cert.rs deleted file mode 100644 index 9c177ac4ae..0000000000 --- a/keystore/src/entities/platform/generic/mls/e2ei_intermediate_cert.rs +++ /dev/null @@ -1,173 +0,0 @@ -// 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, TransactionWrapper}, - entities::{E2eiIntermediateCert, Entity, EntityBase, EntityFindParams, EntityTransactionExt, StringEntityId}, - CryptoKeystoreResult, MissingKeyErrorKind, -}; - -#[async_trait::async_trait] -impl Entity for E2eiIntermediateCert { - fn id_raw(&self) -> &[u8] { - self.ski_aki_pair.as_bytes() - } - - async fn find_all( - conn: &mut Self::ConnectionType, - params: EntityFindParams, - ) -> crate::CryptoKeystoreResult> { - let transaction = conn.transaction()?; - let query: String = format!( - "SELECT rowid, ski_aki_pair FROM e2ei_intermediate_certs {}", - params.to_sql() - ); - - let mut stmt = transaction.prepare_cached(&query)?; - let mut rows = stmt.query_map([], |r| Ok((r.get(0)?, r.get(1)?)))?; - let entities = rows.try_fold(Vec::new(), |mut acc, row_res| { - use std::io::Read as _; - let (rowid, ski_aki_pair) = row_res?; - - let mut blob = transaction.blob_open( - rusqlite::DatabaseName::Main, - "e2ei_intermediate_certs", - "content", - rowid, - false, - )?; - - let mut content = vec![]; - blob.read_to_end(&mut content)?; - blob.close()?; - - acc.push(Self { ski_aki_pair, content }); - - crate::CryptoKeystoreResult::Ok(acc) - })?; - - Ok(entities) - } - - async fn find_one( - conn: &mut Self::ConnectionType, - id: &StringEntityId, - ) -> crate::CryptoKeystoreResult> { - let transaction = conn.transaction()?; - use rusqlite::OptionalExtension as _; - let ski_aki_pair: String = id.try_into()?; - let mut row_id = transaction - .query_row( - "SELECT rowid FROM e2ei_intermediate_certs WHERE ski_aki_pair = ?", - [ski_aki_pair.as_str()], - |r| r.get::<_, i64>(0), - ) - .optional()?; - - if let Some(rowid) = row_id.take() { - let mut blob = transaction.blob_open( - rusqlite::DatabaseName::Main, - "e2ei_intermediate_certs", - "content", - rowid, - true, - )?; - use std::io::Read as _; - let mut buf = Vec::with_capacity(blob.len()); - blob.read_to_end(&mut buf)?; - blob.close()?; - - transaction.commit()?; - - Ok(Some(Self { - ski_aki_pair, - content: buf, - })) - } else { - Ok(None) - } - } - - async fn count(conn: &mut Self::ConnectionType) -> crate::CryptoKeystoreResult { - Ok(conn.query_row("SELECT COUNT(*) FROM e2ei_intermediate_certs", [], |r| r.get(0))?) - } -} - -#[async_trait::async_trait] -impl EntityBase for E2eiIntermediateCert { - type ConnectionType = KeystoreDatabaseConnection; - type AutoGeneratedFields = (); - const COLLECTION_NAME: &'static str = "e2ei_intermediate_certs"; - fn to_missing_key_err_kind() -> MissingKeyErrorKind { - MissingKeyErrorKind::E2eiIntermediateCert - } - - fn to_transaction_entity(self) -> crate::transaction::dynamic_dispatch::Entity { - crate::transaction::dynamic_dispatch::Entity::E2eiIntermediateCert(self) - } -} - -#[async_trait::async_trait] -impl EntityTransactionExt for E2eiIntermediateCert { - async fn save(&self, transaction: &TransactionWrapper<'_>) -> CryptoKeystoreResult<()> { - use rusqlite::ToSql as _; - - Self::ConnectionType::check_buffer_size(self.content.len())?; - - let zb = rusqlite::blob::ZeroBlob(self.content.len() as i32); - - // UPSERT (ON CONFLICT DO UPDATE) with RETURNING to get the rowid - let sql = " - INSERT INTO e2ei_intermediate_certs (ski_aki_pair, content) - VALUES (?, ?) - ON CONFLICT(ski_aki_pair) DO UPDATE SET content = excluded.content - RETURNING rowid"; - - // Execute the UPSERT and get the row_id of the affected row - let row_id: i64 = transaction.query_row(sql, [self.ski_aki_pair.to_sql()?, zb.to_sql()?], |r| r.get(0))?; - - // Open a blob to write the content data - let mut blob = transaction.blob_open( - rusqlite::DatabaseName::Main, - "e2ei_intermediate_certs", - "content", - row_id, - false, - )?; - - use std::io::Write as _; - blob.write_all(&self.content)?; - blob.close()?; - - Ok(()) - } - - async fn delete_fail_on_missing_id( - transaction: &TransactionWrapper<'_>, - id: StringEntityId<'_>, - ) -> CryptoKeystoreResult<()> { - let updated = transaction.execute( - "DELETE FROM e2ei_intermediate_certs WHERE ski_aki_pair = ?", - [id.try_as_str()?], - )?; - - if updated > 0 { - Ok(()) - } else { - Err(Self::to_missing_key_err_kind().into()) - } - } -} diff --git a/keystore/src/entities/platform/generic/mls/enrollment.rs b/keystore/src/entities/platform/generic/mls/enrollment.rs deleted file mode 100644 index 53f9320825..0000000000 --- a/keystore/src/entities/platform/generic/mls/enrollment.rs +++ /dev/null @@ -1,158 +0,0 @@ -// 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, TransactionWrapper}, - entities::{E2eiEnrollment, Entity, EntityBase, EntityFindParams, EntityTransactionExt, StringEntityId}, - CryptoKeystoreError, CryptoKeystoreResult, MissingKeyErrorKind, -}; - -#[async_trait::async_trait] -impl Entity for E2eiEnrollment { - fn id_raw(&self) -> &[u8] { - &self.id[..] - } - - async fn find_all( - conn: &mut Self::ConnectionType, - params: EntityFindParams, - ) -> crate::CryptoKeystoreResult> { - let transaction = conn.transaction()?; - let query: String = format!("SELECT rowid, id FROM e2ei_enrollment {}", params.to_sql()); - - let mut stmt = transaction.prepare_cached(&query)?; - let mut rows = stmt.query_map([], |r| Ok((r.get(0)?, r.get(1)?)))?; - let entities = rows.try_fold(Vec::new(), |mut acc, row_res| { - use std::io::Read as _; - let (rowid, id) = row_res?; - - let mut blob = - transaction.blob_open(rusqlite::DatabaseName::Main, "e2ei_enrollment", "content", rowid, false)?; - - let mut content = vec![]; - blob.read_to_end(&mut content)?; - blob.close()?; - - acc.push(Self { id, content }); - - crate::CryptoKeystoreResult::Ok(acc) - })?; - - Ok(entities) - } - - async fn find_one( - conn: &mut Self::ConnectionType, - id: &StringEntityId, - ) -> crate::CryptoKeystoreResult> { - let transaction = conn.transaction()?; - use rusqlite::OptionalExtension as _; - let mut row_id = transaction - .query_row("SELECT rowid FROM e2ei_enrollment WHERE id = ?", [id.as_slice()], |r| { - r.get::<_, i64>(0) - }) - .optional()?; - - if let Some(rowid) = row_id.take() { - let mut blob = - transaction.blob_open(rusqlite::DatabaseName::Main, "e2ei_enrollment", "content", rowid, true)?; - use std::io::Read as _; - let mut buf = Vec::with_capacity(blob.len()); - blob.read_to_end(&mut buf)?; - blob.close()?; - - transaction.commit()?; - - Ok(Some(Self { - id: id.to_bytes(), - content: buf, - })) - } else { - Ok(None) - } - } - - async fn count(conn: &mut Self::ConnectionType) -> crate::CryptoKeystoreResult { - Ok(conn.query_row("SELECT COUNT(*) FROM e2ei_enrollment", [], |r| r.get(0))?) - } -} - -#[async_trait::async_trait] -impl EntityBase for E2eiEnrollment { - type ConnectionType = KeystoreDatabaseConnection; - type AutoGeneratedFields = (); - const COLLECTION_NAME: &'static str = "e2ei_enrollment"; - - fn to_missing_key_err_kind() -> MissingKeyErrorKind { - MissingKeyErrorKind::E2eiEnrollment - } - - fn to_transaction_entity(self) -> crate::transaction::dynamic_dispatch::Entity { - crate::transaction::dynamic_dispatch::Entity::E2eiEnrollment(self) - } -} - -#[async_trait::async_trait] -impl EntityTransactionExt for E2eiEnrollment { - async fn save(&self, transaction: &TransactionWrapper<'_>) -> CryptoKeystoreResult<()> { - use rusqlite::ToSql as _; - - Self::ConnectionType::check_buffer_size(self.content.len())?; - - // Attempt to insert directly, handling conflicts as errors - let zb = rusqlite::blob::ZeroBlob(self.content.len() as i32); - let sql = "INSERT INTO e2ei_enrollment (id, content) VALUES (?, ?) RETURNING rowid"; - - let row_id_result: Result = - transaction.query_row(sql, [self.id.to_sql()?, zb.to_sql()?], |r| r.get(0)); - - match row_id_result { - Ok(row_id) => { - // Open a blob to write the content data - let mut blob = transaction.blob_open( - rusqlite::DatabaseName::Main, - "e2ei_enrollment", - "content", - row_id, - false, - )?; - - use std::io::Write as _; - blob.write_all(&self.content)?; - blob.close()?; - - Ok(()) - } - Err(rusqlite::Error::SqliteFailure(e, _)) if e.extended_code == rusqlite::ffi::SQLITE_CONSTRAINT_UNIQUE => { - Err(CryptoKeystoreError::AlreadyExists) - } - Err(e) => Err(e.into()), - } - } - - async fn delete_fail_on_missing_id( - transaction: &TransactionWrapper<'_>, - id: StringEntityId<'_>, - ) -> CryptoKeystoreResult<()> { - let updated = transaction.execute("DELETE FROM e2ei_enrollment WHERE id = ?", [id.as_slice()])?; - - if updated > 0 { - Ok(()) - } else { - Err(Self::to_missing_key_err_kind().into()) - } - } -} diff --git a/keystore/src/entities/platform/generic/mls/mod.rs b/keystore/src/entities/platform/generic/mls/mod.rs index 6108ff60a8..84496aa399 100644 --- a/keystore/src/entities/platform/generic/mls/mod.rs +++ b/keystore/src/entities/platform/generic/mls/mod.rs @@ -16,10 +16,7 @@ pub mod credential; pub mod e2ei_acme_ca; -pub mod e2ei_crl; -pub mod e2ei_intermediate_cert; pub mod encryption_keypair; -pub mod enrollment; pub mod epoch_encryption_keypair; pub mod group; pub mod hpke_private_key; diff --git a/keystore/src/entities/platform/generic/proteus/mod.rs b/keystore/src/entities/platform/generic/proteus/mod.rs index 56167ecdc3..37ea649c9d 100644 --- a/keystore/src/entities/platform/generic/proteus/mod.rs +++ b/keystore/src/entities/platform/generic/proteus/mod.rs @@ -16,4 +16,3 @@ pub(crate) mod identity; pub mod prekey; -pub mod session; diff --git a/keystore/src/entities/platform/generic/proteus/session.rs b/keystore/src/entities/platform/generic/proteus/session.rs deleted file mode 100644 index 9d6d264d00..0000000000 --- a/keystore/src/entities/platform/generic/proteus/session.rs +++ /dev/null @@ -1,192 +0,0 @@ -// 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::TransactionWrapper; -use crate::entities::{EntityFindParams, EntityTransactionExt, ProteusSession, StringEntityId}; -use crate::CryptoKeystoreError; -use crate::{ - connection::{DatabaseConnection, KeystoreDatabaseConnection}, - entities::{Entity, EntityBase}, - MissingKeyErrorKind, -}; - -#[async_trait::async_trait] -impl Entity for ProteusSession { - fn id_raw(&self) -> &[u8] { - self.id.as_bytes() - } - - async fn find_all( - conn: &mut Self::ConnectionType, - params: EntityFindParams, - ) -> crate::CryptoKeystoreResult> { - let transaction = conn.transaction()?; - let query: String = format!("SELECT rowid, id FROM proteus_sessions {}", params.to_sql()); - - let mut stmt = transaction.prepare_cached(&query)?; - let mut rows = stmt.query_map([], |r| Ok((r.get(0)?, r.get(1)?)))?; - let entities = rows.try_fold(Vec::new(), |mut acc, q_result| { - use std::io::Read as _; - let (rowid, id) = q_result?; - - let mut blob = - transaction.blob_open(rusqlite::DatabaseName::Main, "proteus_sessions", "session", rowid, true)?; - let mut session = vec![]; - blob.read_to_end(&mut session)?; - blob.close()?; - - acc.push(Self { id, session }); - crate::CryptoKeystoreResult::Ok(acc) - })?; - - Ok(entities) - } - - async fn find_one( - conn: &mut Self::ConnectionType, - id: &StringEntityId, - ) -> crate::CryptoKeystoreResult> { - use rusqlite::OptionalExtension as _; - let transaction = conn.transaction()?; - let id_string: String = id.try_into()?; - let mut rowid: Option = transaction - .query_row("SELECT rowid FROM proteus_sessions WHERE id = ?", [&id_string], |r| { - r.get::<_, i64>(0) - }) - .optional()?; - - if let Some(rowid) = rowid.take() { - use std::io::Read as _; - - let mut blob = - transaction.blob_open(rusqlite::DatabaseName::Main, "proteus_sessions", "session", rowid, true)?; - let mut session = Vec::with_capacity(blob.len()); - blob.read_to_end(&mut session)?; - blob.close()?; - - Ok(Some(Self { id: id_string, session })) - } else { - Ok(None) - } - } - - async fn find_many( - conn: &mut Self::ConnectionType, - _ids: &[StringEntityId], - ) -> crate::CryptoKeystoreResult> { - // Plot twist: we always select ALL the persisted groups. Unsure if we want to make it a real API with selection - let mut stmt = conn.prepare_cached("SELECT rowid, id FROM proteus_sessions ORDER BY rowid ASC")?; - let rows: Vec<(i64, String)> = stmt - .query_map([], |r| Ok((r.get(0)?, r.get(1)?)))? - .map(|r| r.map_err(CryptoKeystoreError::from)) - .collect::>()?; - - drop(stmt); - - if rows.is_empty() { - return Ok(Default::default()); - } - - let transaction = conn.transaction()?; - - let mut res = Vec::with_capacity(rows.len()); - for (rowid, id) in rows.into_iter() { - use std::io::Read as _; - - let mut blob = - transaction.blob_open(rusqlite::DatabaseName::Main, "proteus_sessions", "session", rowid, true)?; - let mut session = Vec::with_capacity(blob.len()); - blob.read_to_end(&mut session)?; - blob.close()?; - - res.push(Self { id, session }); - } - - transaction.commit()?; - - Ok(res) - } - - async fn count(conn: &mut Self::ConnectionType) -> crate::CryptoKeystoreResult { - Ok(conn.query_row("SELECT COUNT(*) FROM proteus_sessions", [], |r| r.get(0))?) - } -} - -#[async_trait::async_trait] -impl EntityBase for ProteusSession { - type ConnectionType = KeystoreDatabaseConnection; - type AutoGeneratedFields = (); - const COLLECTION_NAME: &'static str = "proteus_sessions"; - - fn to_missing_key_err_kind() -> MissingKeyErrorKind { - MissingKeyErrorKind::ProteusSession - } - - fn to_transaction_entity(self) -> crate::transaction::dynamic_dispatch::Entity { - crate::transaction::dynamic_dispatch::Entity::ProteusSession(self) - } -} - -#[async_trait::async_trait] -impl EntityTransactionExt for ProteusSession { - async fn save(&self, transaction: &TransactionWrapper<'_>) -> crate::CryptoKeystoreResult<()> { - use rusqlite::ToSql as _; - - let session_id = &self.id; - let session = &self.session; - - Self::ConnectionType::check_buffer_size(session.len())?; - Self::ConnectionType::check_buffer_size(session_id.len())?; - - let zb = rusqlite::blob::ZeroBlob(session.len() as i32); - - // Use UPSERT (ON CONFLICT DO UPDATE) - let sql = " - INSERT INTO proteus_sessions (id, session) - VALUES (?, ?) - ON CONFLICT(id) DO UPDATE SET session = excluded.session - RETURNING rowid"; - - let row_id: i64 = transaction.query_row(sql, [&session_id.to_sql()?, &zb.to_sql()?], |r| r.get(0))?; - - let mut blob = transaction.blob_open( - rusqlite::DatabaseName::Main, - "proteus_sessions", - "session", - row_id, - false, - )?; - use std::io::Write as _; - blob.write_all(session)?; - blob.close()?; - - Ok(()) - } - - async fn delete_fail_on_missing_id( - transaction: &TransactionWrapper<'_>, - id: StringEntityId<'_>, - ) -> crate::CryptoKeystoreResult<()> { - let id_string: String = (&id).try_into()?; - let updated = transaction.execute("DELETE FROM proteus_sessions WHERE id = ?", [id_string])?; - - if updated > 0 { - Ok(()) - } else { - Err(Self::to_missing_key_err_kind().into()) - } - } -} diff --git a/keystore/src/entities/platform/wasm/mls/e2ei_crl.rs b/keystore/src/entities/platform/wasm/mls/e2ei_crl.rs deleted file mode 100644 index a153317910..0000000000 --- a/keystore/src/entities/platform/wasm/mls/e2ei_crl.rs +++ /dev/null @@ -1,70 +0,0 @@ -// 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::{E2eiCrl, Entity, EntityBase, EntityFindParams, EntityTransactionExt, StringEntityId}, - CryptoKeystoreResult, MissingKeyErrorKind, -}; - -#[async_trait::async_trait(?Send)] -impl EntityBase for E2eiCrl { - type ConnectionType = KeystoreDatabaseConnection; - type AutoGeneratedFields = (); - const COLLECTION_NAME: &'static str = "e2ei_crls"; - - fn to_missing_key_err_kind() -> MissingKeyErrorKind { - MissingKeyErrorKind::E2eiCrl - } - - fn to_transaction_entity(self) -> crate::transaction::dynamic_dispatch::Entity { - crate::transaction::dynamic_dispatch::Entity::E2eiCrl(self) - } -} - -#[async_trait::async_trait(?Send)] -impl EntityTransactionExt for E2eiCrl {} - -#[async_trait::async_trait(?Send)] -impl Entity for E2eiCrl { - fn id_raw(&self) -> &[u8] { - self.distribution_point.as_bytes() - } - - async fn find_all(conn: &mut Self::ConnectionType, params: EntityFindParams) -> CryptoKeystoreResult> { - let storage = conn.storage(); - storage.get_all(Self::COLLECTION_NAME, Some(params)).await - } - - async fn find_one(conn: &mut Self::ConnectionType, id: &StringEntityId) -> CryptoKeystoreResult> { - conn.storage().get(Self::COLLECTION_NAME, id.as_slice()).await - } - - async fn count(conn: &mut Self::ConnectionType) -> CryptoKeystoreResult { - conn.storage().count(Self::COLLECTION_NAME).await - } - - fn encrypt(&mut self, cipher: &aes_gcm::Aes256Gcm) -> CryptoKeystoreResult<()> { - self.content = self.encrypt_data(cipher, self.content.as_slice())?; - 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())?; - Ok(()) - } -} diff --git a/keystore/src/entities/platform/wasm/mls/e2ei_intermediate_cert.rs b/keystore/src/entities/platform/wasm/mls/e2ei_intermediate_cert.rs deleted file mode 100644 index a7b533d82d..0000000000 --- a/keystore/src/entities/platform/wasm/mls/e2ei_intermediate_cert.rs +++ /dev/null @@ -1,70 +0,0 @@ -// 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::{E2eiIntermediateCert, Entity, EntityBase, EntityFindParams, EntityTransactionExt, StringEntityId}, - CryptoKeystoreResult, MissingKeyErrorKind, -}; - -#[async_trait::async_trait(?Send)] -impl EntityBase for E2eiIntermediateCert { - type ConnectionType = KeystoreDatabaseConnection; - type AutoGeneratedFields = (); - const COLLECTION_NAME: &'static str = "e2ei_intermediate_certs"; - - fn to_missing_key_err_kind() -> MissingKeyErrorKind { - MissingKeyErrorKind::E2eiIntermediateCert - } - - fn to_transaction_entity(self) -> crate::transaction::dynamic_dispatch::Entity { - crate::transaction::dynamic_dispatch::Entity::E2eiIntermediateCert(self) - } -} - -#[async_trait::async_trait(?Send)] -impl EntityTransactionExt for E2eiIntermediateCert {} - -#[async_trait::async_trait(?Send)] -impl Entity for E2eiIntermediateCert { - fn id_raw(&self) -> &[u8] { - self.ski_aki_pair.as_bytes() - } - - async fn find_all(conn: &mut Self::ConnectionType, params: EntityFindParams) -> CryptoKeystoreResult> { - let storage = conn.storage(); - storage.get_all(Self::COLLECTION_NAME, Some(params)).await - } - - async fn find_one(conn: &mut Self::ConnectionType, id: &StringEntityId) -> CryptoKeystoreResult> { - conn.storage().get(Self::COLLECTION_NAME, id.as_slice()).await - } - - async fn count(conn: &mut Self::ConnectionType) -> CryptoKeystoreResult { - conn.storage().count(Self::COLLECTION_NAME).await - } - - fn encrypt(&mut self, cipher: &aes_gcm::Aes256Gcm) -> CryptoKeystoreResult<()> { - self.content = self.encrypt_data(cipher, self.content.as_slice())?; - 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())?; - Ok(()) - } -} diff --git a/keystore/src/entities/platform/wasm/mls/enrollment.rs b/keystore/src/entities/platform/wasm/mls/enrollment.rs deleted file mode 100644 index b97da23bd6..0000000000 --- a/keystore/src/entities/platform/wasm/mls/enrollment.rs +++ /dev/null @@ -1,69 +0,0 @@ -// 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::{E2eiEnrollment, Entity, EntityBase, EntityFindParams, EntityTransactionExt, StringEntityId}, - CryptoKeystoreResult, MissingKeyErrorKind, -}; - -#[async_trait::async_trait(?Send)] -impl EntityBase for E2eiEnrollment { - type ConnectionType = KeystoreDatabaseConnection; - type AutoGeneratedFields = (); - const COLLECTION_NAME: &'static str = "e2ei_enrollment"; - - fn to_missing_key_err_kind() -> MissingKeyErrorKind { - MissingKeyErrorKind::E2eiEnrollment - } - - fn to_transaction_entity(self) -> crate::transaction::dynamic_dispatch::Entity { - crate::transaction::dynamic_dispatch::Entity::E2eiEnrollment(self) - } -} - -#[async_trait::async_trait(?Send)] -impl EntityTransactionExt for E2eiEnrollment {} - -#[async_trait::async_trait(?Send)] -impl Entity for E2eiEnrollment { - fn id_raw(&self) -> &[u8] { - &self.id[..] - } - - async fn find_all(conn: &mut Self::ConnectionType, params: EntityFindParams) -> CryptoKeystoreResult> { - conn.storage().get_all(Self::COLLECTION_NAME, Some(params)).await - } - - async fn find_one(conn: &mut Self::ConnectionType, id: &StringEntityId) -> CryptoKeystoreResult> { - conn.storage().get(Self::COLLECTION_NAME, id.as_slice()).await - } - - async fn count(conn: &mut Self::ConnectionType) -> CryptoKeystoreResult { - conn.storage().count(Self::COLLECTION_NAME).await - } - - fn encrypt(&mut self, cipher: &aes_gcm::Aes256Gcm) -> CryptoKeystoreResult<()> { - self.content = self.encrypt_data(cipher, self.content.as_slice())?; - 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())?; - Ok(()) - } -} diff --git a/keystore/src/entities/platform/wasm/mls/mod.rs b/keystore/src/entities/platform/wasm/mls/mod.rs index 9f4aff0ed0..309c8c1987 100644 --- a/keystore/src/entities/platform/wasm/mls/mod.rs +++ b/keystore/src/entities/platform/wasm/mls/mod.rs @@ -16,10 +16,7 @@ pub mod credential; pub mod e2ei_acme_ca; -pub mod e2ei_crl; -pub mod e2ei_intermediate_cert; pub mod encryption_keypair; -pub mod enrollment; pub mod epoch_encryption_keypair; pub mod group; pub mod hpke_private_key; diff --git a/keystore/src/entities/platform/wasm/proteus/mod.rs b/keystore/src/entities/platform/wasm/proteus/mod.rs index 56167ecdc3..37ea649c9d 100644 --- a/keystore/src/entities/platform/wasm/proteus/mod.rs +++ b/keystore/src/entities/platform/wasm/proteus/mod.rs @@ -16,4 +16,3 @@ pub(crate) mod identity; pub mod prekey; -pub mod session; diff --git a/keystore/src/entities/platform/wasm/proteus/session.rs b/keystore/src/entities/platform/wasm/proteus/session.rs deleted file mode 100644 index 4a3eeab925..0000000000 --- a/keystore/src/entities/platform/wasm/proteus/session.rs +++ /dev/null @@ -1,77 +0,0 @@ -// 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::{Entity, EntityBase, EntityFindParams, EntityTransactionExt, ProteusSession, StringEntityId}, - CryptoKeystoreResult, MissingKeyErrorKind, -}; - -#[async_trait::async_trait(?Send)] -impl EntityBase for ProteusSession { - type ConnectionType = KeystoreDatabaseConnection; - type AutoGeneratedFields = (); - const COLLECTION_NAME: &'static str = "proteus_sessions"; - - fn to_missing_key_err_kind() -> MissingKeyErrorKind { - MissingKeyErrorKind::ProteusSession - } - - fn to_transaction_entity(self) -> crate::transaction::dynamic_dispatch::Entity { - crate::transaction::dynamic_dispatch::Entity::ProteusSession(self) - } -} - -#[async_trait::async_trait(?Send)] -impl EntityTransactionExt for ProteusSession {} - -#[async_trait::async_trait(?Send)] -impl Entity for ProteusSession { - fn id_raw(&self) -> &[u8] { - self.id.as_bytes() - } - - async fn find_all(conn: &mut Self::ConnectionType, params: EntityFindParams) -> CryptoKeystoreResult> { - let storage = conn.storage(); - storage.get_all(Self::COLLECTION_NAME, Some(params)).await - } - - async fn find_one( - conn: &mut Self::ConnectionType, - id: &StringEntityId, - ) -> crate::CryptoKeystoreResult> { - let storage = conn.storage(); - storage.get(Self::COLLECTION_NAME, id.as_slice()).await - } - - async fn count(conn: &mut Self::ConnectionType) -> crate::CryptoKeystoreResult { - let storage = conn.storage(); - storage.count(Self::COLLECTION_NAME).await - } - - fn encrypt(&mut self, cipher: &aes_gcm::Aes256Gcm) -> CryptoKeystoreResult<()> { - self.session = self.encrypt_data(cipher, self.session.as_slice())?; - Self::ConnectionType::check_buffer_size(self.session.len())?; - - Ok(()) - } - - fn decrypt(&mut self, cipher: &aes_gcm::Aes256Gcm) -> CryptoKeystoreResult<()> { - self.session = self.decrypt_data(cipher, self.session.as_slice())?; - - Ok(()) - } -} diff --git a/keystore/src/entities/proteus.rs b/keystore/src/entities/proteus.rs index 1573cafa9f..b09c79cfce 100644 --- a/keystore/src/entities/proteus.rs +++ b/keystore/src/entities/proteus.rs @@ -106,7 +106,7 @@ impl ProteusPrekey { } } -#[derive(Debug, Clone, Zeroize, PartialEq, Eq)] +#[derive(Debug, Clone, Zeroize, PartialEq, Eq, core_crypto_macros::Entity)] #[zeroize(drop)] #[cfg_attr( any(target_family = "wasm", feature = "serde"),