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