Skip to main content

bitwarden_unlock/
session_key.rs

1use std::{fmt::Display, str::FromStr};
2
3use bitwarden_crypto::{
4    CryptoError, KeySlotIds, KeyStoreContext, SymmetricCryptoKey, SymmetricKeyAlgorithm,
5    safe::{SymmetricKeyEnvelope, SymmetricKeyEnvelopeError, SymmetricKeyEnvelopeNamespace},
6};
7
8/// A symmetric key that wraps the user key in the persisted state, allowing a
9/// rehydrated client to unlock without re-deriving the user key from a master
10/// password or other primary unlock factor.
11///
12/// Callers are responsible for storing this key in a secure location outside
13/// the SDK (e.g. the OS keychain) and providing it back to
14/// [`UnlockClient::unlock`](crate::UnlockClient::unlock) when reconstructing
15/// the client.
16#[derive(PartialEq, Clone)] // This is ok because SymmetricCryptoKey implements PartialEq with constant-time equality checks.
17pub struct SessionKey(pub(crate) SymmetricCryptoKey);
18
19impl SessionKey {
20    /// Mint a new random session key.
21    pub fn make() -> Self {
22        Self(SymmetricCryptoKey::make(
23            SymmetricKeyAlgorithm::XChaCha20Poly1305,
24        ))
25    }
26
27    /// Mint a new session key, seal `key_to_seal` (already present in `ctx`)
28    /// with it, and return both the envelope and the new session key.
29    pub fn from_context<Ids: KeySlotIds>(
30        key_to_seal: Ids::Symmetric,
31        ctx: &mut KeyStoreContext<Ids>,
32    ) -> Result<(SymmetricKeyEnvelope, SessionKey), SymmetricKeyEnvelopeError> {
33        let session_key = SessionKey::make();
34        let session_key_id = ctx.add_local_symmetric_key(session_key.0.clone());
35        let envelope = SymmetricKeyEnvelope::seal(
36            key_to_seal,
37            session_key_id,
38            SymmetricKeyEnvelopeNamespace::SessionKey,
39            ctx,
40        )?;
41        Ok((envelope, session_key))
42    }
43
44    /// Unseal `envelope` using this session key and place the resulting key in
45    /// `ctx`, returning the local id under which it is registered.
46    pub fn unwrap_to_context<Ids: KeySlotIds>(
47        &self,
48        envelope: &SymmetricKeyEnvelope,
49        ctx: &mut KeyStoreContext<Ids>,
50    ) -> Result<Ids::Symmetric, SymmetricKeyEnvelopeError> {
51        let session_key_id = ctx.add_local_symmetric_key(self.0.clone());
52        envelope.unseal(
53            session_key_id,
54            SymmetricKeyEnvelopeNamespace::SessionKey,
55            ctx,
56        )
57    }
58}
59
60impl FromStr for SessionKey {
61    type Err = CryptoError;
62
63    fn from_str(s: &str) -> Result<Self, Self::Err> {
64        Ok(SessionKey(s.parse()?))
65    }
66}
67
68impl Display for SessionKey {
69    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70        self.0.to_base64().fmt(f)
71    }
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77
78    #[test]
79    fn from_str_roundtrip_recovers_session_key() {
80        let original = SessionKey::make();
81        let encoded = original.0.to_base64().to_string();
82
83        let parsed: SessionKey = encoded.parse().unwrap();
84        assert!(parsed == original);
85    }
86
87    #[test]
88    fn from_str_rejects_invalid_base64() {
89        let result: Result<SessionKey, _> = "not-a-valid-key".parse();
90        assert!(matches!(result, Err(CryptoError::InvalidKey)));
91    }
92}