1use super::{Error, Result, invalid};
2use aes::cipher::{BlockModeDecrypt, BlockModeEncrypt, KeyIvInit, block_padding::NoPadding};
3use base64::{Engine, engine::general_purpose::STANDARD};
4use sha1::{Digest, Sha1};
5use subtle::ConstantTimeEq;
6use zeroize::{Zeroize, Zeroizing};
7
8const NS: &str = "http://schemas.microsoft.com/office/2006/encryption";
9const PASSWORD_NS: &str = "http://schemas.microsoft.com/office/2006/keyEncryptor/password";
10
11#[derive(Clone)]
12pub(super) struct Key {
13 value: Zeroizing<[u8; 16]>,
14 file_iv: [u8; 16],
15}
16
17fn 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
32fn 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
44fn 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
70fn 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
78fn 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
85fn 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.
93const SPINS: u32 = 100_000;
94/// MS-OFFCRYPTO's block keys for the verifier input, its hash and the wrapped key.
95const VERIFIER_INPUT: [u8; 8] = [0xfe, 0xa7, 0xd2, 0x76, 0x3b, 0x4b, 0x9e, 0x79];
96const VERIFIER_VALUE: [u8; 8] = [0xd7, 0xaa, 0x0f, 0x6d, 0x30, 0x61, 0x34, 0x4e];
97const 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.
101fn 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.
118fn 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.
127fn 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
134impl 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)]
367mod 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}