Skip to main content

playready/
cdm.rs

1//! Core module of playready-rs.
2
3use crate::{
4    binary_format::xmr_license::CipherType,
5    crypto::{
6        aes,
7        ecc_p256::{self, ToUntaggedBytes},
8        sha256,
9    },
10    device::Device,
11    license::License,
12    pssh::WrmHeader,
13    revocation_list::{RevocationListInfos, RevocationLists},
14    xml_key::XmlKey,
15    xml_utils,
16};
17use base64::{Engine, prelude::BASE64_STANDARD};
18use rand::{RngExt, rng};
19use std::{
20    fmt,
21    sync::{Arc, RwLock},
22    time::{SystemTime, UNIX_EPOCH},
23};
24
25const CLIENT_VERSION: &str = "10.0.16384.10011";
26const RGB_MAGIC_CONSTANT_ZERO: [u8; 16] = [
27    0x7e, 0xe9, 0xed, 0x4a, 0xf7, 0x73, 0x22, 0x4f, 0x00, 0xb8, 0xea, 0x7e, 0xfb, 0x02, 0x7c, 0xbb,
28];
29
30/// Structure representing key id (KID).
31#[derive(Clone)]
32pub struct KeyId([u8; 16]);
33
34impl KeyId {
35    fn from_uuid(u: &[u8; 16]) -> Self {
36        Self([
37            u[3], u[2], u[1], u[0], u[5], u[4], u[7], u[6], u[8], u[9], u[10], u[11], u[12], u[13],
38            u[14], u[15],
39        ])
40    }
41}
42
43impl From<[u8; 16]> for KeyId {
44    fn from(value: [u8; 16]) -> Self {
45        KeyId(value)
46    }
47}
48
49impl From<KeyId> for [u8; 16] {
50    fn from(value: KeyId) -> Self {
51        value.0
52    }
53}
54
55impl AsRef<[u8]> for KeyId {
56    fn as_ref(&self) -> &[u8] {
57        &self.0
58    }
59}
60
61impl fmt::Debug for KeyId {
62    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63        f.write_str(hex::encode(self.0).as_str())
64    }
65}
66
67impl fmt::Display for KeyId {
68    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69        f.write_str(hex::encode(self.0).as_str())
70    }
71}
72
73/// Structure representing content key.
74#[derive(Clone)]
75pub struct ContentKey(Box<[u8]>);
76
77impl From<Box<[u8]>> for ContentKey {
78    fn from(value: Box<[u8]>) -> Self {
79        ContentKey(value)
80    }
81}
82
83impl From<ContentKey> for Box<[u8]> {
84    fn from(value: ContentKey) -> Self {
85        value.0
86    }
87}
88
89impl AsRef<[u8]> for ContentKey {
90    fn as_ref(&self) -> &[u8] {
91        &self.0
92    }
93}
94
95impl fmt::Debug for ContentKey {
96    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
97        f.write_str(hex::encode(&self.0).as_str())
98    }
99}
100
101impl fmt::Display for ContentKey {
102    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
103        f.write_str(hex::encode(&self.0).as_str())
104    }
105}
106
107struct ContentIntegrityKey(Box<[u8]>);
108
109impl From<Box<[u8]>> for ContentIntegrityKey {
110    fn from(value: Box<[u8]>) -> Self {
111        ContentIntegrityKey(value)
112    }
113}
114
115impl From<ContentIntegrityKey> for Box<[u8]> {
116    fn from(value: ContentIntegrityKey) -> Self {
117        value.0
118    }
119}
120
121impl AsRef<[u8]> for ContentIntegrityKey {
122    fn as_ref(&self) -> &[u8] {
123        &self.0
124    }
125}
126
127impl fmt::Debug for ContentIntegrityKey {
128    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
129        f.write_str(hex::encode(&self.0).as_str())
130    }
131}
132
133impl fmt::Display for ContentIntegrityKey {
134    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135        f.write_str(hex::encode(&self.0).as_str())
136    }
137}
138
139type KidCkCi = (KeyId, ContentKey, ContentIntegrityKey);
140type KidCk = (KeyId, ContentKey);
141
142/// Provides the core functionality of PlayReady CDM.
143///
144/// The easiest way to construct it is to use [`Cdm::from_device()`] function.
145#[derive(Debug, Clone)]
146pub struct Cdm {
147    device: Arc<Device>,
148    rev_list_infos: Arc<RwLock<Option<RevocationListInfos>>>,
149}
150
151impl Cdm {
152    /// Creates CDM from the [`Device`].
153    pub fn from_device(device: Device) -> Self {
154        Self {
155            device: Arc::new(device),
156            rev_list_infos: Arc::new(RwLock::new(None)),
157        }
158    }
159
160    /// Sets initial revocation list infos.
161    pub fn with_initial_revocation_list_infos(
162        mut self,
163        rev_list_infos: RevocationListInfos,
164    ) -> Self {
165        self.rev_list_infos = Arc::new(RwLock::new(Some(rev_list_infos)));
166        self
167    }
168
169    /// Returns current revocation list infos (after they are parsed from challenge response).
170    pub fn get_current_revocation_list_infos(&self) -> Option<RevocationListInfos> {
171        self.rev_list_infos.read().unwrap().clone()
172    }
173
174    /// Generates XML containing license acquisition challenge.
175    /// XML prolog is deliberately missing as sometimes challenge XML is embedded in JSON.
176    ///
177    /// # Arguments
178    ///
179    /// `wrm_header` - header usually extracted from [`crate::pssh::Pssh`]
180    /// `custom_data` - optional custom data. WARNING: content of custom_data is NOT escaped. Passing malformed string may cause to produce invalid XML
181    pub fn get_license_challenge(
182        &self,
183        wrm_header: WrmHeader,
184        custom_data: Option<String>,
185    ) -> Result<String, crate::Error> {
186        let nonce = BASE64_STANDARD.encode(rng().random::<[u8; 16]>());
187        let xml_key = XmlKey::new();
188        let wmrm_cipher = BASE64_STANDARD.encode(Self::key_data(&xml_key));
189        let cert_cipher = BASE64_STANDARD.encode(self.cipher_data(&xml_key)?);
190
191        let protocol_version = match wrm_header.version() {
192            [4, 3, 0, 0] => "5",
193            [4, 2, 0, 0] => "4",
194            _ => "1",
195        };
196
197        let la_content_tag = xml_utils::build_digest_content(
198            String::from(protocol_version),
199            String::from(CLIENT_VERSION),
200            Self::client_time(),
201            wrm_header.into(),
202            nonce,
203            wmrm_cipher,
204            cert_cipher,
205            self.rev_list_infos.read().unwrap().as_ref(),
206            custom_data,
207        )?;
208
209        let la_content = xml_utils::render(&la_content_tag)?;
210        let la_hash = BASE64_STANDARD.encode(sha256::hash(&la_content));
211
212        let signed_info_tag = xml_utils::build_signed_info(la_hash)?;
213        let signed_info = xml_utils::render(&signed_info_tag)?;
214
215        let signature = ecc_p256::sign(self.device.signing_key(), &signed_info);
216        let signature = BASE64_STANDARD.encode(signature);
217
218        let public_key = self
219            .device
220            .signing_key()
221            .verifying_key()
222            .as_affine()
223            .to_untagged_bytes();
224        let public_key = BASE64_STANDARD.encode(public_key);
225
226        let challenge_tag = xml_utils::build_license_challenge(
227            la_content_tag,
228            signed_info_tag,
229            signature,
230            public_key,
231        )?;
232
233        let challenge = xml_utils::render(&challenge_tag)?;
234
235        String::from_utf8(challenge).map_err(|e| e.into())
236    }
237
238    /// Parses response (usually got from the license server) and returns vector of KID and key tuples.
239    pub fn get_keys_from_challenge_response(
240        &self,
241        response: &str,
242    ) -> Result<Vec<KidCk>, crate::Error> {
243        let licenses = xml_utils::parse_challenge_response(response)?;
244        if licenses.is_empty() {
245            return Err(crate::Error::LicenseMissingError);
246        }
247
248        let device_public_key = self
249            .device
250            .encryption_key()
251            .public()
252            .as_element()
253            .to_untagged_bytes();
254
255        let mut decrypted_keys = Vec::<KidCk>::with_capacity(licenses.len());
256
257        for license in licenses {
258            let license = match License::from_b64(license.as_str()) {
259                Ok(license) => license,
260                Err(e) => {
261                    log::error!("Failed to create license: {e:?}");
262                    continue;
263                }
264            };
265
266            if *license.public_key()? != *device_public_key {
267                return Err(crate::Error::PublicKeyMismatchError("device"));
268            }
269
270            let aux_key = license.auxiliary_key();
271
272            decrypted_keys.extend(license.encrypted_keys().iter().filter_map(|encrypted_key| {
273                let (kid, ck, ci) = self
274                    .decrypt_key(encrypted_key, aux_key)
275                    .inspect_err(|e| log::error!("Failed to decrypt key: {e:?}"))
276                    .ok()?;
277
278                let (msg, signature) = license
279                    .cmac_verification_data()
280                    .inspect_err(|e| {
281                        log::error!(
282                            "Failed to get MAC verification data {e:?}. Skipping KID: {kid:?}"
283                        )
284                    })
285                    .ok()?;
286
287                aes::verify_cmac(ci.as_ref(), msg, signature)
288                    .inspect_err(|e| log::error!("Signature mismatch {e:?}. Skipping KID: {kid:?}"))
289                    .ok()?;
290
291                Some((kid, ck))
292            }));
293        }
294        let rev_lists_update = RevocationLists::from_xml_str(response)
295            .inspect_err(|e| log::error!("Failed to parse revocation lists {e:?}"));
296
297        if let Ok(rev_lists_update) = rev_lists_update
298            && !rev_lists_update.is_empty()
299        {
300            let rev_list_infos_update =
301                RevocationListInfos::from_revocation_lists(&rev_lists_update);
302
303            let mut rev_list_infos = self.rev_list_infos.write().unwrap();
304            match rev_list_infos.as_mut() {
305                Some(r) => {
306                    let _ = r.update(&rev_list_infos_update);
307                }
308                None => {
309                    let _ = rev_list_infos.insert(rev_list_infos_update);
310                }
311            };
312        }
313
314        Ok(decrypted_keys)
315    }
316
317    fn decrypt_key(
318        &self,
319        encrypted_key: &(CipherType, &[u8; 16], &[u8]),
320        aux_key: Option<&[u8; 16]>,
321    ) -> Result<KidCkCi, crate::Error> {
322        if !matches!(
323            encrypted_key.0,
324            CipherType::Ecc256 | CipherType::Ecc256WithKZ | CipherType::Ecc256ViaSymmetric
325        ) {
326            return Err(crate::Error::UnsupportedCipherTypeError(encrypted_key.0));
327        }
328
329        let decrypted = ecc_p256::decrypt(self.device.encryption_key().secret(), encrypted_key.2)?;
330
331        let (ci, ck) =
332            match aux_key {
333                None => {
334                    let ci = decrypted
335                        .get(..16)
336                        .ok_or(crate::Error::SliceOutOfBoundsError(
337                            "decrypted",
338                            decrypted.len(),
339                        ))?
340                        .to_vec();
341                    let ck = decrypted
342                        .get(16..32)
343                        .ok_or(crate::Error::SliceOutOfBoundsError(
344                            "decrypted",
345                            decrypted.len(),
346                        ))?
347                        .to_vec();
348
349                    (ci, ck)
350                }
351                Some(aux_key) => {
352                    let ck = decrypted
353                        .iter()
354                        .copied()
355                        .skip(1)
356                        .step_by(2)
357                        .take(16)
358                        .collect::<Vec<_>>();
359
360                    if encrypted_key.0 != CipherType::Ecc256ViaSymmetric {
361                        let ci = decrypted
362                            .iter()
363                            .copied()
364                            .step_by(2)
365                            .take(16)
366                            .collect::<Vec<_>>();
367
368                        (ci, ck)
369                    } else {
370                        let embedded_root_license = encrypted_key.2.get(..144).ok_or(
371                            crate::Error::SliceOutOfBoundsError(
372                                "encrypted_key",
373                                encrypted_key.2.len(),
374                            ),
375                        )?;
376                        let embedded_leaf_license = encrypted_key.2.get(144..).ok_or(
377                            crate::Error::SliceOutOfBoundsError(
378                                "encrypted_key",
379                                encrypted_key.2.len(),
380                            ),
381                        )?;
382
383                        let rgb_key = ck
384                            .iter()
385                            .zip(RGB_MAGIC_CONSTANT_ZERO)
386                            .map(|v| v.0 ^ v.1)
387                            .collect::<Vec<_>>();
388
389                        let content_key_prime = aes::encrypt_ecb(&ck, &rgb_key)?;
390                        let uplink_x_key = aes::encrypt_ecb(&content_key_prime, aux_key)?;
391
392                        let secondary_key = aes::encrypt_ecb(
393                            &ck,
394                            embedded_root_license.get(128..).ok_or(
395                                crate::Error::SliceOutOfBoundsError(
396                                    "embedded_root_license",
397                                    embedded_root_license.len(),
398                                ),
399                            )?,
400                        )?;
401
402                        let embedded_leaf_license =
403                            aes::encrypt_ecb(&uplink_x_key, embedded_leaf_license)?;
404                        let embedded_leaf_license =
405                            aes::encrypt_ecb(&secondary_key, &embedded_leaf_license)?;
406
407                        let ci = embedded_leaf_license
408                            .get(..16)
409                            .ok_or(crate::Error::SliceOutOfBoundsError(
410                                "embedded_leaf_license",
411                                embedded_leaf_license.len(),
412                            ))?
413                            .to_vec();
414                        let ck = embedded_leaf_license
415                            .get(16..)
416                            .ok_or(crate::Error::SliceOutOfBoundsError(
417                                "embedded_leaf_license",
418                                embedded_leaf_license.len(),
419                            ))?
420                            .to_vec();
421
422                        (ci, ck)
423                    }
424                }
425            };
426
427        Ok((
428            KeyId::from_uuid(encrypted_key.1),
429            ContentKey::from(ck.into_boxed_slice()),
430            ContentIntegrityKey::from(ci.into_boxed_slice()),
431        ))
432    }
433
434    fn cipher_data(&self, xml_key: &XmlKey) -> Result<Vec<u8>, crate::Error> {
435        let body_tag = xml_utils::build_cipher_data(
436            BASE64_STANDARD.encode(self.device.certificate_chain().raw()),
437        )?;
438
439        let body = xml_utils::render(&body_tag)?;
440        let ciphertext = aes::encrypt_cbc(xml_key.aes_key(), xml_key.aes_iv(), &body)?;
441
442        Ok([xml_key.aes_iv(), ciphertext.as_slice()].concat())
443    }
444
445    fn key_data(xml_key: &XmlKey) -> Vec<u8> {
446        ecc_p256::encrypt(
447            ecc_p256::wmrm_public_key(),
448            xml_key.public_key().as_element(),
449        )
450    }
451
452    fn client_time() -> String {
453        SystemTime::now()
454            .duration_since(UNIX_EPOCH)
455            .unwrap()
456            .as_secs()
457            .to_string()
458    }
459}