core_crypto/transaction_context/credential/
mod.rs1mod check;
2
3use std::sync::Arc;
4
5use super::{Error, Result};
6use crate::{Credential, CredentialRef, RecursiveError, transaction_context::TransactionContext};
7
8impl TransactionContext {
9 pub(crate) fn add_credential_without_clientid_check(&self, mut credential: Credential) -> Result<Arc<Credential>> {
16 let inner = self.inner()?;
17 let _credential_ref = credential
18 .save(&inner.transaction)
19 .map_err(RecursiveError::context("saving credential"))?;
20
21 Ok(Arc::new(credential))
22 }
23 pub async fn add_credential(&self, credential: Credential) -> Result<CredentialRef> {
25 let credential = self.add_credential_producing_arc(credential).await?;
26 Ok(CredentialRef::from_credential(&credential))
27 }
28
29 pub(crate) async fn add_credential_producing_arc(&self, credential: Credential) -> Result<Arc<Credential>> {
35 if *credential.client_id() != self.session().await?.id() {
36 return Err(Error::WrongCredential);
37 }
38
39 self.add_credential_without_clientid_check(credential)
40 }
41
42 pub async fn remove_credential(&self, credential_ref: &CredentialRef) -> Result<bool> {
47 if *credential_ref.client_id() != self.session().await?.id() {
49 return Err(Error::WrongCredential);
50 }
51
52 let inner = self.inner()?;
53
54 let removed = credential_ref.delete(&inner.transaction).map_err(|err| -> Error {
55 let crate::KeystoreError { source, .. } = &err;
56 let Some(core_crypto_keystore::CryptoKeystoreError::DbError(rusqlite_err)) = source.downcast_ref() else {
57 return err.into();
58 };
59 if rusqlite_err
60 .sqlite_error_code()
61 .is_some_and(|code| code == core_crypto_keystore::RusqliteErrorCode::ConstraintViolation)
62 {
63 Error::CredentialStillInUse
64 } else {
65 err.into()
66 }
67 })?;
68
69 Ok(removed)
70 }
71}
72
73#[cfg(test)]
74mod tests {
75 use core_crypto_keystore::{
76 entities::{StoredCredential, StoredCredentialPk, StoredKeyPackage},
77 traits::FetchFromDatabase as _,
78 };
79
80 use super::*;
81 use crate::{CertificateBundle, test_utils::*};
82
83 async fn credential_is_stored(session: &SessionContext, credential: &CredentialRef) -> bool {
84 session
85 .transaction
86 .database()
87 .unwrap()
88 .get::<StoredCredential>(&StoredCredentialPk::new(
89 credential.public_key_hash(),
90 credential.r#type().into(),
91 ))
92 .await
93 .unwrap()
94 .is_some()
95 }
96
97 async fn count_key_packages_for(session: &SessionContext, credential: &CredentialRef) -> usize {
99 session
100 .transaction
101 .database()
102 .unwrap()
103 .search::<StoredKeyPackage, _>(&credential.keystore_pk())
104 .await
105 .unwrap()
106 .len()
107 }
108
109 #[apply(all_cred_cipher)]
113 async fn credential_in_use_by_a_conversation_cannot_be_removed(case: TestContext) {
114 const KEY_PACKAGES: usize = 2;
115
116 let [alice] = case.sessions().await;
117 let conversation = case.create_conversation([&alice]).await;
118 let credential = alice.initial_credential.clone();
119
120 for _ in 0..KEY_PACKAGES {
121 alice.new_keypackage_from_ref(credential.clone(), None).await;
122 }
123 assert_eq!(count_key_packages_for(&alice, &credential).await, KEY_PACKAGES);
124
125 let result = alice.transaction.remove_credential(&credential).await;
126 assert!(
127 matches!(result, Err(Error::CredentialStillInUse)),
128 "removing a credential in use must fail with `CredentialStillInUse`, but got {result:?}"
129 );
130
131 assert!(
134 credential_is_stored(&alice, &credential).await,
135 "a refused removal must keep the credential"
136 );
137 assert_eq!(
138 count_key_packages_for(&alice, &credential).await,
139 KEY_PACKAGES,
140 "a refused removal must keep the key packages generated from the credential"
141 );
142
143 if !case.is_x509() {
146 return;
147 }
148
149 let intermediate_ca = alice.x509_chain_unchecked().find_local_intermediate_ca();
150 let certificate = CertificateBundle::new_with_default_values(intermediate_ca, None);
151 let new_credential = Credential::x509(case.cipher_suite(), certificate).unwrap();
152 alice
153 .transaction
154 .add_credential_without_clientid_check(new_credential.clone())
155 .unwrap();
156 let new_credential = CredentialRef::from_credential(&new_credential);
157 conversation.set_credential_by_ref_notify(&new_credential).await;
158
159 let removed = alice
160 .transaction
161 .remove_credential(&credential)
162 .await
163 .expect("a credential no conversation uses any more can be removed");
164 assert!(removed, "removal must report that the credential was deleted");
165 assert!(!credential_is_stored(&alice, &credential).await);
166 assert_eq!(count_key_packages_for(&alice, &credential).await, 0);
167 }
168}