| 1 | use super::{Error, Result, invalid}; |
| 2 | use aes::cipher::{BlockModeDecrypt, BlockModeEncrypt, KeyIvInit, block_padding::NoPadding}; |
| 3 | use base64::{Engine, engine::general_purpose::STANDARD}; |
| 4 | use sha1::{Digest, Sha1}; |
| 5 | use subtle::ConstantTimeEq; |
| 6 | use zeroize::{Zeroize, Zeroizing}; |
| 7 | |
| 8 | const NS: &str = "http://schemas.microsoft.com/office/2006/encryption"; |
| 9 | const PASSWORD_NS: &str = "http://schemas.microsoft.com/office/2006/keyEncryptor/password"; |
| 10 | |
| 11 | #[derive(Clone)] |
| 12 | pub(super) struct Key { |
| 13 | value: Zeroizing<[u8; 16]>, |
| 14 | file_iv: [u8; 16], |
| 15 | } |
| 16 | |
| 17 | fn child<'a, 'input>( |
| 18 | node: roxmltree::Node<'a, 'input>, |
| 19 | name: &str, |
| 20 | ns: &str, |
| 21 | ) -> Result<roxmltree::Node<'a, 'input>> { |
| 22 | let mut matches = node.children().filter(|n| n.has_tag_name((ns, name))); |
| 23 | let value = matches |
| 24 | .next() |
| 25 | .ok_or_else(|| invalid("Missing encryption metadata element"))?; |
| 26 | if matches.next().is_some() { |
| 27 | return Err(invalid("Repeated encryption metadata element")); |
| 28 | } |
| 29 | Ok(value) |
| 30 | } |
| 31 | |
| 32 | fn decoded<const N: usize>(node: roxmltree::Node<'_, '_>, name: &str) -> Result<[u8; N]> { |
| 33 | let value = node |
| 34 | .attribute(name) |
| 35 | .ok_or_else(|| invalid("Missing encryption metadata attribute"))?; |
| 36 | let bytes = STANDARD |
| 37 | .decode(value.split_ascii_whitespace().collect::<String>()) |
| 38 | .map_err(|_| invalid("Invalid encryption metadata base64"))?; |
| 39 | bytes |
| 40 | .try_into() |
| 41 | .map_err(|_| invalid("Invalid encryption metadata byte length")) |
| 42 | } |
| 43 | |
| 44 | fn profile(node: roxmltree::Node<'_, '_>) -> Result<()> { |
| 45 | for (attribute, value) in [ |
| 46 | ("saltSize", 16), |
| 47 | ("blockSize", 16), |
| 48 | ("keyBits", 128), |
| 49 | ("hashSize", 20), |
| 50 | ] { |
| 51 | if number(node, attribute)? != value { |
| 52 | return Err(Error::Unsupported); |
| 53 | } |
| 54 | } |
| 55 | for (attribute, value) in [ |
| 56 | ("cipherAlgorithm", "AES"), |
| 57 | ("cipherChaining", "ChainingModeCBC"), |
| 58 | ("hashAlgorithm", "SHA1"), |
| 59 | ] { |
| 60 | let actual = node |
| 61 | .attribute(attribute) |
| 62 | .ok_or_else(|| invalid("Missing encryption algorithm attribute"))?; |
| 63 | if actual != value { |
| 64 | return Err(Error::Unsupported); |
| 65 | } |
| 66 | } |
| 67 | Ok(()) |
| 68 | } |
| 69 | |
| 70 | fn number(node: roxmltree::Node<'_, '_>, name: &str) -> Result<u32> { |
| 71 | node.attribute(name) |
| 72 | .ok_or_else(|| invalid("Missing encryption numeric attribute"))? |
| 73 | .trim() |
| 74 | .parse() |
| 75 | .map_err(|_| invalid("Invalid encryption numeric attribute")) |
| 76 | } |
| 77 | |
| 78 | fn decrypt(key: &[u8; 16], iv: &[u8; 16], bytes: &mut [u8]) -> Result<()> { |
| 79 | cbc::Decryptor::<aes::Aes128>::new(key.into(), iv.into()) |
| 80 | .decrypt_padded::<NoPadding>(bytes) |
| 81 | .map_err(|_| invalid("Encrypted payload is not block aligned"))?; |
| 82 | Ok(()) |
| 83 | } |
| 84 | |
| 85 | fn encrypt(key: &[u8; 16], iv: &[u8; 16], bytes: &mut [u8]) { |
| 86 | let length = bytes.len(); |
| 87 | cbc::Encryptor::<aes::Aes128>::new(key.into(), iv.into()) |
| 88 | .encrypt_padded::<NoPadding>(bytes, length) |
| 89 | .expect("block aligned"); |
| 90 | } |
| 91 | |
| 92 | /// Password-hash rounds OneNote 2010 writes. |
| 93 | const SPINS: u32 = 100_000; |
| 94 | /// MS-OFFCRYPTO's block keys for the verifier input, its hash and the wrapped key. |
| 95 | const VERIFIER_INPUT: [u8; 8] = [0xfe, 0xa7, 0xd2, 0x76, 0x3b, 0x4b, 0x9e, 0x79]; |
| 96 | const VERIFIER_VALUE: [u8; 8] = [0xd7, 0xaa, 0x0f, 0x6d, 0x30, 0x61, 0x34, 0x4e]; |
| 97 | const KEY_VALUE: [u8; 8] = [0x14, 0x6e, 0x0b, 0xe7, 0xab, 0xac, 0xd0, 0xd6]; |
| 98 | |
| 99 | /// The iterated password hash: SHA-1 of salt and UTF-16LE password, then `count` rounds of |
| 100 | /// SHA-1 over the round number and the previous hash. |
| 101 | fn seed(salt: &[u8; 16], password: &str, count: u32) -> Zeroizing<[u8; 20]> { |
| 102 | let mut hash = Sha1::new(); |
| 103 | hash.update(salt); |
| 104 | for word in password.encode_utf16() { |
| 105 | hash.update(word.to_le_bytes()); |
| 106 | } |
| 107 | let mut seed = Zeroizing::new(<[u8; 20]>::from(hash.finalize())); |
| 108 | for index in 0..count { |
| 109 | let mut hash = Sha1::new(); |
| 110 | hash.update(index.to_le_bytes()); |
| 111 | hash.update(seed.as_ref()); |
| 112 | *seed = hash.finalize().into(); |
| 113 | } |
| 114 | seed |
| 115 | } |
| 116 | |
| 117 | /// The AES key a block key derives from the password hash. |
| 118 | fn block(seed: &[u8; 20], label: [u8; 8]) -> Zeroizing<[u8; 16]> { |
| 119 | let mut hash = Sha1::new(); |
| 120 | hash.update(seed); |
| 121 | hash.update(label); |
| 122 | let derived = Zeroizing::new(<[u8; 20]>::from(hash.finalize())); |
| 123 | Zeroizing::new(derived[..16].try_into().unwrap()) |
| 124 | } |
| 125 | |
| 126 | /// The IV of payloads: SHA-1 of the key data's salt and block 0. |
| 127 | fn file_iv(salt: &[u8; 16]) -> [u8; 16] { |
| 128 | let mut hash = Sha1::new(); |
| 129 | hash.update(salt); |
| 130 | hash.update(0_u32.to_le_bytes()); |
| 131 | hash.finalize()[..16].try_into().unwrap() |
| 132 | } |
| 133 | |
| 134 | impl Key { |
| 135 | pub(super) fn open(data: &[u8], password: &str, rounds: &mut u64) -> Result<Self> { |
| 136 | if data.len() > 65536 || password.len() > 65536 { |
| 137 | return Err(Error::Limit); |
| 138 | } |
| 139 | let mut c = crate::bytes::Cursor { |
| 140 | bytes: data, |
| 141 | offset: 0, |
| 142 | }; |
| 143 | if u32::from_le_bytes(c.read()?) != 3 { |
| 144 | return Err(Error::Unsupported); |
| 145 | } |
| 146 | let length = u32::from_le_bytes(c.read()?) as usize; |
| 147 | let offset = u32::from_le_bytes(c.read()?) as usize; |
| 148 | let inner = u32::from_le_bytes(c.read()?) as usize; |
| 149 | if length != data.len() || offset != 16 || inner != data.len() - 16 { |
| 150 | return Err(invalid("Inconsistent encryption metadata framing")); |
| 151 | } |
| 152 | if c.read::<8>()? != [4, 0, 4, 0, 64, 0, 0, 0] { |
| 153 | return Err(Error::Unsupported); |
| 154 | } |
| 155 | let text = std::str::from_utf8(c.bytes) |
| 156 | .map_err(|_| invalid("Encryption metadata is not UTF-8"))?; |
| 157 | let xml = roxmltree::Document::parse_with_options( |
| 158 | text, |
| 159 | roxmltree::ParsingOptions { |
| 160 | nodes_limit: 64, |
| 161 | ..Default::default() |
| 162 | }, |
| 163 | ) |
| 164 | .map_err(|_| invalid("Invalid encryption metadata XML"))?; |
| 165 | let root = xml.root_element(); |
| 166 | if !root.has_tag_name((NS, "encryption")) { |
| 167 | return Err(Error::Unsupported); |
| 168 | } |
| 169 | let data_key = child(root, "keyData", NS)?; |
| 170 | let encryptors = child(root, "keyEncryptors", NS)?; |
| 171 | if root.children().filter(|n| n.is_element()).count() != 2 |
| 172 | || encryptors.children().filter(|n| n.is_element()).count() != 1 |
| 173 | { |
| 174 | return Err(Error::Unsupported); |
| 175 | } |
| 176 | let encryptor = child(encryptors, "keyEncryptor", NS)?; |
| 177 | if encryptor.attribute("uri") != Some(PASSWORD_NS) |
| 178 | || encryptor.children().filter(|n| n.is_element()).count() != 1 |
| 179 | { |
| 180 | return Err(Error::Unsupported); |
| 181 | } |
| 182 | let wrapped = child(encryptor, "encryptedKey", PASSWORD_NS)?; |
| 183 | profile(data_key)?; |
| 184 | profile(wrapped)?; |
| 185 | let count = number(wrapped, "spinCount")?; |
| 186 | *rounds = rounds.checked_sub(u64::from(count)).ok_or(Error::Limit)?; |
| 187 | let salt = decoded::<16>(wrapped, "saltValue")?; |
| 188 | let mut verifier = Zeroizing::new(decoded::<16>(wrapped, "encryptedVerifierHashInput")?); |
| 189 | let mut expected = Zeroizing::new(decoded::<32>(wrapped, "encryptedVerifierHashValue")?); |
| 190 | let mut value = Zeroizing::new(decoded::<16>(wrapped, "encryptedKeyValue")?); |
| 191 | let seed = seed(&salt, password, count); |
| 192 | for (label, bytes) in [ |
| 193 | (VERIFIER_INPUT, verifier.as_mut_slice()), |
| 194 | (VERIFIER_VALUE, expected.as_mut_slice()), |
| 195 | (KEY_VALUE, value.as_mut_slice()), |
| 196 | ] { |
| 197 | decrypt(&block(&seed, label), &salt, bytes)?; |
| 198 | } |
| 199 | let actual = Zeroizing::new(<[u8; 20]>::from(Sha1::digest(verifier.as_slice()))); |
| 200 | if !bool::from(actual.as_slice().ct_eq(&expected[..20])) { |
| 201 | return Err(Error::PasswordMismatch); |
| 202 | } |
| 203 | Ok(Self { |
| 204 | value, |
| 205 | file_iv: file_iv(&decoded::<16>(data_key, "saltValue")?), |
| 206 | }) |
| 207 | } |
| 208 | |
| 209 | /// A fresh key for `password` and the encryption data that opens it, as OneNote 2010 |
| 210 | /// writes them: random salts, key and verifier, 100,000 SHA-1 rounds, no integrity block. |
| 211 | pub(super) fn create(password: &str) -> Result<(Self, Vec<u8>)> { |
| 212 | if password.len() > 65536 { |
| 213 | return Err(Error::Limit); |
| 214 | } |
| 215 | let random = |bytes: &mut [u8]| { |
| 216 | getrandom::fill(bytes).map_err(|_| invalid("System random source failed")) |
| 217 | }; |
| 218 | let (mut data_salt, mut salt) = ([0; 16], [0; 16]); |
| 219 | let mut value = Zeroizing::new([0; 16]); |
| 220 | let mut verifier = Zeroizing::new([0; 16]); |
| 221 | random(&mut data_salt)?; |
| 222 | random(&mut salt)?; |
| 223 | random(value.as_mut_slice())?; |
| 224 | random(verifier.as_mut_slice())?; |
| 225 | let seed = seed(&salt, password, SPINS); |
| 226 | let mut hash = Zeroizing::new([0; 32]); |
| 227 | hash[..20].copy_from_slice(&Sha1::digest(verifier.as_slice())); |
| 228 | let mut wrapped_value = *value; |
| 229 | let mut wrapped_verifier = *verifier; |
| 230 | let mut wrapped_hash = *hash; |
| 231 | encrypt(&block(&seed, VERIFIER_INPUT), &salt, &mut wrapped_verifier); |
| 232 | encrypt(&block(&seed, VERIFIER_VALUE), &salt, &mut wrapped_hash); |
| 233 | encrypt(&block(&seed, KEY_VALUE), &salt, &mut wrapped_value); |
| 234 | let profile = r#"saltSize="16" blockSize="16" keyBits="128" hashSize="20" cipherAlgorithm="AES" cipherChaining="ChainingModeCBC" hashAlgorithm="SHA1""#; |
| 235 | let xml = format!( |
| 236 | "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\r\n\ |
| 237 | <encryption xmlns=\"{NS}\" xmlns:p=\"{PASSWORD_NS}\">\ |
| 238 | <keyData {profile} saltValue=\"{}\"/>\ |
| 239 | <keyEncryptors><keyEncryptor uri=\"{PASSWORD_NS}\">\ |
| 240 | <p:encryptedKey spinCount=\"{SPINS}\" {profile} saltValue=\"{}\" \ |
| 241 | encryptedVerifierHashInput=\"{}\" encryptedVerifierHashValue=\"{}\" \ |
| 242 | encryptedKeyValue=\"{}\"/></keyEncryptor></keyEncryptors></encryption>", |
| 243 | STANDARD.encode(data_salt), |
| 244 | STANDARD.encode(salt), |
| 245 | STANDARD.encode(wrapped_verifier), |
| 246 | STANDARD.encode(wrapped_hash), |
| 247 | STANDARD.encode(wrapped_value), |
| 248 | ); |
| 249 | let length = u32::try_from(24 + xml.len()).map_err(|_| Error::Limit)?; |
| 250 | let mut data = Vec::with_capacity(length as usize); |
| 251 | for word in [3, length, 16, length - 16] { |
| 252 | data.extend_from_slice(&word.to_le_bytes()); |
| 253 | } |
| 254 | data.extend_from_slice(&[4, 0, 4, 0, 64, 0, 0, 0]); |
| 255 | data.extend_from_slice(xml.as_bytes()); |
| 256 | Ok(( |
| 257 | Self { |
| 258 | value, |
| 259 | file_iv: file_iv(&data_salt), |
| 260 | }, |
| 261 | data, |
| 262 | )) |
| 263 | } |
| 264 | |
| 265 | /// The AES key the section's objects and payloads are encrypted under. |
| 266 | pub(super) fn value(&self) -> &[u8; 16] { |
| 267 | &self.value |
| 268 | } |
| 269 | |
| 270 | pub(super) fn property(&self, input: &[u8]) -> Result<Zeroizing<Vec<u8>>> { |
| 271 | let mut c = crate::bytes::Cursor { |
| 272 | bytes: input, |
| 273 | offset: 0, |
| 274 | }; |
| 275 | crate::properties::reference_streams(&mut c)?; |
| 276 | let prefix = c.offset; |
| 277 | let length = u32::from_le_bytes(c.read()?) as usize; |
| 278 | let encrypted = c.take(length)?; |
| 279 | if c.bytes.len() > 7 || c.bytes.iter().any(|b| *b != 0) { |
| 280 | return Err(invalid("Invalid encrypted property alignment")); |
| 281 | } |
| 282 | let (iv, body) = encrypted |
| 283 | .split_first_chunk::<16>() |
| 284 | .ok_or_else(|| invalid("Missing encrypted property IV"))?; |
| 285 | let mut clear = Zeroizing::new(body.to_vec()); |
| 286 | decrypt(&self.value, iv, &mut clear)?; |
| 287 | let padding = clear |
| 288 | .first_chunk::<2>() |
| 289 | .map(|b| usize::from(u16::from_le_bytes(*b))) |
| 290 | .ok_or_else(|| invalid("Missing encrypted property padding count"))?; |
| 291 | if padding >= 16 || clear.len() < 2 + padding { |
| 292 | return Err(invalid("Invalid encrypted property padding count")); |
| 293 | } |
| 294 | let mut output = Zeroizing::new(Vec::with_capacity(prefix + clear.len() - 2 - padding)); |
| 295 | output.extend_from_slice(&input[..prefix]); |
| 296 | output.extend_from_slice(&clear[2..clear.len() - padding]); |
| 297 | crate::PropertySets::parse(&output)?; |
| 298 | Ok(output) |
| 299 | } |
| 300 | |
| 301 | /// The stored form of a plaintext property object, the inverse of `property`. |
| 302 | pub(super) fn seal_property(&self, clear: &[u8], iv: [u8; 16]) -> Result<Vec<u8>> { |
| 303 | let mut c = crate::bytes::Cursor { |
| 304 | bytes: clear, |
| 305 | offset: 0, |
| 306 | }; |
| 307 | crate::properties::reference_streams(&mut c)?; |
| 308 | let prefix = c.offset; |
| 309 | let padding = (16 - (2 + clear.len() - prefix) % 16) % 16; |
| 310 | // Sized once: a buffer abandoned by growth would keep plaintext. |
| 311 | let mut body = Zeroizing::new(Vec::with_capacity(2 + clear.len() - prefix + padding)); |
| 312 | body.extend_from_slice(&(padding as u16).to_le_bytes()); |
| 313 | body.extend_from_slice(&clear[prefix..]); |
| 314 | let length = body.len() + padding; |
| 315 | body.resize(length, 0); |
| 316 | getrandom::fill(&mut body[length - padding..]) |
| 317 | .map_err(|_| invalid("System random source failed"))?; |
| 318 | encrypt(&self.value, &iv, &mut body); |
| 319 | let mut output = clear[..prefix].to_vec(); |
| 320 | output.extend_from_slice(&((16 + body.len()) as u32).to_le_bytes()); |
| 321 | output.extend_from_slice(&iv); |
| 322 | output.extend_from_slice(&body); |
| 323 | output.resize(output.len().next_multiple_of(8), 0); |
| 324 | Ok(output) |
| 325 | } |
| 326 | |
| 327 | /// The stored form of a file payload, the inverse of `file`: its length, the bytes and |
| 328 | /// random padding to the block, as OneNote pads. |
| 329 | pub(super) fn seal_file(&self, clear: &[u8]) -> Result<Vec<u8>> { |
| 330 | if clear.is_empty() { |
| 331 | return Ok(Vec::new()); |
| 332 | } |
| 333 | let length = (8 + clear.len()).next_multiple_of(16); |
| 334 | let mut output = Vec::with_capacity(length); |
| 335 | output.extend_from_slice(&(clear.len() as u64).to_le_bytes()); |
| 336 | output.extend_from_slice(clear); |
| 337 | let end = output.len(); |
| 338 | output.resize(length, 0); |
| 339 | getrandom::fill(&mut output[end..]).map_err(|_| invalid("System random source failed"))?; |
| 340 | encrypt(&self.value, &self.file_iv, &mut output); |
| 341 | Ok(output) |
| 342 | } |
| 343 | |
| 344 | pub(super) fn file(&self, input: &[u8]) -> Result<Zeroizing<Vec<u8>>> { |
| 345 | if input.is_empty() { |
| 346 | return Ok(Zeroizing::new(Vec::new())); |
| 347 | } |
| 348 | let mut clear = Zeroizing::new(input.to_vec()); |
| 349 | decrypt(&self.value, &self.file_iv, &mut clear)?; |
| 350 | let length = clear |
| 351 | .first_chunk::<8>() |
| 352 | .map(|b| u64::from_le_bytes(*b)) |
| 353 | .ok_or_else(|| invalid("Missing encrypted file length"))?; |
| 354 | let length = usize::try_from(length) |
| 355 | .map_err(|_| invalid("Encrypted file length exceeds address space"))?; |
| 356 | if length > clear.len() - 8 || clear.len() - 8 - length >= 16 { |
| 357 | return Err(invalid("Invalid encrypted file length")); |
| 358 | } |
| 359 | clear.copy_within(8..8 + length, 0); |
| 360 | clear[length..].zeroize(); |
| 361 | clear.truncate(length); |
| 362 | Ok(clear) |
| 363 | } |
| 364 | } |
| 365 | |
| 366 | #[cfg(test)] |
| 367 | mod tests { |
| 368 | use super::*; |
| 369 | use crate::{ObjectData, Reference, RevisionIndex, Store}; |
| 370 | |
| 371 | #[test] |
| 372 | fn native_frames_and_bounded_malformed_inputs() { |
| 373 | let root = std::path::Path::new("../../corpus/native-encrypted"); |
| 374 | let bytes = std::fs::read(root.join("encrypted-01/notebook/synthetic.one")).unwrap(); |
| 375 | let manifest: serde_json::Value = |
| 376 | serde_json::from_slice(&std::fs::read(root.join("manifest.json")).unwrap()).unwrap(); |
| 377 | let password = manifest["password"].as_str().unwrap(); |
| 378 | let store = Store::parse(&bytes).unwrap(); |
| 379 | let index = RevisionIndex::parse(&store).unwrap(); |
| 380 | let node = index |
| 381 | .spaces |
| 382 | .values() |
| 383 | .next() |
| 384 | .unwrap() |
| 385 | .revisions |
| 386 | .values() |
| 387 | .next() |
| 388 | .unwrap() |
| 389 | .nodes |
| 390 | .first() |
| 391 | .unwrap(); |
| 392 | let Some(Reference::Data(chunk)) = node.reference else { |
| 393 | panic!("Missing native key") |
| 394 | }; |
| 395 | let metadata = store.encryption_key(chunk).unwrap(); |
| 396 | for length in 0..metadata.len() { |
| 397 | assert!(Key::open(&metadata[..length], password, &mut 0).is_err()); |
| 398 | } |
| 399 | let key = Key::open(metadata, password, &mut 100000).unwrap(); |
| 400 | let xml = std::str::from_utf8(&metadata[24..]).unwrap(); |
| 401 | let frame = |xml: &str| { |
| 402 | let mut value = metadata[..24].to_vec(); |
| 403 | value.extend_from_slice(xml.as_bytes()); |
| 404 | let length = u32::try_from(value.len()).unwrap(); |
| 405 | value[4..8].copy_from_slice(&length.to_le_bytes()); |
| 406 | value[12..16].copy_from_slice(&(length - 16).to_le_bytes()); |
| 407 | value |
| 408 | }; |
| 409 | assert!(matches!( |
| 410 | Key::open( |
| 411 | &frame(&xml.replace("keyBits=\"128\"", "keyBits=\"256\"")), |
| 412 | password, |
| 413 | &mut 100000 |
| 414 | ), |
| 415 | Err(Error::Unsupported) |
| 416 | )); |
| 417 | assert!(matches!( |
| 418 | Key::open( |
| 419 | &frame(&xml.replace("keyBits=\"128\"", "")), |
| 420 | password, |
| 421 | &mut 100000 |
| 422 | ), |
| 423 | Err(Error::Invalid(_)) |
| 424 | )); |
| 425 | let tree = roxmltree::Document::parse(xml).unwrap(); |
| 426 | let salt = child(tree.root_element(), "keyData", NS) |
| 427 | .unwrap() |
| 428 | .attribute("saltValue") |
| 429 | .unwrap(); |
| 430 | let spaced = xml |
| 431 | .replace("keyBits=\"128\"", "keyBits=\" +0128 \"") |
| 432 | .replace("spinCount=\"100000\"", "spinCount=\" 0100000 \"") |
| 433 | .replace(salt, &format!(" {}\n{} ", &salt[..8], &salt[8..])); |
| 434 | let spaced = Key::open(&frame(&spaced), password, &mut 100000).unwrap(); |
| 435 | for (sid, space) in &index.spaces { |
| 436 | for rid in space.labels.values() { |
| 437 | let revision = index.resolve(*sid, *rid).unwrap(); |
| 438 | for object in revision.objects.values() { |
| 439 | if let ObjectData::Encrypted(bytes) = object.data { |
| 440 | let mut cursor = crate::bytes::Cursor { bytes, offset: 0 }; |
| 441 | crate::properties::reference_streams(&mut cursor).unwrap(); |
| 442 | let length = u32::from_le_bytes(cursor.read().unwrap()) as usize; |
| 443 | let end = cursor.offset + length; |
| 444 | let iv = bytes[cursor.offset..cursor.offset + 16].try_into().unwrap(); |
| 445 | // Native padding is arbitrary; it only reaches the last block. |
| 446 | let sealed = key |
| 447 | .seal_property(&key.property(bytes).unwrap(), iv) |
| 448 | .unwrap(); |
| 449 | assert_eq!(sealed.len(), bytes.len()); |
| 450 | assert_eq!(sealed[..end - 16], bytes[..end - 16]); |
| 451 | assert_eq!( |
| 452 | *key.property(&sealed).unwrap(), |
| 453 | *key.property(bytes).unwrap() |
| 454 | ); |
| 455 | assert_eq!( |
| 456 | *key.property(bytes).unwrap(), |
| 457 | *spaced.property(bytes).unwrap() |
| 458 | ); |
| 459 | for cut in 0..end { |
| 460 | assert!(key.property(&bytes[..cut]).is_err()); |
| 461 | } |
| 462 | let mut changed = bytes.to_vec(); |
| 463 | changed.push(1); |
| 464 | assert!(key.property(&changed).is_err()); |
| 465 | } |
| 466 | } |
| 467 | } |
| 468 | } |
| 469 | let mut state = 0x4851_e529_b30d_620f_u64; |
| 470 | for length in 0..1024 { |
| 471 | let mut bytes = vec![0; length]; |
| 472 | for byte in &mut bytes { |
| 473 | state ^= state << 13; |
| 474 | state ^= state >> 7; |
| 475 | state ^= state << 17; |
| 476 | *byte = state.to_le_bytes()[0]; |
| 477 | } |
| 478 | let _ = key.property(&bytes); |
| 479 | let _ = key.file(&bytes); |
| 480 | assert!(Key::open(&bytes, password, &mut 0).is_err()); |
| 481 | } |
| 482 | for index in 0..metadata.len() { |
| 483 | let mut changed = metadata.to_vec(); |
| 484 | changed[index] ^= 0x80; |
| 485 | assert!(Key::open(&changed, password, &mut 0).is_err()); |
| 486 | } |
| 487 | } |
| 488 | } |