bitwarden_emergency_access/
confirm.rs1use bitwarden_api_api::models::OrganizationUserConfirmRequestModel;
2use bitwarden_core::{ApiError, key_management::SymmetricKeySlotId};
3use bitwarden_crypto::{CryptoError, PublicKey, SpkiPublicKeyBytes, UnsignedSharedKey};
4use bitwarden_encoding::B64;
5use bitwarden_error::bitwarden_error;
6use thiserror::Error;
7
8use crate::{EmergencyAccessClient, EmergencyAccessId};
9
10#[bitwarden_error(flat)]
12#[derive(Debug, Error)]
13pub enum EmergencyAccessConfirmError {
14 #[error(transparent)]
16 Api(#[from] ApiError),
17 #[error(transparent)]
19 Crypto(#[from] CryptoError),
20}
21
22#[bitwarden_ffi::wasm_export]
23impl EmergencyAccessClient {
24 pub async fn confirm(
31 &self,
32 emergency_access_id: EmergencyAccessId,
33 grantee_public_key: B64,
34 ) -> Result<(), EmergencyAccessConfirmError> {
35 let grantee_public_key =
37 PublicKey::from_der(&SpkiPublicKeyBytes::from(&grantee_public_key))?;
38
39 let key = UnsignedSharedKey::encapsulate(
41 SymmetricKeySlotId::User,
42 &grantee_public_key,
43 &self.key_store.context(),
44 )?;
45
46 let request = OrganizationUserConfirmRequestModel {
48 key: key.to_string(),
49 default_user_collection_name: None,
50 };
51
52 self.api_configurations
53 .api_client
54 .emergency_access_api()
55 .confirm(emergency_access_id.into(), Some(request))
56 .await?;
57
58 Ok(())
59 }
60}
61
62#[cfg(test)]
63mod tests {
64 use std::sync::{Arc, Mutex};
65
66 use bitwarden_core::{
67 Client,
68 client::test_accounts::{test_bitwarden_com_account, test_bitwarden_com_account_v2},
69 key_management::PrivateKeySlotId,
70 };
71
72 use super::*;
73 use crate::{
74 EmergencyAccessClientExt,
75 test_support::{TEST_VECTOR_GRANTOR_KEY, api_error, is_test_id, test_client, test_id},
76 };
77
78 fn public_key_of(client: &Client) -> B64 {
80 let public_key = client
81 .internal
82 .get_key_store()
83 .context()
84 .get_public_key(PrivateKeySlotId::UserPrivateKey)
85 .unwrap();
86
87 public_key.to_der().unwrap().into()
88 }
89
90 #[tokio::test]
91 async fn shares_user_key_with_grantee() {
92 let grantee = Client::init_test_account(test_bitwarden_com_account_v2()).await;
93
94 let sent_key = Arc::new(Mutex::new(None));
95 let captured = sent_key.clone();
96 let grantor = test_client(test_bitwarden_com_account(), move |mock| {
97 mock.emergency_access_api
98 .expect_confirm()
99 .withf(|id, _| is_test_id(id))
100 .returning(move |_, request| {
101 *captured.lock().unwrap() = request.map(|r| r.key);
102 Ok(())
103 })
104 .once();
105 })
106 .await;
107
108 grantor
109 .emergency_access()
110 .confirm(test_id(), public_key_of(&grantee))
111 .await
112 .unwrap();
113
114 let sent_key: UnsignedSharedKey = sent_key.lock().unwrap().take().unwrap().parse().unwrap();
117 let recorded_key: UnsignedSharedKey = TEST_VECTOR_GRANTOR_KEY.parse().unwrap();
118
119 let key_store = grantee.internal.get_key_store();
120 let mut ctx = key_store.context();
121 let sent = sent_key
122 .decapsulate(PrivateKeySlotId::UserPrivateKey, &mut ctx)
123 .unwrap();
124 let recorded = recorded_key
125 .decapsulate(PrivateKeySlotId::UserPrivateKey, &mut ctx)
126 .unwrap();
127
128 #[allow(deprecated)]
129 let (sent, recorded) = (
130 ctx.dangerous_get_symmetric_key(sent).unwrap(),
131 ctx.dangerous_get_symmetric_key(recorded).unwrap(),
132 );
133 assert_eq!(sent, recorded);
134 }
135
136 #[tokio::test]
137 async fn fails_when_public_key_is_malformed() {
138 let grantor = test_client(test_bitwarden_com_account(), |mock| {
139 mock.emergency_access_api.expect_confirm().never();
140 })
141 .await;
142
143 let result = grantor
144 .emergency_access()
145 .confirm(test_id(), B64::from(b"not a key".as_slice()))
146 .await;
147
148 assert!(matches!(
149 result,
150 Err(EmergencyAccessConfirmError::Crypto(_))
151 ));
152 }
153
154 #[tokio::test]
155 async fn fails_when_request_fails() {
156 let grantee = Client::init_test_account(test_bitwarden_com_account_v2()).await;
157 let grantor = test_client(test_bitwarden_com_account(), |mock| {
158 mock.emergency_access_api
159 .expect_confirm()
160 .returning(|_, _| api_error())
161 .once();
162 })
163 .await;
164
165 let result = grantor
166 .emergency_access()
167 .confirm(test_id(), public_key_of(&grantee))
168 .await;
169
170 assert!(matches!(result, Err(EmergencyAccessConfirmError::Api(_))));
171 }
172}