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