Skip to main content

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::{fmt::Debug, str::FromStr};
24
25use bitwarden_crypto::{
26    CoseSerializable, CoseSign1Bytes, CryptoError, EncodingError, KeyIds, KeyStoreContext,
27    SignedObject, SigningNamespace, VerifyingKey,
28};
29use bitwarden_encoding::{B64, FromStrVisitor};
30use serde::{Deserialize, Serialize};
31
32/// Icon URI hashes are enforced starting with this security state version.
33pub const MINIMUM_ENFORCE_ICON_URI_HASH_VERSION: u64 = 2;
34
35#[cfg(feature = "wasm")]
36#[wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section)]
37const TS_CUSTOM_TYPES: &'static str = r#"
38export type SignedSecurityState = string;
39"#;
40
41/// The security state is a signed object attesting to the security state of a user.
42///
43/// It contains a version, which can only ever increment. Based on the version, old formats and
44/// features are blocked. This prevents a server from downgrading a user's account features, because
45/// only the user can create this signed object.
46#[derive(Debug, Clone, Serialize, Deserialize)]
47#[serde(rename_all = "camelCase")]
48pub struct SecurityState {
49    /// The version of the security state gates feature availability. It can only ever be
50    /// incremented. Components can use it to gate format support of specific formats (like
51    /// item url hashes).
52    version: u64,
53}
54
55impl Default for SecurityState {
56    fn default() -> Self {
57        Self::new()
58    }
59}
60
61impl SecurityState {
62    /// Initialize a new `SecurityState` for the given user ID, to the lowest version possible.
63    /// The user needs to be a v2 encryption user.
64    pub fn new() -> Self {
65        SecurityState { version: 2 }
66    }
67
68    /// Returns the version of the security state
69    pub fn version(&self) -> u64 {
70        self.version
71    }
72
73    /// Signs the `SecurityState` with the provided signing key ID from the context.
74    pub fn sign<Ids: KeyIds>(
75        &self,
76        signing_key_id: Ids::Signing,
77        ctx: &mut KeyStoreContext<Ids>,
78    ) -> Result<SignedSecurityState, CryptoError> {
79        Ok(SignedSecurityState(ctx.sign(
80            signing_key_id,
81            &self,
82            &SigningNamespace::SecurityState,
83        )?))
84    }
85}
86
87/// A signed and serialized `SecurityState` object.
88#[derive(Clone, PartialEq)]
89pub struct SignedSecurityState(pub(crate) SignedObject);
90
91impl Debug for SignedSecurityState {
92    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
93        let mut debug_struct = f.debug_struct("SignedSecurityState");
94
95        if let Ok(signed_by) = self.0.signed_by_id() {
96            debug_struct.field("signed_by", &signed_by);
97        }
98        if let Some(state) = self
99            .0
100            .dangerous_unverified_decode_do_not_use_except_for_debug_logs::<SecurityState>()
101        {
102            debug_struct.field("version", &state.version);
103        }
104
105        debug_struct.finish()
106    }
107}
108
109impl SignedSecurityState {
110    /// Verifies the signature of the `SignedSecurityState` using the provided `VerifyingKey`.
111    pub fn verify_and_unwrap(
112        self,
113        verifying_key: &VerifyingKey,
114    ) -> Result<SecurityState, CryptoError> {
115        self.0
116            .verify_and_unwrap(verifying_key, &SigningNamespace::SecurityState)
117    }
118}
119
120impl From<SignedSecurityState> for CoseSign1Bytes {
121    fn from(val: SignedSecurityState) -> Self {
122        val.0.to_cose()
123    }
124}
125
126impl TryFrom<&CoseSign1Bytes> for SignedSecurityState {
127    type Error = EncodingError;
128    fn try_from(bytes: &CoseSign1Bytes) -> Result<Self, EncodingError> {
129        Ok(SignedSecurityState(SignedObject::from_cose(bytes)?))
130    }
131}
132
133impl From<&SignedSecurityState> for String {
134    fn from(val: &SignedSecurityState) -> Self {
135        val.to_owned().into()
136    }
137}
138
139impl From<SignedSecurityState> for String {
140    fn from(val: SignedSecurityState) -> Self {
141        let bytes: CoseSign1Bytes = val.into();
142        B64::from(bytes.as_ref()).to_string()
143    }
144}
145
146impl FromStr for SignedSecurityState {
147    type Err = EncodingError;
148
149    fn from_str(s: &str) -> Result<Self, Self::Err> {
150        let bytes = B64::try_from(s).map_err(|_| EncodingError::InvalidBase64Encoding)?;
151        Self::try_from(&CoseSign1Bytes::from(&bytes))
152    }
153}
154
155impl<'de> Deserialize<'de> for SignedSecurityState {
156    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
157    where
158        D: serde::Deserializer<'de>,
159    {
160        deserializer.deserialize_str(FromStrVisitor::new())
161    }
162}
163
164impl serde::Serialize for SignedSecurityState {
165    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
166    where
167        S: serde::Serializer,
168    {
169        let b64_serialized_signed_public_key: String = self.clone().into();
170        serializer.serialize_str(&b64_serialized_signed_public_key)
171    }
172}
173
174#[cfg(test)]
175mod tests {
176    use bitwarden_crypto::{CoseKeyBytes, KeyStore, SignatureAlgorithm, SigningKey};
177
178    use super::*;
179    use crate::key_management::KeyIds;
180
181    const TEST_SIGNED_SECURITY_STATE: &str = "hFgepAEnAxg8BFBHo5ojcqDqbynNymOZGgJzOgABOH8CoFgkomhlbnRpdHlJZFBHmj2OTpBFO7aDLgeNnbZPZ3ZlcnNpb24CWEA4mQbYRRoPpc77tVHH4LlwY52Vz6tutThv8b/BV3ntQmjuKUxbzIGRxSyOhzCn3ouFJGEVnfsl6SqSm6K9XcME";
182    const TEST_VERIFYING_KEY: &str =
183        "pgEBAlBHo5ojcqDqbynNymOZGgJzAycEgQIgBiFYIK9hIvbLIdnzKhykPt8jT/ktXAlzPUfx4Nyx4EYTpIp7";
184
185    #[test]
186    #[ignore = "Manual test for debug logs"]
187    fn test_security_state_debug_logs() {
188        let store: KeyStore<KeyIds> = KeyStore::default();
189        let mut ctx = store.context_mut();
190
191        let security_state = SecurityState::new();
192        let signing_key = SigningKey::make(SignatureAlgorithm::Ed25519);
193        let key = ctx.add_local_signing_key(signing_key.clone());
194        let signed_security_state = security_state.sign(key, &mut ctx).unwrap();
195
196        println!("{:?}", signed_security_state);
197    }
198
199    #[test]
200    #[ignore = "Make test vectors"]
201    fn test_make_test_vector() {
202        let store: KeyStore<KeyIds> = KeyStore::default();
203        let mut ctx = store.context_mut();
204
205        let security_state = SecurityState::new();
206        let signing_key = SigningKey::make(SignatureAlgorithm::Ed25519);
207        let key = ctx.add_local_signing_key(signing_key.clone());
208        let signed_security_state = security_state.sign(key, &mut ctx).unwrap();
209        let verifying_key = signing_key.to_verifying_key();
210
211        println!(
212            "const TEST_SIGNED_SECURITY_STATE: &str = \"{}\";",
213            String::from(&signed_security_state)
214        );
215        println!(
216            "const TEST_VERIFYING_KEY: &str = \"{}\";",
217            B64::from(verifying_key.to_cose())
218        );
219    }
220
221    #[test]
222    fn test_security_state_signing() {
223        let store: KeyStore<KeyIds> = KeyStore::default();
224        let mut ctx = store.context_mut();
225
226        let security_state = SecurityState::new();
227        let signing_key = SigningKey::make(SignatureAlgorithm::Ed25519);
228        let key = ctx.add_local_signing_key(signing_key.clone());
229        let signed_security_state = security_state.sign(key, &mut ctx).unwrap();
230
231        let verifying_key = signing_key.to_verifying_key();
232        let verified_security_state = signed_security_state
233            .verify_and_unwrap(&verifying_key)
234            .unwrap();
235
236        assert_eq!(verified_security_state.version(), 2);
237    }
238
239    #[test]
240    fn test_stable_testvector() {
241        let b64 = B64::try_from(TEST_VERIFYING_KEY).unwrap();
242        let verifying_key = VerifyingKey::from_cose(&CoseKeyBytes::from(&b64)).unwrap();
243        let signed_security_state =
244            SignedSecurityState::from_str(TEST_SIGNED_SECURITY_STATE).unwrap();
245        let verified_security_state = signed_security_state
246            .verify_and_unwrap(&verifying_key)
247            .unwrap();
248
249        assert_eq!(verified_security_state.version(), 2);
250    }
251}