1use crate::{binary_format, xml_utils};
4use base64::{Engine, prelude::BASE64_STANDARD};
5use binrw::{BinRead, BinWrite};
6use byteorder::{LittleEndian, ReadBytesExt};
7use std::{
8 fs::File,
9 io::{Cursor, Read},
10 path::Path,
11};
12
13pub type ListId = [u8; 16];
15
16#[derive(Debug, Clone, Default)]
18pub struct RevocationListInfo {
19 list_id: ListId,
20 version: u32,
21}
22
23impl RevocationListInfo {
24 pub fn new(list_id: ListId, version: u32) -> Self {
26 Self { list_id, version }
27 }
28
29 pub fn list_id(&self) -> &ListId {
31 &self.list_id
32 }
33
34 pub fn version(&self) -> u32 {
36 self.version
37 }
38}
39
40#[derive(Debug, Clone, Default)]
42pub struct RevocationListInfos {
43 inner: Vec<RevocationListInfo>,
44}
45
46impl RevocationListInfos {
47 const PLAYREADY_RUNTIME_ID: ListId = [
48 0x8a, 0x8c, 0x9d, 0x4e, 0x52, 0xb6, 0xa7, 0x45, 0x97, 0x91, 0x69, 0x25, 0xa6, 0xb4, 0x79,
49 0x1f,
50 ];
51
52 const PLAYREADY_APPLICATION_ID: ListId = [
53 0x80, 0x2e, 0x08, 0x28, 0xa3, 0xc7, 0xb1, 0x40, 0x82, 0x56, 0x19, 0xe5, 0xb6, 0xd8, 0x9b,
54 0x27,
55 ];
56
57 const REV_INFO_V2_ID: ListId = [
58 0x11, 0xff, 0xd1, 0x52, 0x88, 0xd3, 0xdd, 0x4e, 0x82, 0xb7, 0x68, 0xea, 0x4c, 0x20, 0xa1,
59 0x6c,
60 ];
61
62 const WMDRMNET_ID: ListId = [
63 0x04, 0xe6, 0x75, 0xcd, 0x3d, 0x54, 0x9c, 0x4a, 0x9f, 0x09, 0xfe, 0x6d, 0x24, 0xe8, 0xbf,
64 0x90,
65 ];
66
67 pub fn initial() -> Self {
69 Self {
70 inner: vec![
71 RevocationListInfo::new(RevocationListInfos::PLAYREADY_RUNTIME_ID, 0),
72 RevocationListInfo::new(RevocationListInfos::PLAYREADY_APPLICATION_ID, 0),
73 RevocationListInfo::new(RevocationListInfos::REV_INFO_V2_ID, 0),
74 RevocationListInfo::new(RevocationListInfos::WMDRMNET_ID, 0),
75 ],
76 }
77 }
78
79 pub fn iter(&self) -> impl Iterator<Item = &RevocationListInfo> {
81 self.inner.iter()
82 }
83
84 pub fn from_revocation_lists(rev_lists: &RevocationLists) -> Self {
86 let mut inner = Vec::with_capacity(rev_lists.inner.len());
87
88 for rev_list in &rev_lists.inner {
89 match rev_list.list_id {
90 Self::PLAYREADY_RUNTIME_ID | Self::PLAYREADY_APPLICATION_ID => {
91 let Ok(list) = binary_format::revocation_list::BPrRLSigned::read(
92 &mut Cursor::new(&rev_list.list_data),
93 )
94 .inspect_err(|e| log::error!("Failed to parse BPrRLSigned: {e:?}")) else {
95 continue;
96 };
97
98 inner.push(RevocationListInfo::new(rev_list.list_id, list.data.version));
99 }
100 Self::REV_INFO_V2_ID => {
101 let Ok(list) = binary_format::revocation_list::BRevInfoSigned::read(
102 &mut Cursor::new(&rev_list.list_data),
103 )
104 .inspect_err(|e| log::error!("Failed to parse BRevInfoSigned: {e:?}")) else {
105 continue;
106 };
107
108 inner.push(RevocationListInfo::new(
109 Self::REV_INFO_V2_ID,
110 list.data.sequence_number,
111 ));
112 }
113 Self::WMDRMNET_ID => {
114 let mut xml_text = vec![0u16; rev_list.list_data.len() / 2];
115
116 if Cursor::new(&rev_list.list_data)
117 .read_u16_into::<LittleEndian>(&mut xml_text)
118 .inspect_err(|e| log::error!("Failed to read WMDRMNET string: {e:?}"))
119 .is_err()
120 {
121 continue;
122 }
123
124 let Ok(mut xml_text) = String::from_utf16(&xml_text) else {
125 continue;
126 };
127
128 xml_text.insert_str(0, "<DUMMY>"); xml_text.push_str("</DUMMY>");
130
131 let Ok(Some(template)) = xml_utils::parse_wmdrmnet_list_data(&xml_text)
132 .inspect_err(|e| log::error!("Failed to parse WMDRMNET list data: {e:?}"))
133 else {
134 continue;
135 };
136
137 let Ok(template) = BASE64_STANDARD
138 .decode(template)
139 .map_err(|e| log::error!("Failed to decode list data: {e:?}"))
140 else {
141 continue;
142 };
143
144 let Ok(list) = binary_format::revocation_list::WMDRMNETSigned::read(
145 &mut Cursor::new(template),
146 )
147 .inspect_err(|e| log::error!("Failed to parse WMDRMNETSigned: {e:?}")) else {
148 continue;
149 };
150
151 inner.push(RevocationListInfo::new(
152 Self::WMDRMNET_ID,
153 list.data.version,
154 ));
155 }
156 _ => {}
157 }
158 }
159
160 Self { inner }
161 }
162
163 pub fn from_bytes(bytes: &[u8]) -> Result<Self, crate::Error> {
165 let prl = binary_format::revocation_list::PrlRevListInfos::read(&mut Cursor::new(bytes))?;
166
167 let inner = prl
168 .rev_list_infos
169 .iter()
170 .map(|li| RevocationListInfo::new(li.list_id, li.version))
171 .collect();
172
173 Ok(Self { inner })
174 }
175
176 pub fn from_prl(path: impl AsRef<Path>) -> Result<Self, crate::Error> {
178 let mut file = File::open(path)?;
179 let mut bytes = Vec::<u8>::new();
180 file.read_to_end(&mut bytes)?;
181
182 Self::from_bytes(&bytes)
183 }
184
185 pub fn write_to_file(&self, path: impl AsRef<Path>) -> Result<(), crate::Error> {
187 let mut file = File::create(path)?;
188
189 let rev_list_infos: Vec<_> = self
190 .inner
191 .iter()
192 .map(|li| binary_format::revocation_list::PrlRevListInfo {
193 list_id: li.list_id,
194 version: li.version,
195 })
196 .collect();
197
198 let rev_list_infos = binary_format::revocation_list::PrlRevListInfos {
199 list_count: u32::try_from(rev_list_infos.len()).unwrap(),
200 rev_list_infos,
201 };
202
203 rev_list_infos.write(&mut file)?;
204
205 Ok(())
206 }
207
208 pub fn update(&mut self, other: &Self) -> bool {
212 let mut updated = false;
213
214 for other_list in &other.inner {
215 if let Some(current_list) = self
216 .inner
217 .iter_mut()
218 .find(|r| r.list_id == other_list.list_id)
219 {
220 current_list.version = other_list.version;
221 updated = true;
222 } else {
223 self.inner.push(RevocationListInfo::new(
224 other_list.list_id,
225 other_list.version,
226 ));
227 updated = true;
228 }
229 }
230
231 updated
232 }
233}
234
235#[derive(Debug, Clone, Default)]
237pub struct RevocationList {
238 list_id: ListId,
239 list_data: Vec<u8>,
240}
241
242#[derive(Debug, Clone, Default)]
244pub struct RevocationLists {
245 inner: Vec<RevocationList>,
246}
247
248impl RevocationLists {
249 pub fn from_xml_str(text: &str) -> Result<Self, crate::Error> {
251 let parsed = xml_utils::parse_revocation_lists(text)?;
252 let mut inner = Vec::with_capacity(parsed.len());
253
254 for (list_id, list_data) in parsed {
255 let list_id_vec = BASE64_STANDARD.decode(list_id)?;
256 let list_data = BASE64_STANDARD.decode(list_data)?;
257
258 let mut list_id = ListId::default();
259 if list_id_vec.len() != list_id.len() {
260 log::error!("Wrong length of list id for {list_id_vec:?}");
261 continue;
262 }
263 list_id.copy_from_slice(&list_id_vec);
264
265 inner.push(RevocationList { list_id, list_data });
266 }
267
268 Ok(Self { inner })
269 }
270
271 pub fn is_empty(&self) -> bool {
273 self.inner.is_empty()
274 }
275
276 pub fn get_revoked_public_key_digests(&self) -> Result<RevokedPublicKeyDigests, crate::Error> {
278 let rev_list = self
279 .inner
280 .iter()
281 .find(|r| r.list_id == RevocationListInfos::PLAYREADY_RUNTIME_ID);
282
283 let Some(rev_list) = rev_list else {
284 return Ok(RevokedPublicKeyDigests(Vec::new()));
285 };
286
287 let rev_list = binary_format::revocation_list::BPrRLSigned::read(&mut Cursor::new(
288 &rev_list.list_data,
289 ))?;
290
291 Ok(RevokedPublicKeyDigests(rev_list.data.revocation_entries))
292 }
293}
294
295pub struct RevokedPublicKeyDigests(Vec<[u8; 32]>);
297
298impl RevokedPublicKeyDigests {
299 pub fn contains(&self, key: &[u8; 32]) -> bool {
301 self.0.contains(key)
302 }
303}
304
305#[cfg(test)]
306mod test {
307 use crate::{revocation_list::RevocationListInfos, revocation_list::RevocationLists};
308 use path_macro::path;
309 use std::{fs::File, io::Read};
310
311 fn read_rev_lists_from_file(file_name: &str) -> RevocationLists {
312 let path = path!(env!("CARGO_MANIFEST_DIR") / "testfiles" / file_name);
313 let mut file = File::open(path).unwrap();
314 let mut xml_str = String::new();
315 file.read_to_string(&mut xml_str).unwrap();
316
317 RevocationLists::from_xml_str(&xml_str).unwrap()
318 }
319
320 #[test]
321 fn test_update_from_initial_to_86() {
322 let mut infos = RevocationListInfos::initial();
323 let expected_versions = [0, 0, 0, 0];
324 for (info, expected_version) in infos.iter().zip(expected_versions) {
325 assert_eq!(info.version(), expected_version);
326 }
327
328 let rev_lists = read_rev_lists_from_file("RevInfo2v86_20260121.xml");
329 let infos_update = RevocationListInfos::from_revocation_lists(&rev_lists);
330 infos.update(&infos_update);
331 let expected_versions = [21, 11, 86, 12];
332
333 for (info, expected_version) in infos.iter().zip(expected_versions) {
334 assert_eq!(info.version(), expected_version);
335 }
336 }
337
338 #[test]
339 fn test_update_from_86_to_87() {
340 let rev_lists = read_rev_lists_from_file("RevInfo2v86_20260121.xml");
341 let mut infos = RevocationListInfos::from_revocation_lists(&rev_lists);
342 let expected_versions = [86, 21, 11, 12];
343 for (info, expected_version) in infos.iter().zip(expected_versions) {
344 assert_eq!(info.version(), expected_version);
345 }
346
347 let rev_lists = read_rev_lists_from_file("RevInfo2v87_20260316.xml");
348 let infos_update = RevocationListInfos::from_revocation_lists(&rev_lists);
349 infos.update(&infos_update);
350 let expected_versions = [87, 22, 11, 12];
351
352 for (info, expected_version) in infos.iter().zip(expected_versions) {
353 assert_eq!(info.version(), expected_version);
354 }
355 }
356}