1use pdfrum_object::{Dict, Name, NoResolve, Object, PdfString};
16
17use zeroize::Zeroize;
18
19use crate::Error;
20use crate::SecurityHandler;
21use crate::permissions::Permissions;
22use crate::primitives::aes_cbc_encrypt;
23use crate::standard::{r6_prepared, revision6_hash};
24
25pub const ENTROPY_LEN: usize = 68;
30
31pub struct KeyMaterial([u8; ENTROPY_LEN]);
42
43impl KeyMaterial {
44 pub fn from_os() -> Result<Self, Error> {
52 let mut bytes = [0u8; ENTROPY_LEN];
53 getrandom::fill(&mut bytes).map_err(|_| Error::NoEntropy)?;
54 Ok(Self(bytes))
55 }
56}
57
58impl Drop for KeyMaterial {
59 fn drop(&mut self) {
60 self.0.zeroize();
61 }
62}
63
64const NAMED_PERMISSION_BITS: u32 = 0x0F3C;
67
68pub fn standard_r6(
81 user: &[u8],
82 owner: &[u8],
83 permissions: Permissions,
84 encrypt_metadata: bool,
85 key_material: &KeyMaterial,
86) -> Result<(Dict, SecurityHandler), Error> {
87 let entropy = &key_material.0;
88 let owner = if owner.is_empty() { user } else { owner };
89 let user_prepared = r6_prepared(6, user).ok_or(Error::WrongPassword)?;
90 let owner_prepared = r6_prepared(6, owner).ok_or(Error::WrongPassword)?;
91
92 let mut file_key: [u8; 32] = slice(entropy, 0)?;
93 let user_validation: [u8; 8] = slice(entropy, 32)?;
94 let user_key_salt: [u8; 8] = slice(entropy, 40)?;
95 let owner_validation: [u8; 8] = slice(entropy, 48)?;
96 let owner_key_salt: [u8; 8] = slice(entropy, 56)?;
97 let perms_random: [u8; 4] = slice(entropy, 64)?;
98
99 let mut u = [0u8; 48];
103 u[..32].copy_from_slice(&revision6_hash(&user_prepared, user_validation, None));
104 u[32..40].copy_from_slice(&user_validation);
105 u[40..48].copy_from_slice(&user_key_salt);
106 let ue = wrap(
107 &revision6_hash(&user_prepared, user_key_salt, None),
108 &file_key,
109 )?;
110
111 let mut o = [0u8; 48];
113 o[..32].copy_from_slice(&revision6_hash(&owner_prepared, owner_validation, Some(&u)));
114 o[32..40].copy_from_slice(&owner_validation);
115 o[40..48].copy_from_slice(&owner_key_salt);
116 let oe = wrap(
117 &revision6_hash(&owner_prepared, owner_key_salt, Some(&u)),
118 &file_key,
119 )?;
120
121 let p = permissions.bits() | !NAMED_PERMISSION_BITS;
128 let mut perms = [0u8; 16];
129 perms[..4].copy_from_slice(&p.to_le_bytes());
130 perms[4..8].copy_from_slice(&[0xFF; 4]);
131 perms[8] = if encrypt_metadata { b'T' } else { b'F' };
132 perms[9..12].copy_from_slice(b"adb");
133 perms[12..16].copy_from_slice(&perms_random);
134 aes_cbc_encrypt(&file_key, &[0u8; 16], &mut perms).map_err(|_| Error::WrongPassword)?;
135 file_key.zeroize();
136
137 let name = |s: &str| Object::Name(Name::from(s));
138 let bytes = |b: &[u8]| Object::Str(PdfString::hex(b));
139 let std_cf = Dict::from_pairs([
140 (Name::from("CFM"), name("AESV3")),
141 (Name::from("AuthEvent"), name("DocOpen")),
142 (Name::from("Length"), Object::Int(32)),
143 ]);
144 let cf = Dict::from_pairs([(Name::from("StdCF"), Object::Dict(std_cf))]);
145 #[expect(
146 clippy::cast_possible_wrap,
147 reason = "/P is the same 32 bits read as a signed integer, per the standard"
148 )]
149 let p_signed = i64::from(p as i32);
150 let dict = Dict::from_pairs([
151 (Name::from("Filter"), name("Standard")),
152 (Name::from("V"), Object::Int(5)),
153 (Name::from("R"), Object::Int(6)),
154 (Name::from("Length"), Object::Int(256)),
155 (Name::from("P"), Object::Int(p_signed)),
156 (Name::from("O"), bytes(&o)),
157 (Name::from("U"), bytes(&u)),
158 (Name::from("OE"), bytes(&oe)),
159 (Name::from("UE"), bytes(&ue)),
160 (Name::from("Perms"), bytes(&perms)),
161 (Name::from("CF"), Object::Dict(cf)),
162 (Name::from("StmF"), name("StdCF")),
163 (Name::from("StrF"), name("StdCF")),
164 (
165 Name::from("EncryptMetadata"),
166 Object::Bool(encrypt_metadata),
167 ),
168 ]);
169
170 let handler = SecurityHandler::from_encrypt_dict(&dict, &[], owner, &NoResolve)?;
174 Ok((dict, handler))
175}
176
177fn wrap(intermediate: &[u8; 32], key: &[u8; 32]) -> Result<[u8; 32], Error> {
180 let mut wrapped = *key;
181 aes_cbc_encrypt(intermediate, &[0u8; 16], &mut wrapped).map_err(|_| Error::WrongPassword)?;
182 Ok(wrapped)
183}
184
185fn slice<const N: usize>(entropy: &[u8; ENTROPY_LEN], at: usize) -> Result<[u8; N], Error> {
186 entropy
187 .get(at..at + N)
188 .and_then(|s| <[u8; N]>::try_from(s).ok())
189 .ok_or(Error::WrongPassword)
190}
191
192#[cfg(test)]
193mod tests {
194 use super::{KeyMaterial, standard_r6};
195 use crate::permissions::Permissions;
196 use crate::{CryptClass, SecurityHandler};
197 use pdfrum_object::{NoResolve, ObjRef};
198
199 fn entropy() -> KeyMaterial {
200 KeyMaterial::from_os().unwrap()
201 }
202
203 #[test]
204 fn the_file_opens_with_either_password_and_not_with_a_wrong_one() {
205 let perms = Permissions {
206 print: true,
207 ..Permissions::NONE
208 };
209 let (dict, handler) = standard_r6(b"user", b"owner", perms, true, &entropy()).unwrap();
210 let as_user = SecurityHandler::from_encrypt_dict(&dict, &[], b"user", &NoResolve).unwrap();
211 assert!(!as_user.owner_unlocked());
212 assert!(as_user.permissions().print && !as_user.permissions().copy);
213 let as_owner =
214 SecurityHandler::from_encrypt_dict(&dict, &[], b"owner", &NoResolve).unwrap();
215 assert!(as_owner.owner_unlocked());
216 assert!(SecurityHandler::from_encrypt_dict(&dict, &[], b"nope", &NoResolve).is_err());
217 assert!(
218 handler.owner_unlocked(),
219 "the handler this function hands back is the owner's"
220 );
221 }
222
223 #[test]
224 fn what_the_handler_enciphers_the_opened_one_deciphers() {
225 let (dict, handler) =
226 standard_r6(b"", b"secret", Permissions::ALL, true, &entropy()).unwrap();
227 let obj = ObjRef::new(7, 0);
228 let iv = crate::Iv([3u8; 16]);
229 let enciphered = handler.encrypt(obj, CryptClass::Stream, iv, b"hello, cipher");
230 assert_ne!(enciphered, b"hello, cipher");
231 let reader = SecurityHandler::from_encrypt_dict(&dict, &[], b"", &NoResolve).unwrap();
232 assert_eq!(
233 reader.decrypt(obj, CryptClass::Stream, &enciphered),
234 b"hello, cipher"
235 );
236 }
237
238 #[test]
239 fn an_empty_owner_password_falls_back_to_the_user_password() {
240 let (dict, _) = standard_r6(b"pw", b"", Permissions::ALL, false, &entropy()).unwrap();
241 let opened = SecurityHandler::from_encrypt_dict(&dict, &[], b"pw", &NoResolve).unwrap();
242 assert!(opened.owner_unlocked());
243 assert!(!opened.encrypt_metadata());
244 }
245
246 #[test]
247 fn perms_random_bytes_are_not_the_file_key_prefix() {
248 let mut bytes = [0u8; super::ENTROPY_LEN];
249 bytes[..32].fill(0xAA);
250 bytes[32..64].fill(0x11);
251 bytes[64..68].fill(0xBB);
252 let material = super::KeyMaterial(bytes);
253 let (dict, _) = standard_r6(b"user", b"owner", Permissions::ALL, true, &material).unwrap();
254 let perms = dict
255 .string(&pdfrum_object::Name::from("Perms"))
256 .expect("standard_r6 writes /Perms");
257 let mut block = [0u8; 16];
258 block.copy_from_slice(perms.bytes.as_ref());
259 crate::primitives::aes_cbc_decrypt(&[0xAA; 32], &[0u8; 16], &mut block)
260 .expect("the file key decrypts /Perms");
261 assert_eq!(&block[12..16], &[0xBB; 4], "Algorithm 10's four bytes");
262 assert_ne!(
263 &block[12..16],
264 &bytes[..4],
265 "must not reuse the file-key prefix"
266 );
267 }
268}