Skip to main content

bitwarden_access_token_crypto/
key.rs

1//! The symmetric key derived from an access-token seed.
2
3use std::fmt;
4
5use bitwarden_crypto::{
6    BitwardenLegacyKeyBytes, EncString, KeyDecryptable, KeySlotIds, KeyStoreContext,
7    SymmetricCryptoKey, derive_shareable_key,
8};
9use bitwarden_encoding::B64;
10use serde::Deserialize;
11use zeroize::Zeroizing;
12
13use crate::{AccessTokenError, AccessTokenSeed, purpose::KeyPurpose};
14
15/// Becomes the HKDF salt `bitwarden-accesstoken`, shared by every [`KeyPurpose`].
16pub(crate) const DERIVE_NAME: &str = "accesstoken";
17
18/// A key derived from an [`AccessTokenSeed`] for one [`KeyPurpose`]. The raw key never leaves this
19/// crate; [`Self::open_payload`] installs what it unwraps straight into a [`KeyStoreContext`].
20pub struct AccessTokenKey(SymmetricCryptoKey);
21
22impl fmt::Debug for AccessTokenKey {
23    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
24        f.debug_tuple("AccessTokenKey")
25            .field(&"[REDACTED]")
26            .finish()
27    }
28}
29
30impl AccessTokenKey {
31    /// Deterministically derives the key for `purpose` from `seed`.
32    pub fn derive(seed: &AccessTokenSeed, purpose: KeyPurpose) -> Self {
33        Self(SymmetricCryptoKey::Aes256CbcHmacKey(derive_shareable_key(
34            Zeroizing::new(*seed.as_bytes()),
35            DERIVE_NAME,
36            Some(purpose.as_str()),
37        )))
38    }
39
40    /// Decrypts `encrypted_payload` and installs the organization key it carries at
41    /// `organization_key` in `ctx`. Errors carry no payload content.
42    pub fn open_payload<Ids: KeySlotIds>(
43        &self,
44        ctx: &mut KeyStoreContext<Ids>,
45        encrypted_payload: &str,
46        organization_key: Ids::Symmetric,
47    ) -> Result<(), AccessTokenError> {
48        let encrypted_payload: EncString = encrypted_payload
49            .parse()
50            .map_err(|_| AccessTokenError::InvalidPayload)?;
51
52        let decrypted: Vec<u8> = encrypted_payload
53            .decrypt_with_key(&self.0)
54            .map_err(|_| AccessTokenError::InvalidPayload)?;
55
56        #[derive(Deserialize)]
57        struct Payload {
58            #[serde(rename = "encryptionKey")]
59            encryption_key: B64,
60        }
61
62        let payload: Payload =
63            serde_json::from_slice(&decrypted).map_err(|_| AccessTokenError::InvalidPayload)?;
64
65        let key_bytes = BitwardenLegacyKeyBytes::from(&payload.encryption_key);
66        let key = SymmetricCryptoKey::try_from(&key_bytes)
67            .map_err(|_| AccessTokenError::InvalidOrgKey)?;
68
69        let local = ctx.add_local_symmetric_key(key);
70        ctx.persist_symmetric_key(local, organization_key)?;
71        Ok(())
72    }
73}
74
75#[cfg(test)]
76impl AccessTokenKey {
77    pub(crate) fn to_base64_for_tests(&self) -> String {
78        self.0.to_base64().to_string()
79    }
80}