1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3
4use crate::cipher::{
5 field::FieldType, linked_id::LinkedIdType, login::UriMatchType, secure_note::SecureNoteType,
6};
7
8#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
9#[serde(rename_all = "camelCase")]
10pub(crate) struct CipherBlobV1 {
11 pub name: String,
12 pub notes: Option<String>,
13 pub type_data: CipherTypeDataV1,
14 #[serde(default, skip_serializing_if = "Vec::is_empty")]
15 pub fields: Vec<FieldDataV1>,
16 #[serde(default, skip_serializing_if = "Vec::is_empty")]
17 pub password_history: Vec<PasswordHistoryDataV1>,
18}
19
20impl bitwarden_crypto::safe::SealableData for CipherBlobV1 {}
21
22#[allow(clippy::large_enum_variant)]
26#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
27#[serde(tag = "type", rename_all = "camelCase")]
28pub(crate) enum CipherTypeDataV1 {
29 Login(LoginDataV1),
30 Card(CardDataV1),
31 Identity(IdentityDataV1),
32 SecureNote(SecureNoteDataV1),
33 SshKey(SshKeyDataV1),
34 BankAccount(BankAccountDataV1),
35}
36
37#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
38#[serde(rename_all = "camelCase")]
39pub(crate) struct LoginDataV1 {
40 pub username: Option<String>,
41 pub password: Option<String>,
42 pub password_revision_date: Option<DateTime<Utc>>,
43 #[serde(default, skip_serializing_if = "Vec::is_empty")]
44 pub uris: Vec<LoginUriDataV1>,
45 pub totp: Option<String>,
46 pub autofill_on_page_load: Option<bool>,
47 #[serde(default, skip_serializing_if = "Vec::is_empty")]
48 pub fido2_credentials: Vec<Fido2CredentialDataV1>,
49}
50
51#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
52#[serde(rename_all = "camelCase")]
53pub(crate) struct LoginUriDataV1 {
54 pub uri: Option<String>,
55 pub r#match: Option<UriMatchType>,
56}
57
58#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
59#[serde(rename_all = "camelCase")]
60pub(crate) struct Fido2CredentialDataV1 {
61 pub credential_id: String,
62 pub key_type: String,
63 pub key_algorithm: String,
64 pub key_curve: String,
65 pub key_value: String,
66 pub rp_id: String,
67 pub user_handle: Option<String>,
68 pub user_name: Option<String>,
69 pub counter: u64,
70 pub rp_name: Option<String>,
71 pub user_display_name: Option<String>,
72 pub discoverable: bool,
73 pub creation_date: DateTime<Utc>,
74}
75
76#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
77#[serde(rename_all = "camelCase")]
78pub(crate) struct CardDataV1 {
79 pub cardholder_name: Option<String>,
80 pub exp_month: Option<String>,
81 pub exp_year: Option<String>,
82 pub code: Option<String>,
83 pub brand: Option<String>,
84 pub number: Option<String>,
85}
86
87#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
88#[serde(rename_all = "camelCase")]
89pub(crate) struct IdentityDataV1 {
90 pub title: Option<String>,
91 pub first_name: Option<String>,
92 pub middle_name: Option<String>,
93 pub last_name: Option<String>,
94 pub address1: Option<String>,
95 pub address2: Option<String>,
96 pub address3: Option<String>,
97 pub city: Option<String>,
98 pub state: Option<String>,
99 pub postal_code: Option<String>,
100 pub country: Option<String>,
101 pub company: Option<String>,
102 pub email: Option<String>,
103 pub phone: Option<String>,
104 pub ssn: Option<String>,
105 pub username: Option<String>,
106 pub passport_number: Option<String>,
107 pub license_number: Option<String>,
108}
109
110#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
111#[serde(rename_all = "camelCase")]
112pub(crate) struct SecureNoteDataV1 {
113 #[serde(rename = "secureNoteType")]
114 pub r#type: SecureNoteType,
115}
116
117#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
118#[serde(rename_all = "camelCase")]
119pub(crate) struct SshKeyDataV1 {
120 pub private_key: String,
121 pub public_key: String,
122 pub fingerprint: String,
123}
124
125#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
126#[serde(rename_all = "camelCase")]
127pub(crate) struct BankAccountDataV1 {
128 pub bank_name: Option<String>,
129 pub name_on_account: Option<String>,
130 pub account_type: Option<String>,
131 pub account_number: Option<String>,
132 pub routing_number: Option<String>,
133 pub branch_number: Option<String>,
134 pub pin: Option<String>,
135 pub swift_code: Option<String>,
136 pub iban: Option<String>,
137 pub bank_contact_phone: Option<String>,
138}
139
140#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
141#[serde(rename_all = "camelCase")]
142pub(crate) struct FieldDataV1 {
143 pub name: Option<String>,
144 pub value: Option<String>,
145 pub r#type: FieldType,
146 pub linked_id: Option<LinkedIdType>,
147}
148
149#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
150#[serde(rename_all = "camelCase")]
151pub(crate) struct PasswordHistoryDataV1 {
152 pub password: String,
153 pub last_used_date: DateTime<Utc>,
154}
155
156#[cfg(test)]
157mod tests {
158 use bitwarden_core::key_management::KeySlotIds;
159 use bitwarden_crypto::{KeyStore, SymmetricCryptoKey, safe::DataEnvelope};
160 use bitwarden_encoding::B64;
161 use chrono::TimeZone;
162
163 use super::*;
164 use crate::cipher::{
165 blob::CipherBlob, linked_id::LoginLinkedIdType, secure_note::SecureNoteType,
166 };
167
168 const TEST_VECTOR_SECURE_NOTE_CEK: &str =
169 "pQEEAlApYxU9rgfIc9v9sHuglhkKAzoAARFvBIEEIFggDKYmieFA7a0UoOMAt4BErOpk7VfwZI8Dk0Yc9FxFzWIB";
170 const TEST_VECTOR_SECURE_NOTE_ENVELOPE: &str = "g1hLpQE6AAERbwN4I2FwcGxpY2F0aW9uL3guYml0d2FyZGVuLmNib3ItcGFkZGVkBFApYxU9rgfIc9v9sHuglhkKOgABOIECOgABOIABoQVYGMihknYX7mrmC03/w0V7rTMij2+q237p21h2H2Sq0hnlzZ4ka8yMW1yFOOMSt5ZS3tNQxzT07qeeY10tAeczgizA2g5AvmIQJdXK+7KR5mP3zk5VaVfGjC9mZUUFXwIAXsxC16HUII7Z1Iwhpd+MrJDf+itZGFVd07ExaXkH5+MjfaXhJqXBSxAStCG5zLGsEg==";
171
172 const TEST_VECTOR_LOGIN_CEK: &str =
173 "pQEEAlClJ9tO9x8fN2JVe5N8uzAaAzoAARFvBIEEIFggZwJXkLK7A6Sy5Y9+dacJrzCg9bo4RMRxXaRGDWYfbTYB";
174 const TEST_VECTOR_LOGIN_ENVELOPE: &str = "g1hLpQE6AAERbwN4I2FwcGxpY2F0aW9uL3guYml0d2FyZGVuLmNib3ItcGFkZGVkBFClJ9tO9x8fN2JVe5N8uzAaOgABOIECOgABOIABoQVYGNYP1rgAT3D2T6q2lGTRjIPHR2IELUDWE1kC2erlZM4Dqyeew1VlDkdXnZIE+t2g4SJh8IFSHo9WuzmyY+qC+V9cuGW3QHt+sg7pfZ5kQBh40U9uxUcxOHdVF3jQhcAmgB9abShR68u41NMDwwLSafG8PLzsUfhhxpCG0+ZuOda3tFVM1y5TyiDPJBBoJECuYK/K/1RLNAWAy5AU98yI0RgdK0MHxpoOqdSC8dXXu6fGgON7XLQWkkceFWILp0o51/c7OvdJ3B1nCiCIZjwAyS96+oWOzLrsPaGkBjqedBCi5iwelzLOttXk6nrzE/FfC0PkeeSsPSr9UdXBbeuUSK7wKtir9Lx/gtJ4sMPFidtTNdXCcDT9RA7y21h+3+wHQgGSlOGRigXFgXWi5ajSPCs9zLn7ERoG4BZ4IHa6EMSJbGH1pANW+Ibg4aadBF7LzOi/BZx2oZ/6z54XfAfqb44FR3/XXdNFHosPH9IH7CkKbNvLTuGrOTk9S998qFDNkWeGthrDYclaYSFktSHULvmHhSPadRL4uM6254HTmSjTAG6FDhhqdJU3SvoifrvBeHqjEP7F5R+zjVQol+JUcs/ExwmlrxXETOTzGyC/++FflZyPIUnH9U7ZqXhiYGd0ZvcyrnrnieehYToWFdwFR9ho+6h9hB/SCjhGudP5CRPZDL9GkNAUS5+pAd4ZC19fDjWpwMnEbgPTuthXKl6YRHxxCV+xpc9jncVQt9zF31e0bSIP7kVcdmlgjXaV6Nmd3aZ+PqeJnj27gCxG2tZkMimdJmEgxkL/cfvNHENg43+rpnV2mCTrCAO/X+RDGKdi7SIzsLesPVXVHEZCM4UBv2v+S/vpDFC3ie09cz525PCgt/7Je68k8S5WsTwKVeL0+T/ysqDo7wJvFSToY2G/LOGnBYMUYcCLfJGQbD8g2xI3oC+go8kcyVDJv+936/MkAYSxnvZDrMr30zEZaNm5YBh8cRqKr4MxOjJBk1XWjwDNsSQrDw==";
175
176 const TEST_VECTOR_CARD_CEK: &str =
177 "pQEEAlBqeNyImi1f1pMtJVlvDuV1AzoAARFvBIEEIFggyyOmMlDBj/oRic4qPjXnnKXGf4QGYMq7KvztZO3it+cB";
178 const TEST_VECTOR_CARD_ENVELOPE: &str = "g1hLpQE6AAERbwN4I2FwcGxpY2F0aW9uL3guYml0d2FyZGVuLmNib3ItcGFkZGVkBFBqeNyImi1f1pMtJVlvDuV1OgABOIECOgABOIABoQVYGHZO47yxMBN7r8jgWNJ8c+1bfUld0uzdNli2xQgFm6X0chG+qNdkiROVAUxi75+cN4jiTOzt41pG7bXyo7U4D8R38zR+l7jePi39w3YV4tnmxIenwBPK/0qdO8pLKUMwi8PMIBqJ2UxanLqRhP6Ru2i43rpVZMgAmasGgzGG8hhJttii1CidG8ntNP/zRvRl38F/7bphlN1a08/FeycdIAfQUf7tgzoaj1JegSwEs1M7/+ONHlPtlkmovN/zJTP0ZHL7U7NAt/JBIWbeScbGP6E=";
179
180 const TEST_VECTOR_IDENTITY_CEK: &str =
181 "pQEEAlBKyRoZr3LVRJsXJQ1msUhQAzoAARFvBIEEIFggVf/R4MMZFsa5DDtjjTG/1GozhF9jNQACFt9KpMpA9D8B";
182 const TEST_VECTOR_IDENTITY_ENVELOPE: &str = "g1hLpQE6AAERbwN4I2FwcGxpY2F0aW9uL3guYml0d2FyZGVuLmNib3ItcGFkZGVkBFBKyRoZr3LVRJsXJQ1msUhQOgABOIECOgABOIABoQVYGNZ9Ckqt+ftCL1eTn3LHTP/bLQVSiT2nFFkBl2ON9MRyKHrBCGlRlKxcGgMhFQhf3LY7kiDTjvvUhbZr3/rbc/fA8+7HS2UYu/SMOnxF4fg5AlBDc2kwE5iPqwJAJU4fnMqlQm+0jBAE2MS7oppPwHYh8cxDE9pqEa7Ehz1XygUgmUWtEpgGo2Nia+mdCnltws2X+uLCeAf6x5Ioc+HvFUIzFiYEN4WQ+NLmaCNrES9Zw9TXQTSh0drdPqaW7SSMjpBLk1TRtKX4hnSqE+tlRcfG24hPf214jG8On9tw1cdMQbF9GeC2FidfX+snjrU5psmje2bCcExfnvL7pgPeTV/R6+Fct0Jx5pKFXdTQM3SM1Ms8I+sq22sSc9Bu++V9nXFlLIyvWF9H/9FMYXrUR6HfBzSMJk7BSSin4wk/BKTEE59uaW+MtT/sDsW76aRo29VUqymbd5aezHCNxM8CFYaRGEXqWmakwXOPkqZh6CRhT3IZ6MjMQw2GbmDG+qv/KcbJatlKT1ZE6LUos/zpErOf0AT16D1WkS+9QwIeTP5QLv6F291nlBR2xDPg9v/cuauw";
183
184 const TEST_VECTOR_SSH_KEY_CEK: &str =
185 "pQEEAlApE2RsnNwb3+3FyIr/kcfWAzoAARFvBIEEIFggDk3igU6wYnicl6jRSYILSaPlDWYCjnRUqMLdqfPkVKAB";
186 const TEST_VECTOR_SSH_KEY_ENVELOPE: &str = "g1hLpQE6AAERbwN4I2FwcGxpY2F0aW9uL3guYml0d2FyZGVuLmNib3ItcGFkZGVkBFApE2RsnNwb3+3FyIr/kcfWOgABOIECOgABOIABoQVYGHPwqnuSuDHdwTg3twT5B0b3AXKVK+cySVkBSzorjdnfAdt1aNM32x3BPUg4QMkR99SQum3yc4eIT5eqi2FZjHyvEVPMwxfcWqg26g8UTc3dsRW57RYRF4ajx4+MGcJj+wWTrI8jPmthhLAnEHT11eC2YjYIW1INWKGFJTKnTjwHw1LTVJvEzA9MAZRk2y2NC+qkkdDM3wKmhl4PqoEPmt/x6qBjlR5+rlA4rUqkm9ja+NqqEbz8McGXBw8QWOh99/xE1PorFk7S+o9LW1Kcv1/GL+1wv6X7tTo1dYVYa2uCo9Hp9C8D5zXz/iVLm9w98NQFZQlteO8yibEOp+F/VNpgpsmZjOQzJ6wf0hKabFF2eXIUJ2RT1vJT+zUdcfc+TMkypaBbJEagmAiEBnZFcxVEhQ3tn1ZyJFRUcMzm91azIHQMmQ9cS6h/SqTGFF3z+q0H4+8w2S+yl+D5/OVWQHKcSOFvsPA=";
187
188 const TEST_VECTOR_BANK_ACCOUNT_CEK: &str =
189 "pQEEAlCz1mvOGP9yRKdx0pA5WbP7AzoAARFvBIEEIFggF30KGp58Duu4VcVvoFJ+Lhw1yEpfQvTUW2dvOP+WMd0B";
190 const TEST_VECTOR_BANK_ACCOUNT_ENVELOPE: &str = "g1hLpQE6AAERbwN4I2FwcGxpY2F0aW9uL3guYml0d2FyZGVuLmNib3ItcGFkZGVkBFCz1mvOGP9yRKdx0pA5WbP7OgABOIECOgABOIABoQVYGDbBFtW702QwCdi03+f9Uahq4Xf0bJ8i7VkBQxZB8XgvwLS13sHp8iz3VmTVcWJCyoxp6ycEUNSllpzURnZtfTsm9hkHCM0iFvMAXgDHBamHpI+8cX4sZ1qyjrGx4JDkGL1wDPUKMY7pLIN6alssjgYNl/6ijicWk2uNDneAGVgJdAHmxVKYPKbwYp0e8bLeAjgj6FOSFHaXv1a6TdF82iRCF/r5Uh/Ohx1FEbtRnaCSMJ4tLsf8YC9oq3duarJzSB2aINL9EnGAqqUlJ8cy8lyfkopUxV0OMnRWiHpja4CrEphhNeKKPoFRezsVoDYQ3f7kjryVAQ661gVxsEG3FB03+CcvVsT849QfrDcERxsQoKwy1E9yHaoE2kgWiYTHS+6gCH/gikDw1t4GBBUdjeJhP3bqQJbmM4cgRxWMgyswfFAfZok25kcA15EpHabkczydiPtnG2UW9qfu+bfw";
191
192 fn test_blob_secure_note() -> CipherBlobV1 {
193 CipherBlobV1 {
194 name: "Test Secure Note".to_string(),
195 notes: Some("Some notes".to_string()),
196 type_data: CipherTypeDataV1::SecureNote(SecureNoteDataV1 {
197 r#type: SecureNoteType::Generic,
198 }),
199 fields: Vec::new(),
200 password_history: Vec::new(),
201 }
202 }
203
204 fn test_blob_login() -> CipherBlobV1 {
205 CipherBlobV1 {
206 name: "Test Login".to_string(),
207 notes: Some("Login notes".to_string()),
208 type_data: CipherTypeDataV1::Login(LoginDataV1 {
209 username: Some("[email protected]".to_string()),
210 password: Some("p@ssw0rd123".to_string()),
211 password_revision_date: Some(Utc.with_ymd_and_hms(2024, 1, 15, 12, 0, 0).unwrap()),
212 uris: vec![LoginUriDataV1 {
213 uri: Some("https://example.com/login".to_string()),
214 r#match: Some(UriMatchType::Domain),
215 }],
216 totp: Some("otpauth://totp/test?secret=JBSWY3DPEHPK3PXP".to_string()),
217 autofill_on_page_load: Some(true),
218 fido2_credentials: vec![Fido2CredentialDataV1 {
219 credential_id: "credential-id-123".to_string(),
220 key_type: "public-key".to_string(),
221 key_algorithm: "ECDSA".to_string(),
222 key_curve: "P-256".to_string(),
223 key_value: "key-value-base64".to_string(),
224 rp_id: "example.com".to_string(),
225 user_handle: Some("user-handle-456".to_string()),
226 user_name: Some("testuser".to_string()),
227 counter: 42,
228 rp_name: Some("Example".to_string()),
229 user_display_name: Some("Test User".to_string()),
230 discoverable: true,
231 creation_date: Utc.with_ymd_and_hms(2024, 6, 1, 10, 30, 0).unwrap(),
232 }],
233 }),
234 fields: vec![FieldDataV1 {
235 name: Some("Custom Field".to_string()),
236 value: Some("custom-value".to_string()),
237 r#type: FieldType::Linked,
238 linked_id: Some(LinkedIdType::Login(LoginLinkedIdType::Username)),
239 }],
240 password_history: vec![PasswordHistoryDataV1 {
241 password: "old-password-1".to_string(),
242 last_used_date: Utc.with_ymd_and_hms(2023, 12, 1, 8, 0, 0).unwrap(),
243 }],
244 }
245 }
246
247 fn test_blob_card() -> CipherBlobV1 {
248 CipherBlobV1 {
249 name: "Test Card".to_string(),
250 notes: Some("Card notes".to_string()),
251 type_data: CipherTypeDataV1::Card(CardDataV1 {
252 cardholder_name: Some("John Doe".to_string()),
253 exp_month: Some("12".to_string()),
254 exp_year: Some("2028".to_string()),
255 code: Some("123".to_string()),
256 brand: Some("Visa".to_string()),
257 number: Some("4111111111111111".to_string()),
258 }),
259 fields: Vec::new(),
260 password_history: Vec::new(),
261 }
262 }
263
264 fn test_blob_identity() -> CipherBlobV1 {
265 CipherBlobV1 {
266 name: "Test Identity".to_string(),
267 notes: Some("Identity notes".to_string()),
268 type_data: CipherTypeDataV1::Identity(IdentityDataV1 {
269 title: Some("Mr".to_string()),
270 first_name: Some("John".to_string()),
271 middle_name: Some("Michael".to_string()),
272 last_name: Some("Doe".to_string()),
273 address1: Some("123 Main St".to_string()),
274 address2: Some("Apt 4B".to_string()),
275 address3: Some("Building C".to_string()),
276 city: Some("New York".to_string()),
277 state: Some("NY".to_string()),
278 postal_code: Some("10001".to_string()),
279 country: Some("US".to_string()),
280 company: Some("Acme Corp".to_string()),
281 email: Some("[email protected]".to_string()),
282 phone: Some("555-0123".to_string()),
283 ssn: Some("123-45-6789".to_string()),
284 username: Some("johndoe".to_string()),
285 passport_number: Some("P12345678".to_string()),
286 license_number: Some("DL-987654".to_string()),
287 }),
288 fields: Vec::new(),
289 password_history: Vec::new(),
290 }
291 }
292
293 fn test_blob_ssh_key() -> CipherBlobV1 {
294 CipherBlobV1 {
295 name: "Test SSH Key".to_string(),
296 notes: Some("SSH key notes".to_string()),
297 type_data: CipherTypeDataV1::SshKey(SshKeyDataV1 {
298 private_key: "-----BEGIN OPENSSH PRIVATE KEY-----\nb3BlbnNzaC1rZXktdjEA\n-----END OPENSSH PRIVATE KEY-----".to_string(),
299 public_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI [email protected]".to_string(),
300 fingerprint: "SHA256:abcdefghijklmnopqrstuvwxyz012345678901234567".to_string(),
301 }),
302 fields: Vec::new(),
303 password_history: Vec::new(),
304 }
305 }
306
307 fn test_blob_bank_account() -> CipherBlobV1 {
308 CipherBlobV1 {
309 name: "Test Bank Account".to_string(),
310 notes: Some("Bank account notes".to_string()),
311 type_data: CipherTypeDataV1::BankAccount(BankAccountDataV1 {
312 bank_name: Some("Test Bank".to_string()),
313 name_on_account: Some("John Doe".to_string()),
314 account_type: Some("Checking".to_string()),
315 account_number: Some("1234567890".to_string()),
316 routing_number: Some("021000021".to_string()),
317 branch_number: Some("001".to_string()),
318 pin: Some("1234".to_string()),
319 swift_code: Some("TESTUS33".to_string()),
320 iban: Some("US12345678901234567890".to_string()),
321 bank_contact_phone: Some("555-0123".to_string()),
322 }),
323 fields: Vec::new(),
324 password_history: Vec::new(),
325 }
326 }
327
328 #[test]
329 #[ignore]
330 fn generate_test_vectors() {
331 let blobs: Vec<(&str, CipherBlobV1)> = vec![
332 ("LOGIN", test_blob_login()),
333 ("CARD", test_blob_card()),
334 ("IDENTITY", test_blob_identity()),
335 ("SECURE_NOTE", test_blob_secure_note()),
336 ("SSH_KEY", test_blob_ssh_key()),
337 ("BANK_ACCOUNT", test_blob_bank_account()),
338 ];
339
340 for (name, blob) in blobs {
341 let data: CipherBlob = blob.into();
342 let store: KeyStore<KeySlotIds> = KeyStore::default();
343 let mut ctx = store.context_mut();
344 let (envelope, cek_id) = DataEnvelope::seal(data, &mut ctx).unwrap();
345
346 #[allow(deprecated)]
347 let cek = ctx.dangerous_get_symmetric_key(cek_id).unwrap();
348 println!(
349 "const TEST_VECTOR_{}_CEK: &str = \"{}\";",
350 name,
351 cek.to_base64()
352 );
353 println!(
354 "const TEST_VECTOR_{}_ENVELOPE: &str = \"{}\";",
355 name,
356 String::from(envelope)
357 );
358 println!();
359 }
360 }
361
362 fn verify_test_vector(cek_str: &str, envelope_str: &str, expected: CipherBlobV1) {
363 let cek = SymmetricCryptoKey::try_from(B64::try_from(cek_str).unwrap()).unwrap();
364
365 let store: KeyStore<KeySlotIds> = KeyStore::default();
366 let mut ctx = store.context_mut();
367 let cek_id = ctx.add_local_symmetric_key(cek);
368
369 let envelope: DataEnvelope = envelope_str.parse().unwrap();
370 let unsealed: CipherBlob = envelope
371 .unseal(cek_id, &mut ctx)
372 .expect("CipherBlobV1 has changed in a backwards-incompatible way. Existing encrypted data must remain deserializable. If a new format is needed, create a new version instead of modifying V1.");
373 assert_eq!(unsealed, expected.into());
374 }
375
376 #[test]
377 fn test_recorded_secure_note_test_vector() {
378 verify_test_vector(
379 TEST_VECTOR_SECURE_NOTE_CEK,
380 TEST_VECTOR_SECURE_NOTE_ENVELOPE,
381 test_blob_secure_note(),
382 );
383 }
384
385 #[test]
386 fn test_recorded_login_test_vector() {
387 verify_test_vector(
388 TEST_VECTOR_LOGIN_CEK,
389 TEST_VECTOR_LOGIN_ENVELOPE,
390 test_blob_login(),
391 );
392 }
393
394 #[test]
395 fn test_recorded_card_test_vector() {
396 verify_test_vector(
397 TEST_VECTOR_CARD_CEK,
398 TEST_VECTOR_CARD_ENVELOPE,
399 test_blob_card(),
400 );
401 }
402
403 #[test]
404 fn test_recorded_identity_test_vector() {
405 verify_test_vector(
406 TEST_VECTOR_IDENTITY_CEK,
407 TEST_VECTOR_IDENTITY_ENVELOPE,
408 test_blob_identity(),
409 );
410 }
411
412 #[test]
413 fn test_recorded_ssh_key_test_vector() {
414 verify_test_vector(
415 TEST_VECTOR_SSH_KEY_CEK,
416 TEST_VECTOR_SSH_KEY_ENVELOPE,
417 test_blob_ssh_key(),
418 );
419 }
420
421 #[test]
422 fn test_recorded_bank_account_test_vector() {
423 verify_test_vector(
424 TEST_VECTOR_BANK_ACCOUNT_CEK,
425 TEST_VECTOR_BANK_ACCOUNT_ENVELOPE,
426 test_blob_bank_account(),
427 );
428 }
429}