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