bitwarden_pam_sealed/
rotation_config.rs1use 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;
11
12#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
15pub struct RotationConfigSealedV1 {
16 pub target_system_id: Uuid,
18 pub cipher_id: Uuid,
20 pub account_identity: String,
22 pub terminate_sessions: bool,
24}
25impl SealableData for RotationConfigSealedV1 {}
26
27generate_versioned_sealable!(
28 RotationConfigSealed,
29 DataEnvelopeNamespace::PamRotationConfig,
30 [
31 RotationConfigSealedV1 => "1",
32 ]
33);
34
35pub fn seal_rotation_config<Ids: KeySlotIds>(
37 data: RotationConfigSealedV1,
38 org_key: &Ids::Symmetric,
39 ctx: &mut KeyStoreContext<Ids>,
40) -> Result<Sealed, DataEnvelopeError> {
41 let versioned: RotationConfigSealed = data.into();
42 let (envelope, wrapped_key) = DataEnvelope::seal_with_wrapping_key(versioned, org_key, ctx)?;
43 Ok(Sealed {
44 envelope,
45 wrapped_key,
46 })
47}
48
49pub fn unseal_rotation_config<Ids: KeySlotIds>(
51 envelope: &DataEnvelope,
52 wrapped_key: &EncString,
53 org_key: &Ids::Symmetric,
54 ctx: &mut KeyStoreContext<Ids>,
55) -> Result<RotationConfigSealedV1, DataEnvelopeError> {
56 let versioned: RotationConfigSealed =
57 envelope.unseal_with_wrapping_key(org_key, wrapped_key, ctx)?;
58 let RotationConfigSealed::RotationConfigSealedV1(data) = versioned;
59 Ok(data)
60}
61
62#[cfg(test)]
63mod tests {
64 use std::str::FromStr;
65
66 use bitwarden_crypto::{KeyStore, SymmetricCryptoKey};
67 use bitwarden_encoding::B64;
68
69 use super::*;
70 use crate::test_support::TestIds;
71
72 fn sample() -> RotationConfigSealedV1 {
73 RotationConfigSealedV1 {
74 target_system_id: Uuid::parse_str("8c6e7d1a-3e3f-4a5b-9c1d-2f3e4d5c6b7a").unwrap(),
75 cipher_id: Uuid::parse_str("1a2b3c4d-5e6f-4789-9abc-def012345678").unwrap(),
76 account_identity: "[email protected]".to_string(),
77 terminate_sessions: true,
78 }
79 }
80
81 #[test]
82 fn round_trip() {
83 let store = KeyStore::<TestIds>::default();
84 let mut ctx = store.context_mut();
85 let org_key = ctx.generate_symmetric_key();
86
87 let data = sample();
88 let Sealed {
89 envelope,
90 wrapped_key,
91 } = seal_rotation_config(data.clone(), &org_key, &mut ctx).unwrap();
92 let unsealed = unseal_rotation_config(&envelope, &wrapped_key, &org_key, &mut ctx).unwrap();
93
94 assert_eq!(unsealed, data);
95 }
96
97 #[test]
98 fn wire_string_round_trip() {
99 let store = KeyStore::<TestIds>::default();
100 let mut ctx = store.context_mut();
101 let org_key = ctx.generate_symmetric_key();
102
103 let data = sample();
104 let Sealed {
105 envelope,
106 wrapped_key,
107 } = seal_rotation_config(data.clone(), &org_key, &mut ctx).unwrap();
108 let envelope: DataEnvelope = envelope.to_string().parse().unwrap();
109 let wrapped_key: EncString = wrapped_key.to_string().parse().unwrap();
110
111 let unsealed = unseal_rotation_config(&envelope, &wrapped_key, &org_key, &mut ctx).unwrap();
112 assert_eq!(unsealed, data);
113 }
114
115 #[test]
116 fn wrong_key_fails() {
117 let store = KeyStore::<TestIds>::default();
118 let mut ctx = store.context_mut();
119 let org_key = ctx.generate_symmetric_key();
120 let wrong_key = ctx.generate_symmetric_key();
121
122 let Sealed {
123 envelope,
124 wrapped_key,
125 } = seal_rotation_config(sample(), &org_key, &mut ctx).unwrap();
126 let result =
127 unseal_rotation_config::<TestIds>(&envelope, &wrapped_key, &wrong_key, &mut ctx);
128
129 assert!(result.is_err());
130 }
131
132 #[test]
133 #[ignore = "Manual test to generate test vectors"]
134 fn generate_test_vectors() {
135 let store = KeyStore::<TestIds>::default();
136 let mut ctx = store.context_mut();
137 let org_key = ctx.generate_symmetric_key();
138 let key_b64 = {
139 #[allow(deprecated)]
140 let key = ctx.dangerous_get_symmetric_key(org_key).unwrap();
141 key.to_base64()
142 };
143
144 let Sealed {
145 envelope,
146 wrapped_key,
147 } = seal_rotation_config(sample(), &org_key, &mut ctx).unwrap();
148
149 let (envelope, wrapped_key) = (envelope.to_string(), wrapped_key.to_string());
150 println!("const TEST_VECTOR_ORG_KEY: &str = \"{key_b64}\";");
151 println!("const TEST_VECTOR_ROTATION_CONFIG_SEALED_DATA: &str = \"{envelope}\";");
152 println!("const TEST_VECTOR_ROTATION_CONFIG_WRAPPED_KEY: &str = \"{wrapped_key}\";");
153 }
154
155 const TEST_VECTOR_ORG_KEY: &str =
156 "vA1+lDkWyNN0ZVTRLJMs64LXMbwW+h0x1nN2nPJ5clYtuXLL3ek3bKW/uDb1Kmye7vz7o7hADuZBDeMuQfoCiw==";
157 const TEST_VECTOR_ROTATION_CONFIG_SEALED_DATA: &str = "g1hHpQEDA3gjYXBwbGljYXRpb24veC5iaXR3YXJkZW4uY2Jvci1wYWRkZWQEUOwuU91Xcz4pzHy3rmrPeP06AAE4gQI6AAE4gAWhBUyORP6msVGzzmXPo29YoSD80pwt83q+nnI+6B519Ur9T+t0PHxX50GRwzeJTE5zmD/fG5D5a32OrKkbeePwL/BDUjL4YPu44XC9Fp3T2vEUunJp4G4pm8gHWZdAmp6Pz2zNsq3nRSBBKUfkpQVRPf0+oCUVIRJF8UYIY+QkZmHimmNZi/DrwiXbNusjO7DjM5s2X5cnhxhLwpBaBwnyqMnCNLuptbBZYYKluNkebXFK";
158 const TEST_VECTOR_ROTATION_CONFIG_WRAPPED_KEY: &str = "2.n0nzXEnaK2oxw+szuM3OZA==|DoiILS1QEEt7VbD5VyBmVuhhlkdoqkXNeAhxvNHCJQbq5/9Ut89nUZ45uMNfqf0kaM0ttRAHly30433dJ8PwplyKnZqU5OoaoxBxWY7C1SA=|ZP7wSGPyte0dkkjjxQxZiJ/gBaK8iy/1MEuw+O9UZGM=";
159
160 #[test]
161 fn rotation_config_test_vector() {
162 let org_key =
163 SymmetricCryptoKey::try_from(B64::try_from(TEST_VECTOR_ORG_KEY).unwrap()).unwrap();
164
165 let store = KeyStore::<TestIds>::default();
166 let mut ctx = store.context_mut();
167 let org_key_id = ctx.add_local_symmetric_key(org_key);
168
169 let envelope = DataEnvelope::from_str(TEST_VECTOR_ROTATION_CONFIG_SEALED_DATA).unwrap();
170 let wrapped_key = EncString::from_str(TEST_VECTOR_ROTATION_CONFIG_WRAPPED_KEY).unwrap();
171 let unsealed = unseal_rotation_config(&envelope, &wrapped_key, &org_key_id, &mut ctx)
172 .expect(
173 "RotationConfigSealed wire format has changed in a backwards-incompatible \
174 way. Existing sealed data must remain deserializable.",
175 );
176
177 assert_eq!(unsealed, sample());
178 }
179}