bitwarden_core/key_management/
security_state.rs1use std::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
32use crate::UserId;
33
34pub const MINIMUM_ENFORCE_ICON_URI_HASH_VERSION: u64 = 2;
36
37#[cfg(feature = "wasm")]
38#[wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section)]
39const TS_CUSTOM_TYPES: &'static str = r#"
40export type SignedSecurityState = string;
41"#;
42
43#[derive(Debug, Clone, Serialize, Deserialize)]
49#[serde(rename_all = "camelCase")]
50pub struct SecurityState {
51 entity_id: UserId,
54 version: u64,
58}
59
60impl SecurityState {
61 pub fn initialize_for_user(user_id: UserId) -> Self {
64 SecurityState {
65 entity_id: user_id,
66 version: 2,
67 }
68 }
69
70 pub fn version(&self) -> u64 {
72 self.version
73 }
74
75 pub fn sign<Ids: KeyIds>(
77 &self,
78 signing_key_id: Ids::Signing,
79 ctx: &mut KeyStoreContext<Ids>,
80 ) -> Result<SignedSecurityState, CryptoError> {
81 Ok(SignedSecurityState(ctx.sign(
82 signing_key_id,
83 &self,
84 &SigningNamespace::SecurityState,
85 )?))
86 }
87}
88
89#[derive(Clone, Debug)]
91pub struct SignedSecurityState(pub(crate) SignedObject);
92
93impl SignedSecurityState {
94 pub fn verify_and_unwrap(
96 self,
97 verifying_key: &VerifyingKey,
98 ) -> Result<SecurityState, CryptoError> {
99 self.0
100 .verify_and_unwrap(verifying_key, &SigningNamespace::SecurityState)
101 }
102}
103
104impl From<SignedSecurityState> for CoseSign1Bytes {
105 fn from(val: SignedSecurityState) -> Self {
106 val.0.to_cose()
107 }
108}
109
110impl TryFrom<&CoseSign1Bytes> for SignedSecurityState {
111 type Error = EncodingError;
112 fn try_from(bytes: &CoseSign1Bytes) -> Result<Self, EncodingError> {
113 Ok(SignedSecurityState(SignedObject::from_cose(bytes)?))
114 }
115}
116
117impl From<&SignedSecurityState> for String {
118 fn from(val: &SignedSecurityState) -> Self {
119 val.to_owned().into()
120 }
121}
122
123impl From<SignedSecurityState> for String {
124 fn from(val: SignedSecurityState) -> Self {
125 let bytes: CoseSign1Bytes = val.into();
126 B64::from(bytes.as_ref()).to_string()
127 }
128}
129
130impl FromStr for SignedSecurityState {
131 type Err = EncodingError;
132
133 fn from_str(s: &str) -> Result<Self, Self::Err> {
134 let bytes = B64::try_from(s).map_err(|_| EncodingError::InvalidBase64Encoding)?;
135 Self::try_from(&CoseSign1Bytes::from(&bytes))
136 }
137}
138
139impl<'de> Deserialize<'de> for SignedSecurityState {
140 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
141 where
142 D: serde::Deserializer<'de>,
143 {
144 deserializer.deserialize_str(FromStrVisitor::new())
145 }
146}
147
148impl serde::Serialize for SignedSecurityState {
149 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
150 where
151 S: serde::Serializer,
152 {
153 let b64_serialized_signed_public_key: String = self.clone().into();
154 serializer.serialize_str(&b64_serialized_signed_public_key)
155 }
156}
157
158#[cfg(test)]
159mod tests {
160 use bitwarden_crypto::{KeyStore, SignatureAlgorithm, SigningKey};
161
162 use super::*;
163 use crate::key_management::KeyIds;
164
165 #[test]
166 fn test_security_state_signing() {
167 let store: KeyStore<KeyIds> = KeyStore::default();
168 let mut ctx = store.context_mut();
169
170 let user_id = UserId::new_v4();
171 let security_state = SecurityState::initialize_for_user(user_id);
172 let signing_key = SigningKey::make(SignatureAlgorithm::Ed25519);
173 let key = ctx.add_local_signing_key(signing_key.clone()).unwrap();
174 let signed_security_state = security_state.sign(key, &mut ctx).unwrap();
175
176 let verifying_key = signing_key.to_verifying_key();
177 let verified_security_state = signed_security_state
178 .verify_and_unwrap(&verifying_key)
179 .unwrap();
180
181 assert_eq!(verified_security_state.entity_id, user_id);
182 assert_eq!(verified_security_state.version(), 2);
183 }
184}