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        match encrypted_key.0 {
312            CipherType::Ecc256 | CipherType::Ecc256WithKZ | CipherType::Ecc256ViaSymmetric => (),
313            _ => {
314                return Err(crate::Error::UnsupportedCipherTypeError(encrypted_key.0));
315            }
316        };
317
318        let decrypted = ecc_p256::decrypt(self.device.encryption_key().secret(), encrypted_key.2)?;
319
320        let mut ci = decrypted
321            .get(..16)
322            .ok_or(crate::Error::SliceOutOfBoundsError(
323                "decrypted",
324                decrypted.len(),
325            ))?
326            .to_vec();
327        let mut ck = decrypted
328            .get(16..32)
329            .ok_or(crate::Error::SliceOutOfBoundsError(
330                "decrypted",
331                decrypted.len(),
332            ))?
333            .to_vec();
334
335        if let Some(aux_key) = aux_key {
336            ci = decrypted.iter().copied().step_by(2).take(16).collect();
337            ck = decrypted
338                .iter()
339                .copied()
340                .skip(1)
341                .step_by(2)
342                .take(16)
343                .collect();
344
345            if encrypted_key.0 == CipherType::Ecc256ViaSymmetric {
346                let embedded_root_license =
347                    &encrypted_key
348                        .2
349                        .get(..144)
350                        .ok_or(crate::Error::SliceOutOfBoundsError(
351                            "encrypted_key",
352                            encrypted_key.2.len(),
353                        ))?;
354                let embedded_leaf_license =
355                    &encrypted_key
356                        .2
357                        .get(144..)
358                        .ok_or(crate::Error::SliceOutOfBoundsError(
359                            "encrypted_key",
360                            encrypted_key.2.len(),
361                        ))?;
362
363                let rgb_key = ck
364                    .iter()
365                    .zip(RGB_MAGIC_CONSTANT_ZERO)
366                    .map(|v| v.0 ^ v.1)
367                    .collect::<Vec<_>>();
368
369                let content_key_prime = aes::encrypt_ecb(&ck, &rgb_key)?;
370                let uplink_x_key = aes::encrypt_ecb(&content_key_prime, aux_key)?;
371
372                let secondary_key = aes::encrypt_ecb(
373                    &ck,
374                    embedded_root_license
375                        .get(128..)
376                        .ok_or(crate::Error::SliceOutOfBoundsError(
377                            "embedded_root_license",
378                            embedded_root_license.len(),
379                        ))?,
380                )?;
381
382                let embedded_leaf_license = aes::encrypt_ecb(&uplink_x_key, embedded_leaf_license)?;
383                let embedded_leaf_license =
384                    aes::encrypt_ecb(&secondary_key, &embedded_leaf_license)?;
385
386                ci = embedded_leaf_license
387                    .get(..16)
388                    .ok_or(crate::Error::SliceOutOfBoundsError(
389                        "embedded_leaf_license",
390                        embedded_leaf_license.len(),
391                    ))?
392                    .to_vec();
393                ck = embedded_leaf_license
394                    .get(16..)
395                    .ok_or(crate::Error::SliceOutOfBoundsError(
396                        "embedded_leaf_license",
397                        embedded_leaf_license.len(),
398                    ))?
399                    .to_vec();
400            }
401        }
402
403        Ok((
404            KeyId::from_uuid(encrypted_key.1),
405            ContentKey::from(ck.into_boxed_slice()),
406            ContentIntegrityKey::from(ci.into_boxed_slice()),
407        ))
408    }
409
410    fn cipher_data(&self) -> Result<Vec<u8>, crate::Error> {
411        let body_tag =
412            xml_utils::build_cipher_data(BASE64_STANDARD.encode(self.device.group_certificate()))?;
413
414        let body = xml_utils::render(&body_tag)?;
415        let ciphertext = aes::encrypt_cbc(self.xml_key.aes_key(), self.xml_key.aes_iv(), &body)?;
416
417        Ok([self.xml_key.aes_iv(), ciphertext.as_slice()].concat())
418    }
419
420    fn key_data(&self) -> Vec<u8> {
421        ecc_p256::encrypt(
422            ecc_p256::wmrm_public_key(),
423            self.xml_key.public_key().as_element(),
424        )
425    }
426
427    fn client_time() -> String {
428        SystemTime::now()
429            .duration_since(UNIX_EPOCH)
430            .unwrap()
431            .as_secs()
432            .to_string()
433    }
434}