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(SymmetricKeyAlgorithm::XAes256Gcm))
23    }
24
25    /// Mint a new session key, seal `key_to_seal` (already present in `ctx`)
26    /// with it, and return both the envelope and the new session key.
27    pub fn from_context<Ids: KeySlotIds>(
28        key_to_seal: Ids::Symmetric,
29        ctx: &mut KeyStoreContext<Ids>,
30    ) -> Result<(SymmetricKeyEnvelope, SessionKey), SymmetricKeyEnvelopeError> {
31        let session_key = SessionKey::make();
32        let session_key_id = ctx.add_local_symmetric_key(session_key.0.clone());
33        let envelope = SymmetricKeyEnvelope::seal(
34            key_to_seal,
35            session_key_id,
36            SymmetricKeyEnvelopeNamespace::SessionKey,
37            ctx,
38        )?;
39        Ok((envelope, session_key))
40    }
41
42    /// Unseal `envelope` using this session key and place the resulting key in
43    /// `ctx`, returning the local id under which it is registered.
44    pub fn unwrap_to_context<Ids: KeySlotIds>(
45        &self,
46        envelope: &SymmetricKeyEnvelope,
47        ctx: &mut KeyStoreContext<Ids>,
48    ) -> Result<Ids::Symmetric, SymmetricKeyEnvelopeError> {
49        let session_key_id = ctx.add_local_symmetric_key(self.0.clone());
50        envelope.unseal(
51            session_key_id,
52            SymmetricKeyEnvelopeNamespace::SessionKey,
53            ctx,
54        )
55    }
56}
57
58impl FromStr for SessionKey {
59    type Err = CryptoError;
60
61    fn from_str(s: &str) -> Result<Self, Self::Err> {
62        Ok(SessionKey(s.parse()?))
63    }
64}
65
66impl Display for SessionKey {
67    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
68        self.0.to_base64().fmt(f)
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75
76    #[test]
77    fn make_uses_xaes256_gcm() {
78        assert!(matches!(
79            SessionKey::make().0,
80            SymmetricCryptoKey::XAes256GcmKey(_)
81        ));
82    }
83
84    #[test]
85    fn from_str_roundtrip_recovers_session_key() {
86        let original = SessionKey::make();
87        let encoded = original.0.to_base64().to_string();
88
89        let parsed: SessionKey = encoded.parse().unwrap();
90        assert!(parsed == original);
91    }
92
93    #[test]
94    fn from_str_rejects_invalid_base64() {
95        let result: Result<SessionKey, _> = "not-a-valid-key".parse();
96        assert!(matches!(result, Err(CryptoError::InvalidKey)));
97    }
98}