bitwarden_core/key_management/
security_state.rs

1//! Security state is a signed object that attests to a user's (or later an organization's) security
2//! state. The security goal is to prevent downgrades of specific features within the user's account
3//! by the server / a networked attacker with TLS introspection access.
4//!
5//! A security state contains a security version. Based on this version, features can be disabled.
6//! Since the server cannot sign a security state, it can no longer downgrade the feature, because
7//! it cannot produce an arbitrary valid signed security state.
8//!
9//! Note: A long-term compromised server can record the security state of a user, and then replay
10//! this specific state, or the entire account to downgrade users to previous states. This can be
11//! prevented per logged in session by the client, and for bootstrapping a client by
12//! using an extended login-with-device protocol.
13//!
14//! To utilize the security state to disable a feature the following steps are taken:
15//! 1. Assume: Feature with format version A is insecure, and cannot be changed by simple mutation
16//! 2. A new, safe format version B is introduced, and an upgrade path created
17//! 3. The upgrade path is made mandatory
18//! 4. After upgrades are run, the sdk validates that all items are in format version B, and the
19//!    security state can be updated to contain the security version N+1
20//! 5. The client, given a security state with security version N+1 will reject all items that are
21//!    in format version A.
22
23use std::str::FromStr;
24
25use base64::{engine::general_purpose::STANDARD, Engine};
26use bitwarden_crypto::{
27    CoseSerializable, CoseSign1Bytes, CryptoError, EncodingError, FromStrVisitor, KeyIds,
28    KeyStoreContext, SignedObject, SigningNamespace, VerifyingKey,
29};
30use serde::{Deserialize, Serialize};
31use uuid::Uuid;
32
33#[cfg(feature = "wasm")]
34#[wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section)]
35const TS_CUSTOM_TYPES: &'static str = r#"
36export type SignedSecurityState = string;
37"#;
38
39/// The security state is a signed object attesting to the security state of a user.
40///
41/// It contains a version, which can only ever increment. Based on the version, old formats and
42/// features are blocked. This prevents a server from downgrading a user's account features, because
43/// only the user can create this signed object.
44#[derive(Debug, Clone, Serialize, Deserialize)]
45#[serde(rename_all = "camelCase")]
46pub struct SecurityState {
47    /// The entity ID is a permanent, unchangeable, unique identifier for the object this security
48    /// state applies to. For users, this is the user ID, which never changes.
49    entity_id: Uuid,
50    /// The version of the security state gates feature availability. It can only ever be
51    /// incremented. Components can use it to gate format support of specific formats (like
52    /// item url hashes).
53    version: u64,
54}
55
56impl SecurityState {
57    /// Initialize a new `SecurityState` for the given user ID, to the lowest version possible.
58    /// The user needs to be a v2 encryption user.
59    pub fn initialize_for_user(user_id: uuid::Uuid) -> Self {
60        SecurityState {
61            entity_id: user_id,
62            version: 2,
63        }
64    }
65
66    /// Returns the version of the security state
67    pub fn version(&self) -> u64 {
68        self.version
69    }
70
71    /// Signs the `SecurityState` with the provided signing key ID from the context.
72    pub fn sign<Ids: KeyIds>(
73        &self,
74        signing_key_id: Ids::Signing,
75        ctx: &mut KeyStoreContext<Ids>,
76    ) -> Result<SignedSecurityState, CryptoError> {
77        Ok(SignedSecurityState(ctx.sign(
78            signing_key_id,
79            &self,
80            &SigningNamespace::SecurityState,
81        )?))
82    }
83}
84
85/// A signed and serialized `SecurityState` object.
86#[derive(Clone, Debug)]
87pub struct SignedSecurityState(pub(crate) SignedObject);
88
89impl SignedSecurityState {
90    /// Verifies the signature of the `SignedSecurityState` using the provided `VerifyingKey`.
91    pub fn verify_and_unwrap(
92        self,
93        verifying_key: &VerifyingKey,
94    ) -> Result<SecurityState, CryptoError> {
95        self.0
96            .verify_and_unwrap(verifying_key, &SigningNamespace::SecurityState)
97    }
98}
99
100impl From<SignedSecurityState> for CoseSign1Bytes {
101    fn from(val: SignedSecurityState) -> Self {
102        val.0.to_cose()
103    }
104}
105
106impl TryFrom<&CoseSign1Bytes> for SignedSecurityState {
107    type Error = EncodingError;
108    fn try_from(bytes: &CoseSign1Bytes) -> Result<Self, EncodingError> {
109        Ok(SignedSecurityState(SignedObject::from_cose(bytes)?))
110    }
111}
112
113impl From<SignedSecurityState> for String {
114    fn from(val: SignedSecurityState) -> Self {
115        let bytes: CoseSign1Bytes = val.into();
116        STANDARD.encode(&bytes)
117    }
118}
119
120impl FromStr for SignedSecurityState {
121    type Err = EncodingError;
122
123    fn from_str(s: &str) -> Result<Self, Self::Err> {
124        let bytes = STANDARD
125            .decode(s)
126            .map_err(|_| EncodingError::InvalidBase64Encoding)?;
127        Self::try_from(&CoseSign1Bytes::from(bytes))
128    }
129}
130
131impl<'de> Deserialize<'de> for SignedSecurityState {
132    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
133    where
134        D: serde::Deserializer<'de>,
135    {
136        deserializer.deserialize_str(FromStrVisitor::new())
137    }
138}
139
140impl serde::Serialize for SignedSecurityState {
141    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
142    where
143        S: serde::Serializer,
144    {
145        let b64_serialized_signed_public_key: String = self.clone().into();
146        serializer.serialize_str(&b64_serialized_signed_public_key)
147    }
148}
149
150#[cfg(test)]
151mod tests {
152    use bitwarden_crypto::{KeyStore, SignatureAlgorithm, SigningKey};
153
154    use super::*;
155    use crate::key_management::{KeyIds, SigningKeyId};
156
157    #[test]
158    fn test_security_state_signing() {
159        let store: KeyStore<KeyIds> = KeyStore::default();
160        let mut ctx = store.context_mut();
161
162        let user_id = uuid::Uuid::new_v4();
163        let security_state = SecurityState::initialize_for_user(user_id);
164        let signing_key = SigningKey::make(SignatureAlgorithm::Ed25519);
165        #[allow(deprecated)]
166        ctx.set_signing_key(SigningKeyId::Local(""), signing_key.clone())
167            .unwrap();
168        let signed_security_state = security_state
169            .sign(SigningKeyId::Local(""), &mut ctx)
170            .unwrap();
171
172        let verifying_key = signing_key.to_verifying_key();
173        let verified_security_state = signed_security_state
174            .verify_and_unwrap(&verifying_key)
175            .unwrap();
176
177        assert_eq!(verified_security_state.entity_id, user_id);
178        assert_eq!(verified_security_state.version(), 2);
179    }
180}