Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
287bcca
test: also assert the ciphersuite when deduplicating credentials
SimonThormeyer Dec 3, 2025
68e5d04
feat(keystore/wasm): add `mls_credentials_new` object store
SimonThormeyer Dec 5, 2025
c767179
build: move to our `idb` fork
SimonThormeyer Dec 5, 2025
e55194f
chore: drop usages of `Metabuilder`
SimonThormeyer Dec 5, 2025
771c6a9
chore: drop `metabuilder` module
SimonThormeyer Dec 5, 2025
d94753d
feat(keystore/wasm): migrate credentials to new object store
SimonThormeyer Dec 5, 2025
9e2a99a
fix(keystore/wasm): update migration target version
SimonThormeyer Dec 5, 2025
376a595
feat(keystore/generic): update credentials schema
SimonThormeyer Dec 5, 2025
f585dc7
chore(keystore/generic): update credential `Entity` impl to new schema
SimonThormeyer Dec 8, 2025
547e696
chore(keystore/generic): no more `StoredCredential` deletion by value
SimonThormeyer Dec 8, 2025
a11f0cb
chore(keystore/wasm): no more `StoredCredential` deletion by value
SimonThormeyer Dec 8, 2025
426c60b
chore(transaction): no more `StoredCredential` deletion by value
SimonThormeyer Dec 8, 2025
d50fb81
chore: remove usages of `StoredCredential` deletion by value
SimonThormeyer Dec 8, 2025
432bc67
fix: generate a new signature keypair when creating an E2EI enrollment
SimonThormeyer Dec 8, 2025
4c640b1
chore(wasm): provide credential deletion by value in migrations
SimonThormeyer Dec 9, 2025
bf16c6f
chore(wasm): don't use old credential deletion API in migrations
SimonThormeyer Dec 9, 2025
34a79f9
chore(wasm/keystore): add `console_error_panic_hook` as dev dependency
SimonThormeyer Dec 10, 2025
491e3e7
test(keystore/wasm): set up panic hook
SimonThormeyer Dec 10, 2025
d090333
test: `mls_signature_keypairs` is no longer among object stores
SimonThormeyer Dec 10, 2025
33e5245
chore(wasm/keystore): add helper to open a migration connection/trans…
SimonThormeyer Dec 10, 2025
311236f
fix(keystore/wasm): stop using `Database::open()` in keystore migrations
SimonThormeyer Dec 10, 2025
fc5bca9
refactor: rename `StoredCredential.secret_key` to `private_key`
SimonThormeyer Dec 10, 2025
62ea1ed
refactor: rename `StoredCredential.id` to `session_id` to avoid confu…
SimonThormeyer Dec 10, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,9 @@ derive_more = { version = "2.0", features = [
] }
futures-util = "0.3"
hex = "0.4"
idb = "0.6"
indexmap = "2"

# Until devashishdxt/idb#36 is resolved, we're keeping our fork
idb = { git = "https://github.com/wireapp/idb", rev = "05573e5b29191fdeb442104ad1040346a3889822" }
itertools = "0.14"
log = { version = "0.4", features = ["kv_serde"] }
log-reload = "0.1.3"
Expand Down
1 change: 1 addition & 0 deletions crypto/src/e2e_identity/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ mod error;
pub(crate) mod id;
pub(crate) mod identity;
mod pki_env;
pub(crate) use crypto::E2eiSignatureKeypair;
pub use pki_env::NewCrlDistributionPoints;
pub(crate) use pki_env::restore_pki_env;
#[cfg(not(test))]
Expand Down
4 changes: 2 additions & 2 deletions crypto/src/mls/credential/credential_ref/find.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ impl CredentialRef {
.map_err(KeystoreError::wrap("finding all credentials"))?
.into_iter()
.filter(|stored| {
client_id.is_none_or(|client_id| client_id.as_ref() == stored.id)
client_id.is_none_or(|client_id| client_id.as_ref() == stored.session_id)
&& earliest_validity.is_none_or(|earliest_validity| earliest_validity == stored.created_at)
&& ciphersuite.is_none_or(|ciphersuite| u16::from(ciphersuite) == stored.ciphersuite)
&& public_key.is_none_or(|public_key| public_key == stored.public_key)
Expand All @@ -96,7 +96,7 @@ impl CredentialRef {
&& let Ok(ciphersuite) = stored_credential.ciphersuite.try_into()
{
out.push(Self {
client_id: ClientId(stored_credential.id.clone()),
client_id: ClientId(stored_credential.session_id.clone()),
r#type,
ciphersuite,
earliest_validity: stored_credential.created_at,
Expand Down
4 changes: 2 additions & 2 deletions crypto/src/mls/credential/credential_ref/persistence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ impl CredentialRef {
// these are the only checks we can currently do at the DB level: match the client id, creation timestamp,
// public key and signature scheme
.filter(|stored_credential|
stored_credential.id == self.client_id().as_slice()
stored_credential.session_id == self.client_id().as_slice()
&& stored_credential.created_at == self.earliest_validity
&& stored_credential.public_key == self.public_key
&& stored_credential.ciphersuite == u16::from(self.ciphersuite)
Expand All @@ -73,7 +73,7 @@ impl CredentialRef {
let mls_credential = MlsCredential::tls_deserialize(&mut stored_credential.credential.as_slice())
.map_err(Error::tls_deserialize("mls credential"))?;
let ciphersuite = Ciphersuite::try_from(stored_credential.ciphersuite).map_err(RecursiveError::mls("loading ciphersuite from db"))?;
let signature_key_pair = openmls_basic_credential::SignatureKeyPair::from_raw(ciphersuite.signature_algorithm(), stored_credential.secret_key.to_owned(), stored_credential.public_key.to_owned());
let signature_key_pair = openmls_basic_credential::SignatureKeyPair::from_raw(ciphersuite.signature_algorithm(), stored_credential.private_key.to_owned(), stored_credential.public_key.to_owned());
let credential_type = mls_credential.credential_type().try_into().map_err(RecursiveError::mls_credential("loading credential from db"))?;
let earliest_validity = stored_credential.created_at;
Ok(Credential {
Expand Down
11 changes: 3 additions & 8 deletions crypto/src/mls/credential/persistence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ impl Credential {

let stored_credential = database
.save(StoredCredential {
id: self.client_id().to_owned().into_inner(),
session_id: self.client_id().to_owned().into_inner(),
credential: credential_data,
created_at: Default::default(), // updated by the `.save` impl
ciphersuite: u16::from(self.ciphersuite),
secret_key: self.signature_key_pair.private().to_owned(),
private_key: self.signature_key_pair.private().to_owned(),
public_key: self.signature_key().public().to_owned(),
})
.await
Expand All @@ -45,13 +45,8 @@ impl Credential {

/// Delete this credential from the database
pub(crate) async fn delete(self, database: &Database) -> Result<()> {
let credential_data = self
.mls_credential
.tls_serialize_detached()
.map_err(Error::tls_serialize("credential"))?;

database
.cred_delete_by_credential(credential_data)
.remove::<StoredCredential, _>(self.signature_key_pair.public())
.await
.map_err(KeystoreError::wrap("deleting credential"))?;

Expand Down
44 changes: 29 additions & 15 deletions crypto/src/transaction_context/e2e_identity/rotate.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,36 @@
use openmls_traits::OpenMlsCryptoProvider;
use openmls_basic_credential::SignatureKeyPair;
use openmls_traits::{OpenMlsCryptoProvider as _, random::OpenMlsRand as _};

use super::error::{Error, Result};
use crate::{
CertificateBundle, Ciphersuite, Credential, CredentialType, E2eiEnrollment, RecursiveError,
e2e_identity::NewCrlDistributionPoints,
CertificateBundle, Ciphersuite, Credential, CredentialType, E2eiEnrollment, MlsError, RecursiveError,
e2e_identity::{E2eiSignatureKeypair, NewCrlDistributionPoints},
mls::credential::{ext::CredentialExt, x509::CertificatePrivateKey},
transaction_context::TransactionContext,
};

impl TransactionContext {
async fn new_sign_keypair(&self, ciphersuite: Ciphersuite) -> Result<E2eiSignatureKeypair> {
let mls_provider = self
.mls_provider()
.await
.map_err(RecursiveError::transaction("getting mls provider"))?;

let sign_keypair = &SignatureKeyPair::new(
ciphersuite.signature_algorithm(),
&mut *mls_provider
.rand()
.borrow_rand()
.map_err(MlsError::wrap("borrowing rng"))?,
)
.map_err(MlsError::wrap("generating new sign keypair"))?;

sign_keypair
.try_into()
.map_err(RecursiveError::e2e_identity("creating E2eiSignatureKeypair"))
.map_err(Into::into)
}

/// Generates an E2EI enrollment instance for a "regular" client (with a Basic credential)
/// willing to migrate to E2EI. As a consequence, this method does not support changing the
/// ClientId which should remain the same as the Basic one.
Expand Down Expand Up @@ -36,11 +58,7 @@ impl TransactionContext {
.map_err(|_| Error::MissingExistingClient(CredentialType::Basic))?;
let client_id = cb.mls_credential().identity().to_owned().into();

let sign_keypair = Some(
cb.signature_key()
.try_into()
.map_err(RecursiveError::e2e_identity("creating E2eiSignatureKeypair"))?,
);
let sign_keypair = self.new_sign_keypair(ciphersuite).await?;

E2eiEnrollment::try_new(
client_id,
Expand All @@ -50,7 +68,7 @@ impl TransactionContext {
expiry_sec,
&mls_provider,
ciphersuite,
sign_keypair,
Some(sign_keypair),
false, // no x509 credential yet at this point so no OIDC authn yet so no refresh token to restore
)
.map_err(RecursiveError::e2e_identity("creating new enrollment"))
Expand Down Expand Up @@ -84,11 +102,7 @@ impl TransactionContext {
.await
.map_err(|_| Error::MissingExistingClient(CredentialType::X509))?;
let client_id = cb.mls_credential().identity().to_owned().into();
let sign_keypair = Some(
cb.signature_key()
.try_into()
.map_err(RecursiveError::e2e_identity("creating E2eiSignatureKeypair"))?,
);
let sign_keypair = self.new_sign_keypair(ciphersuite).await?;
let existing_identity = cb
.to_mls_credential_with_key()
.extract_identity(ciphersuite, None)
Expand All @@ -107,7 +121,7 @@ impl TransactionContext {
expiry_sec,
&mls_provider,
ciphersuite,
sign_keypair,
Some(sign_keypair),
true, /* Since we are renewing an e2ei certificate we MUST have already generated one hence we MUST
* already have done an OIDC authn and gotten a refresh token from it */
)
Expand Down
6 changes: 3 additions & 3 deletions keystore-dump/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ async fn main() -> anyhow::Result<()> {
core_crypto::Ciphersuite::try_from(cred.ciphersuite)
.expect("ciphersuite from db")
.signature_algorithm(),
cred.secret_key.to_owned(),
cred.private_key.to_owned(),
cred.public_key.to_owned(),
);
let date = chrono::Utc
Expand All @@ -62,11 +62,11 @@ async fn main() -> anyhow::Result<()> {
.ok_or_else(|| anyhow!("Cannot parse credential creation date"))?;

credentials.push(serde_json::json!({
"id": cred.id,
"session_id": cred.session_id,
"credential": mls_credential,
"created_at": date,
"mls_keypair": mls_keypair,
"secret_key": cred.secret_key,
"private_key": cred.private_key,
"public_key": cred.public_key,
}));
}
Expand Down
4 changes: 3 additions & 1 deletion keystore/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ js-sys = "0.3"
web-sys = { version = "0.3", features = ["console"] }
wasm-bindgen = "0.2"
serde-wasm-bindgen = "0.6"
indexmap.workspace = true
# Async WASM stuff
wasm-bindgen-futures = "0.4"
# Crypto stuff
Expand Down Expand Up @@ -113,6 +112,9 @@ smol.workspace = true
version = "0.8"
features = ["async_futures", "html_reports"]

[target.'cfg(target_family = "wasm")'.dev-dependencies]
console_error_panic_hook = "0.1"

[package.metadata.wasm-pack.profile.release]
wasm-opt = [
"-Os",
Expand Down
8 changes: 0 additions & 8 deletions keystore/src/connection/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -392,14 +392,6 @@ impl Database {
.remove_pending_messages_by_conversation_id(conversation_id)
.await
}

pub async fn cred_delete_by_credential(&self, cred: Vec<u8>) -> CryptoKeystoreResult<()> {
let transaction_guard = self.transaction.lock().await;
let Some(transaction) = transaction_guard.as_ref() else {
return Err(CryptoKeystoreError::MutatingOperationWithoutTransaction);
};
transaction.cred_delete_by_credential(cred).await
}
}

#[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,21 +49,21 @@ pub(crate) fn meta_migration(conn: &mut rusqlite::Connection) -> CryptoKeystoreR
if let Some(c) = migrate_to_new_credential(&v5, &kp)? {
tx.execute(
"INSERT INTO mls_credentials_new (
id,
session_id,
credential,
created_at,
signature_scheme,
public_key,
secret_key
private_key
)
VALUES (?1, ?2, datetime(?3, 'unixepoch'), ?4, ?5, ?6)",
(
c.id.clone(),
c.session_id.clone(),
c.credential.clone(),
c.created_at,
c.signature_scheme,
c.public_key.clone(),
c.secret_key.clone(),
c.private_key.clone(),
),
)?;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,47 +26,47 @@ pub(crate) fn meta_migration(conn: &mut rusqlite::Connection) -> CryptoKeystoreR

let mut credential_stmt = tx.prepare(&format!(
"SELECT
id,
session_id,
credential,
unixepoch(created_at) AS created_at,
signature_scheme,
public_key,
secret_key
private_key
FROM {credential_table}",
credential_table = StoredCredential::COLLECTION_NAME,
))?;

let mut rows = credential_stmt.query([])?;
while let Some(row) = rows.next()? {
let v6 = V6Credential {
id: row.get("id")?,
session_id: row.get("session_id")?,
credential: row.get("credential")?,
created_at: row.get("created_at")?,
signature_scheme: row.get("signature_scheme")?,
public_key: row.get("public_key")?,
secret_key: row.get("secret_key")?,
private_key: row.get("private_key")?,
};

// Insert the new credential into temporary mls_credentials_new table, that will be renamed in the next
// migration
if let Some(ciphersuite) = ciphersuite_for_signature_scheme(v6.signature_scheme) {
tx.execute(
"INSERT INTO mls_credentials_new (
id,
session_id,
credential,
created_at,
ciphersuite,
public_key,
secret_key
private_key
)
VALUES (?1, ?2, datetime(?3, 'unixepoch'), ?4, ?5, ?6)",
(
v6.id.clone(),
v6.session_id.clone(),
v6.credential.clone(),
v6.created_at,
ciphersuite,
v6.public_key.clone(),
v6.secret_key.clone(),
v6.private_key.clone(),
),
)?;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@ pub(crate) fn meta_migration(conn: &mut rusqlite::Connection) -> CryptoKeystoreR
Ok(StoredCredential {
ciphersuite: row.get("ciphersuite")?,
public_key: row.get("public_key")?,
id: Vec::new(), // not relevant for this application
credential: Vec::new(), // not relevant for this application
created_at: 0, // not relevant for this application
secret_key: Vec::new(), // not relevant for this application
session_id: Vec::new(), // not relevant for this application
credential: Vec::new(), // not relevant for this application
created_at: 0, // not relevant for this application
private_key: Vec::new(), // not relevant for this application
})
})?
.filter_map(|row| row.ok())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
CREATE TABLE mls_credentials_new (
id BLOB NOT NULL,
session_id BLOB NOT NULL,
credential BLOB NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
signature_scheme INT NOT NULL,
public_key BLOB NOT NULL,
secret_key BLOB NOT NULL
private_key BLOB NOT NULL
);

Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
CREATE TABLE mls_credentials_new (
id BLOB NOT NULL,
session_id BLOB NOT NULL,
credential BLOB NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
ciphersuite INT NOT NULL,
public_key BLOB NOT NULL,
secret_key BLOB NOT NULL
private_key BLOB NOT NULL
);
Loading
Loading