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 bitwarden_crypto::{
26    CoseSerializable, CoseSign1Bytes, CryptoError, EncodingError, KeyIds, KeyStoreContext,
27    SignedObject, SigningNamespace, VerifyingKey,
28};
29use bitwarden_encoding::{FromStrVisitor, B64};
30use serde::{Deserialize, Serialize};
31
32use crate::UserId;
33
34#[cfg(feature = "wasm")]
35#[wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section)]
36const TS_CUSTOM_TYPES: &'static str = r#"
37export type SignedSecurityState = string;
38"#;
39
40/// The security state is a signed object attesting to the security state of a user.
41///
42/// It contains a version, which can only ever increment. Based on the version, old formats and
43/// features are blocked. This prevents a server from downgrading a user's account features, because
44/// only the user can create this signed object.
45#[derive(Debug, Clone, Serialize, Deserialize)]
46#[serde(rename_all = "camelCase")]
47pub struct SecurityState {
48    /// The entity ID is a permanent, unchangeable, unique identifier for the object this security
49    /// state applies to. For users, this is the user ID, which never changes.
50    entity_id: UserId,
51    /// The version of the security state gates feature availability. It can only ever be
52    /// incremented. Components can use it to gate format support of specific formats (like
53    /// item url hashes).
54    version: u64,
55}
56
57impl SecurityState {
58    /// Initialize a new `SecurityState` for the given user ID, to the lowest version possible.
59    /// The user needs to be a v2 encryption user.
60    pub fn initialize_for_user(user_id: UserId) -> Self {
61        SecurityState {
62            entity_id: user_id,
63            version: 2,
64        }
65    }
66
67    /// Returns the version of the security state
68    pub fn version(&self) -> u64 {
69        self.version
70    }
71
72    /// Signs the `SecurityState` with the provided signing key ID from the context.
73    pub fn sign<Ids: KeyIds>(
74        &self,
75        signing_key_id: Ids::Signing,
76        ctx: &mut KeyStoreContext<Ids>,
77    ) -> Result<SignedSecurityState, CryptoError> {
78        Ok(SignedSecurityState(ctx.sign(
79            signing_key_id,
80            &self,
81            &SigningNamespace::SecurityState,
82        )?))
83    }
84}
85
86/// A signed and serialized `SecurityState` object.
87#[derive(Clone, Debug)]
88pub struct SignedSecurityState(pub(crate) SignedObject);
89
90impl SignedSecurityState {
91    /// Verifies the signature of the `SignedSecurityState` using the provided `VerifyingKey`.
92    pub fn verify_and_unwrap(
93        self,
94        verifying_key: &VerifyingKey,
95    ) -> Result<SecurityState, CryptoError> {
96        self.0
97            .verify_and_unwrap(verifying_key, &SigningNamespace::SecurityState)
98    }
99}
100
101impl From<SignedSecurityState> for CoseSign1Bytes {
102    fn from(val: SignedSecurityState) -> Self {
103        val.0.to_cose()
104    }
105}
106
107impl TryFrom<&CoseSign1Bytes> for SignedSecurityState {
108    type Error = EncodingError;
109    fn try_from(bytes: &CoseSign1Bytes) -> Result<Self, EncodingError> {
110        Ok(SignedSecurityState(SignedObject::from_cose(bytes)?))
111    }
112}
113
114impl From<SignedSecurityState> for String {
115    fn from(val: SignedSecurityState) -> Self {
116        let bytes: CoseSign1Bytes = val.into();
117        B64::from(bytes.as_ref()).to_string()
118    }
119}
120
121impl FromStr for SignedSecurityState {
122    type Err = EncodingError;
123
124    fn from_str(s: &str) -> Result<Self, Self::Err> {
125        let bytes = B64::try_from(s).map_err(|_| EncodingError::InvalidBase64Encoding)?;
126        Self::try_from(&CoseSign1Bytes::from(&bytes))
127    }
128}
129
130impl<'de> Deserialize<'de> for SignedSecurityState {
131    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
132    where
133        D: serde::Deserializer<'de>,
134    {
135        deserializer.deserialize_str(FromStrVisitor::new())
136    }
137}
138
139impl serde::Serialize for SignedSecurityState {
140    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
141    where
142        S: serde::Serializer,
143    {
144        let b64_serialized_signed_public_key: String = self.clone().into();
145        serializer.serialize_str(&b64_serialized_signed_public_key)
146    }
147}
148
149#[cfg(test)]
150mod tests {
151    use bitwarden_crypto::{KeyStore, SignatureAlgorithm, SigningKey};
152
153    use super::*;
154    use crate::key_management::{KeyIds, SigningKeyId};
155
156    #[test]
157    fn test_security_state_signing() {
158        let store: KeyStore<KeyIds> = KeyStore::default();
159        let mut ctx = store.context_mut();
160
161        let user_id = UserId::new_v4();
162        let security_state = SecurityState::initialize_for_user(user_id);
163        let signing_key = SigningKey::make(SignatureAlgorithm::Ed25519);
164        #[allow(deprecated)]
165        ctx.set_signing_key(SigningKeyId::Local(""), signing_key.clone())
166            .unwrap();
167        let signed_security_state = security_state
168            .sign(SigningKeyId::Local(""), &mut ctx)
169            .unwrap();
170
171        let verifying_key = signing_key.to_verifying_key();
172        let verified_security_state = signed_security_state
173            .verify_and_unwrap(&verifying_key)
174            .unwrap();
175
176        assert_eq!(verified_security_state.entity_id, user_id);
177        assert_eq!(verified_security_state.version(), 2);
178    }
179}