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::{prelude::BASE64_STANDARD, Engine};
17use rand::{thread_rng, Rng};
18use std::{
19    fmt,
20    sync::{atomic::AtomicU32, Arc},
21    time::{SystemTime, UNIX_EPOCH},
22};
23
24const PROTOCOL_VERSION: &str = "1";
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/// The entry point 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}
149
150/// Represents CDM session. Provides the core functionality of CDM.
151#[derive(Debug, Clone)]
152pub struct Session {
153    id: u32,
154    device: Arc<Device>,
155    xml_key: XmlKey,
156}
157
158impl Cdm {
159    /// Creates CDM from the [`Device`].
160    pub fn from_device(device: Device) -> Self {
161        let device = Arc::new(device);
162
163        Self { device }
164    }
165
166    /// Opens new CDM session.
167    pub fn open_session(&self) -> Session {
168        static SESSION_COUNTER: AtomicU32 = AtomicU32::new(0);
169        let id = SESSION_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
170
171        Session::new(id, Arc::clone(&self.device))
172    }
173}
174
175impl Session {
176    fn new(id: u32, device: Arc<Device>) -> Self {
177        let xml_key = XmlKey::new();
178
179        Self {
180            id,
181            device,
182            xml_key,
183        }
184    }
185
186    /// Returns ID of the session.
187    pub fn id(&self) -> u32 {
188        self.id
189    }
190
191    /// Generates XML containing license acquisition challenge.
192    /// XML prolog is deliberately missing as sometimes challenge XML is embedded in JSON.
193    ///
194    /// # Arguments
195    ///
196    /// `wrm_header` - header usually extracted from [`crate::pssh::Pssh`]
197    pub fn get_license_challenge(&self, wrm_header: WrmHeader) -> Result<String, crate::Error> {
198        let nonce = BASE64_STANDARD.encode(thread_rng().gen::<[u8; 16]>());
199        let wmrm_cipher = BASE64_STANDARD.encode(self.key_data());
200        let cert_cipher = BASE64_STANDARD.encode(self.cipher_data()?);
201
202        let la_content_tag = xml_utils::build_digest_content(
203            String::from(PROTOCOL_VERSION),
204            String::from(CLIENT_VERSION),
205            Self::client_time(),
206            wrm_header.into(),
207            nonce,
208            wmrm_cipher,
209            cert_cipher,
210        )?;
211
212        let la_content = xml_utils::render(&la_content_tag)?;
213        let la_hash = BASE64_STANDARD.encode(sha256::hash(&la_content));
214
215        let signed_info_tag = xml_utils::build_signed_info(la_hash)?;
216        let signed_info = xml_utils::render(&signed_info_tag)?;
217
218        let signature = ecc_p256::sign(self.device.signing_key(), &signed_info);
219        let signature = BASE64_STANDARD.encode(signature);
220
221        let public_key = self
222            .device
223            .signing_key()
224            .verifying_key()
225            .as_affine()
226            .to_untagged_bytes();
227        let public_key = BASE64_STANDARD.encode(public_key);
228
229        let challenge_tag = xml_utils::build_license_challenge(
230            la_content_tag,
231            signed_info_tag,
232            signature,
233            public_key,
234        )?;
235
236        let challenge = xml_utils::render(&challenge_tag)?;
237
238        String::from_utf8(challenge).map_err(|e| e.into())
239    }
240
241    /// Parses response (usually got from the license server) and returns vector of KID and key tuples.
242    pub fn get_keys_from_challenge_response(
243        &self,
244        response: &str,
245    ) -> Result<Vec<KidCk>, crate::Error> {
246        let licenses = xml_utils::parse_challenge_response(response)?;
247        if licenses.is_empty() {
248            return Err(crate::Error::LicenseMissingError);
249        }
250
251        let device_public_key = self
252            .device
253            .encryption_key()
254            .public()
255            .as_element()
256            .to_untagged_bytes();
257
258        let mut decrypted_keys = Vec::<KidCk>::with_capacity(licenses.len());
259
260        for license in licenses {
261            let license = match License::from_b64(license.as_str()) {
262                Ok(license) => license,
263                Err(e) => {
264                    log::error!("Failed to create license: {e:?}");
265                    continue;
266                }
267            };
268
269            if *license.public_key()? != *device_public_key {
270                return Err(crate::Error::PublicKeyMismatchError("device"));
271            }
272
273            let aux_key = license.auxiliary_key();
274
275            decrypted_keys.extend(license.encrypted_keys().iter().filter_map(|encrypted_key| {
276                let (kid, ck, ci) = self
277                    .decrypt_key(encrypted_key, aux_key)
278                    .inspect_err(|e| log::error!("Failed to decrypt key: {e:?}"))
279                    .ok()?;
280
281                let (msg, signature) = license
282                    .cmac_verification_data()
283                    .inspect_err(|e| {
284                        log::error!(
285                            "Failed to get MAC verification data {e:?}. Skipping KID: {kid:?}"
286                        )
287                    })
288                    .ok()?;
289
290                aes::verify_cmac(ci.as_ref(), msg, signature)
291                    .inspect_err(|e| log::error!("Signature mismatch {e:?}. Skipping KID: {kid:?}"))
292                    .ok()?;
293
294                Some((kid, ck))
295            }));
296        }
297
298        Ok(decrypted_keys)
299    }
300
301    fn decrypt_key(
302        &self,
303        encrypted_key: &(CipherType, &[u8; 16], &[u8]),
304        aux_key: Option<&[u8; 16]>,
305    ) -> Result<KidCkCi, crate::Error> {
306        match encrypted_key.0 {
307            CipherType::Ecc256 | CipherType::Ecc256WithKZ | CipherType::Ecc256ViaSymmetric => (),
308            _ => {
309                return Err(crate::Error::UnsupportedCipherTypeError(encrypted_key.0));
310            }
311        };
312
313        let decrypted = ecc_p256::decrypt(self.device.encryption_key().secret(), encrypted_key.2)?;
314
315        let mut ci = decrypted
316            .get(..16)
317            .ok_or(crate::Error::SliceOutOfBoundsError(
318                "decrypted",
319                decrypted.len(),
320            ))?
321            .to_vec();
322        let mut ck = decrypted
323            .get(16..32)
324            .ok_or(crate::Error::SliceOutOfBoundsError(
325                "decrypted",
326                decrypted.len(),
327            ))?
328            .to_vec();
329
330        if let Some(aux_key) = aux_key {
331            ci = decrypted.iter().copied().step_by(2).take(16).collect();
332            ck = decrypted
333                .iter()
334                .copied()
335                .skip(1)
336                .step_by(2)
337                .take(16)
338                .collect();
339
340            if encrypted_key.0 == CipherType::Ecc256ViaSymmetric {
341                let embedded_root_license =
342                    &encrypted_key
343                        .2
344                        .get(..144)
345                        .ok_or(crate::Error::SliceOutOfBoundsError(
346                            "encrypted_key",
347                            encrypted_key.2.len(),
348                        ))?;
349                let embedded_leaf_license =
350                    &encrypted_key
351                        .2
352                        .get(144..)
353                        .ok_or(crate::Error::SliceOutOfBoundsError(
354                            "encrypted_key",
355                            encrypted_key.2.len(),
356                        ))?;
357
358                let rgb_key = ck
359                    .iter()
360                    .zip(RGB_MAGIC_CONSTANT_ZERO)
361                    .map(|v| v.0 ^ v.1)
362                    .collect::<Vec<_>>();
363
364                let content_key_prime = aes::encrypt_ecb(&ck, &rgb_key)?;
365                let uplink_x_key = aes::encrypt_ecb(&content_key_prime, aux_key)?;
366
367                let secondary_key = aes::encrypt_ecb(
368                    &ck,
369                    embedded_root_license
370                        .get(128..)
371                        .ok_or(crate::Error::SliceOutOfBoundsError(
372                            "embedded_root_license",
373                            embedded_root_license.len(),
374                        ))?,
375                )?;
376
377                let embedded_leaf_license = aes::encrypt_ecb(&uplink_x_key, embedded_leaf_license)?;
378                let embedded_leaf_license =
379                    aes::encrypt_ecb(&secondary_key, &embedded_leaf_license)?;
380
381                ci = embedded_leaf_license
382                    .get(..16)
383                    .ok_or(crate::Error::SliceOutOfBoundsError(
384                        "embedded_leaf_license",
385                        embedded_leaf_license.len(),
386                    ))?
387                    .to_vec();
388                ck = embedded_leaf_license
389                    .get(16..)
390                    .ok_or(crate::Error::SliceOutOfBoundsError(
391                        "embedded_leaf_license",
392                        embedded_leaf_license.len(),
393                    ))?
394                    .to_vec();
395            }
396        }
397
398        Ok((
399            KeyId::from_uuid(encrypted_key.1),
400            ContentKey::from(ck.into_boxed_slice()),
401            ContentIntegrityKey::from(ci.into_boxed_slice()),
402        ))
403    }
404
405    fn cipher_data(&self) -> Result<Vec<u8>, crate::Error> {
406        let body_tag =
407            xml_utils::build_cipher_data(BASE64_STANDARD.encode(self.device.group_certificate()))?;
408
409        let body = xml_utils::render(&body_tag)?;
410        let ciphertext = aes::encrypt_cbc(self.xml_key.aes_key(), self.xml_key.aes_iv(), &body)?;
411
412        Ok([self.xml_key.aes_iv(), ciphertext.as_slice()].concat())
413    }
414
415    fn key_data(&self) -> Vec<u8> {
416        ecc_p256::encrypt(
417            ecc_p256::wmrm_public_key(),
418            self.xml_key.public_key().as_element(),
419        )
420    }
421
422    fn client_time() -> String {
423        SystemTime::now()
424            .duration_since(UNIX_EPOCH)
425            .unwrap()
426            .as_secs()
427            .to_string()
428    }
429}