1use bitwarden_crypto::{
2 EncString, KeySlotIds, KeyStoreContext, generate_versioned_sealable,
3 safe::{
4 DataEnvelope, DataEnvelopeError, DataEnvelopeNamespace, SealableData, SealableVersionedData,
5 },
6};
7use serde::{Deserialize, Serialize};
8use uuid::Uuid;
9
10use crate::{Sealed, policy::SealedPasswordPolicy};
11
12#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
14#[serde(rename_all = "snake_case")]
15pub enum SealedTargetSystemMethod {
16 Automatic,
18 Manual,
20}
21
22#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash)]
24#[serde(rename_all = "snake_case")]
25pub enum SealedTargetSystemKind {
26 Entra,
28 CustomScript,
30}
31
32#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
35pub struct TargetSystemSealedV1 {
36 pub target_system_id: Uuid,
38 pub method: SealedTargetSystemMethod,
40 pub kind: Option<SealedTargetSystemKind>,
42 pub password_policy: SealedPasswordPolicy,
44 pub supports_session_termination: Option<bool>,
46}
47impl SealableData for TargetSystemSealedV1 {}
48
49generate_versioned_sealable!(
50 TargetSystemSealed,
51 DataEnvelopeNamespace::PamTargetSystem,
52 [
53 TargetSystemSealedV1 => "1",
54 ]
55);
56
57pub fn seal_target_system<Ids: KeySlotIds>(
59 data: TargetSystemSealedV1,
60 org_key: &Ids::Symmetric,
61 ctx: &mut KeyStoreContext<Ids>,
62) -> Result<Sealed, DataEnvelopeError> {
63 let versioned: TargetSystemSealed = data.into();
64 let (envelope, wrapped_key) = DataEnvelope::seal_with_wrapping_key(versioned, org_key, ctx)?;
65 Ok(Sealed {
66 envelope,
67 wrapped_key,
68 })
69}
70
71pub fn unseal_target_system<Ids: KeySlotIds>(
73 envelope: &DataEnvelope,
74 wrapped_key: &EncString,
75 org_key: &Ids::Symmetric,
76 ctx: &mut KeyStoreContext<Ids>,
77) -> Result<TargetSystemSealedV1, DataEnvelopeError> {
78 let versioned: TargetSystemSealed =
79 envelope.unseal_with_wrapping_key(org_key, wrapped_key, ctx)?;
80 let TargetSystemSealed::TargetSystemSealedV1(data) = versioned;
81 Ok(data)
82}
83
84#[cfg(test)]
85mod tests {
86 use std::str::FromStr;
87
88 use bitwarden_crypto::{KeyStore, SymmetricCryptoKey, safe::DataEnvelopeError};
89 use bitwarden_encoding::B64;
90
91 use super::*;
92 use crate::{rotation_config::unseal_rotation_config, test_support::TestIds};
93
94 fn sample() -> TargetSystemSealedV1 {
95 TargetSystemSealedV1 {
96 target_system_id: Uuid::parse_str("8c6e7d1a-3e3f-4a5b-9c1d-2f3e4d5c6b7a").unwrap(),
97 method: SealedTargetSystemMethod::Automatic,
98 kind: Some(SealedTargetSystemKind::Entra),
99 password_policy: SealedPasswordPolicy {
100 min_length: 20,
101 max_length: 20,
102 include_uppercase: true,
103 include_lowercase: true,
104 include_digits: true,
105 include_symbols: true,
106 },
107 supports_session_termination: Some(true),
108 }
109 }
110
111 #[test]
112 fn round_trip() {
113 let store = KeyStore::<TestIds>::default();
114 let mut ctx = store.context_mut();
115 let org_key = ctx.generate_symmetric_key();
116
117 let data = sample();
118 let Sealed {
119 envelope,
120 wrapped_key,
121 } = seal_target_system(data.clone(), &org_key, &mut ctx).unwrap();
122 let unsealed = unseal_target_system(&envelope, &wrapped_key, &org_key, &mut ctx).unwrap();
123
124 assert_eq!(unsealed, data);
125 }
126
127 #[test]
128 fn wire_string_round_trip() {
129 let store = KeyStore::<TestIds>::default();
130 let mut ctx = store.context_mut();
131 let org_key = ctx.generate_symmetric_key();
132
133 let data = sample();
134 let Sealed {
135 envelope,
136 wrapped_key,
137 } = seal_target_system(data.clone(), &org_key, &mut ctx).unwrap();
138 let envelope: DataEnvelope = envelope.to_string().parse().unwrap();
139 let wrapped_key: EncString = wrapped_key.to_string().parse().unwrap();
140
141 let unsealed = unseal_target_system(&envelope, &wrapped_key, &org_key, &mut ctx).unwrap();
142 assert_eq!(unsealed, data);
143 }
144
145 #[test]
146 fn wrong_key_fails() {
147 let store = KeyStore::<TestIds>::default();
148 let mut ctx = store.context_mut();
149 let org_key = ctx.generate_symmetric_key();
150 let wrong_key = ctx.generate_symmetric_key();
151
152 let Sealed {
153 envelope,
154 wrapped_key,
155 } = seal_target_system(sample(), &org_key, &mut ctx).unwrap();
156 let result = unseal_target_system::<TestIds>(&envelope, &wrapped_key, &wrong_key, &mut ctx);
157
158 assert!(result.is_err());
159 }
160
161 #[test]
162 fn target_system_blob_cannot_be_unsealed_as_rotation_config() {
163 let store = KeyStore::<TestIds>::default();
164 let mut ctx = store.context_mut();
165 let org_key = ctx.generate_symmetric_key();
166
167 let Sealed {
168 envelope,
169 wrapped_key,
170 } = seal_target_system(sample(), &org_key, &mut ctx).unwrap();
171 let result = unseal_rotation_config::<TestIds>(&envelope, &wrapped_key, &org_key, &mut ctx);
172
173 assert!(matches!(result, Err(DataEnvelopeError::InvalidNamespace)));
174 }
175
176 #[test]
177 fn swapped_wrapped_key_fails() {
178 let store = KeyStore::<TestIds>::default();
179 let mut ctx = store.context_mut();
180 let org_key = ctx.generate_symmetric_key();
181
182 let Sealed { envelope, .. } = seal_target_system(sample(), &org_key, &mut ctx).unwrap();
183 let Sealed {
184 wrapped_key: other_wrapped_key,
185 ..
186 } = seal_target_system(sample(), &org_key, &mut ctx).unwrap();
187 let result =
188 unseal_target_system::<TestIds>(&envelope, &other_wrapped_key, &org_key, &mut ctx);
189
190 assert!(result.is_err());
191 }
192
193 #[test]
194 #[ignore = "Manual test to generate test vectors"]
195 fn generate_test_vectors() {
196 let store = KeyStore::<TestIds>::default();
197 let mut ctx = store.context_mut();
198 let org_key = ctx.generate_symmetric_key();
199 let key_b64 = {
200 #[allow(deprecated)]
201 let key = ctx.dangerous_get_symmetric_key(org_key).unwrap();
202 key.to_base64()
203 };
204
205 let Sealed {
206 envelope,
207 wrapped_key,
208 } = seal_target_system(sample(), &org_key, &mut ctx).unwrap();
209
210 let (envelope, wrapped_key) = (envelope.to_string(), wrapped_key.to_string());
211 println!("const TEST_VECTOR_ORG_KEY: &str = \"{key_b64}\";");
212 println!("const TEST_VECTOR_TARGET_SYSTEM_SEALED_DATA: &str = \"{envelope}\";");
213 println!("const TEST_VECTOR_TARGET_SYSTEM_WRAPPED_KEY: &str = \"{wrapped_key}\";");
214 }
215
216 const TEST_VECTOR_ORG_KEY: &str =
217 "MUgcMToL6hTXhQSr3bQuvJDw+P4y9vlRlvEQ36yFlgErQ0kdL5Xh3U92i/P3uJVk7X6ujV/uWO9usbo/X+0YcA==";
218 const TEST_VECTOR_TARGET_SYSTEM_SEALED_DATA: &str = "g1hHpQEDA3gjYXBwbGljYXRpb24veC5iaXR3YXJkZW4uY2Jvci1wYWRkZWQEUHRhqF0LBJ8L12LJH4jFX186AAE4gQI6AAE4gAShBUxjXNBlDL/mPqel7U1Y8r2jmj4RoevZ2EkAV+RTDBLzvGAH6a0YBM8wm9G72AK+rpeVpLrQrFnAimeptb+OJj0Y5Wd2aC5ZZaRfeAzISQeSOGSk6M6Hfdn3YZR0RiJXbggAAc+rs0Zi7zSbfzffpmAfLxGuNG8tCEgInq8KijZnK1p8ff6uCEcNMhstzt5uFC3R1Mm/ua+iNiaYqXAZZLGn1vKQ8f4IC2G61SYtmgN70iZjea4HCad48Lrp16ny1/OAxAsR9iJgMTOHVDGXrAerGBKcJiho0My6CwVPQmQNHgNN1Ki/gvflT1FiYWFkiHat09pOfgGoDyjc3X8dbcgy";
219 const TEST_VECTOR_TARGET_SYSTEM_WRAPPED_KEY: &str = "2.WakihY9ZIyZBuvrrvELJiQ==|j4sTuFZ7BqvcHiGKGcuI2tTjf3sZv29iUvh/I0eO/3EHzCuxsrEajOdZOneS9w1Hyag8agcO2IjmEOc0t3baBSpFqqZrmy/5UJ99n/zE3gU=|+DDgtyBastM+rPThuzq2ec4xa1ejGZMJaijzwf7aK9w=";
220
221 #[test]
222 fn target_system_test_vector() {
223 let org_key =
224 SymmetricCryptoKey::try_from(B64::try_from(TEST_VECTOR_ORG_KEY).unwrap()).unwrap();
225
226 let store = KeyStore::<TestIds>::default();
227 let mut ctx = store.context_mut();
228 let org_key_id = ctx.add_local_symmetric_key(org_key);
229
230 let envelope = DataEnvelope::from_str(TEST_VECTOR_TARGET_SYSTEM_SEALED_DATA).unwrap();
231 let wrapped_key = EncString::from_str(TEST_VECTOR_TARGET_SYSTEM_WRAPPED_KEY).unwrap();
232 let unsealed = unseal_target_system(&envelope, &wrapped_key, &org_key_id, &mut ctx).expect(
233 "TargetSystemSealed wire format has changed in a backwards-incompatible way. \
234 Existing sealed data must remain deserializable.",
235 );
236
237 assert_eq!(unsealed, sample());
238 }
239}