Pywidevine Versions Save

Python implementation of Google's Widevine DRM CDM (Content Decryption Module)

v1.8.0

4 months ago

Added

  • Added py.typed file to support PEP561 and silence Mypy (#43).

Changed

  • Dropped support for Python 3.7.
  • Recompiled protobuffers for version 4.25.

Fixed

  • Missing yaml dependency as it was only installed alongside the serve extras group.
  • Duplicate Concatenated SignedMessages no longer throw a verification failure in Cdm.set_service_certificate(). To ensure security of the messages, verification will still fail if any of the SignedMessages do not match each other.

New Contributors

v1.7.0

5 months ago
  • Supported Serve API: v1.4.3 or newer

Added

  • Ability to specify output filename by specifying a full path or a relative file name in CLI command create-device.
  • Add the staging privacy certificate (staging.google.com) to Cdm.staging_privacy_cert.
    • Similar to common_privacy_cert which would be used on Google's production license server,
    • Though this one is used on Google's staging license server (a production-ready testing server).

Changed

  • Raise an error if a file already exists at the output path in CLI command create-device.
  • Use std-lib xml instead of lxml to reduce dependencies and support ARM (#35).
  • Lessen restriction on Python version to any Python version >=3.7, but <4.0.
    • I was hoping to do ^3.7, but some dependencies also require <4.0 therefore I cannot, for now.
  • Move Key ID parsing to static PSSH.parse_key_ids() method.
  • The shaka-packager subprocess call's return code is now returned from Cdm.decrypt().
  • The flags variable of a Device now defaults to a dict, even if not set.
  • Heavily improve initializing of protobuf objects, improving readability, typing, and linting quite a bit.
  • Renamed Device's _Types enum class to DeviceTypes.

Removed

  • Removed Device.Types class variable alias to _Types enum class as a static linter cannot recognize a class variable as a type. Instead, the actual _Types (now named DeviceTypes) enum should be imported and used instead.

Fixed

  • Ensure output directory exists before creating new .wvd files in CLI command create-device.
  • Ignore empty Key ID values in v4.0.0.0 PlayReadyHeaders.
  • Remove Cdm.system_id class variable as it conflicted with the cdm.system_id class instance variable of the same name. It's also generally not needed. The same data can be gotten via Cdm.uuid.bytes.
  • Casting of type_ when passed a non-int value in Cdm.get_license_challenge().
  • Pass a PSSH object in test CLI command instead of a string.
  • Lower-case and setup __all__ correctly, add missing __all__ in some of the modules.
    • For the longest time I thought it was __ALL__ and an iterable of objects/variables.
    • However, its actually __all__ and explicitly a list of Strings...

New Contributors

v1.6.0

1 year ago
  • Supported Serve API: v1.4.3 or newer

Added

  • Support Python 3.11.
  • New CLI command export-device to export WVD files back as files. I.e., a private key and client ID blob file.

v1.5.3

1 year ago
  • Supported Serve API: v1.4.3 or newer

Added

  • New utility load_xml() to parse XML data with lxml ignoring Namespaces.
  • PSSH class now have __str__ and __repr__ methods to print the object in more Human-friendly ways.
    • str(pssh) is now identical to pssh.dumps().
    • repr(pssh) or just pssh in some cases will result in a nice overview of the PSSHs contents.
  • New to_playready() method to convert Widevine PSSH Data to PlayReady PSSH Data. Please note that the Checksums for AES-CTR and COCKTAIL KIDs cannot be calculated as the Content Encryption Key would be needed.

Changed

  • The System ID must now be explicitly specified when creating a new PSSH box in PSSH.new().
    • This allows you to now create PlayReady PSSH boxes.
  • The playready_to_widevine() method has been renamed to just to_widevine().

Fixed

  • Correct capitalization of the key_IDs field when making the new box in PSSH.new().
  • Correct the value type of key_IDs value when creating a new box in PSSH.new().
  • Ensure Key IDs are list of UUIDs instead of bytes in PSSH.new().
  • Create v0 PSSH boxes by only setting the key_IDs field when the version is set to 1 in PSSH.new().
  • Fix loading of PlayReadyHeaders (and PlayReadyObjects) as PSSH boxes. It would previously load it under the Widevine SystemID breaking all PlayReady-specific code after construction.
  • Parse Key IDs within PlayReadyHeaders by using the new load_xml() utility to ignore namespaces so that xpath can correctly locate any and all KID tags.
  • Support parsing PlayReadyObjects with more than one PlayReadyHeader (more than one record).

v1.5.2

1 year ago
  • Supported Serve API: v1.4.3 or newer

Fixed

  • Fixed license signature calculation for newer Widevine Server licenses on OEM Crypto v16.0.0 or newer. The oemcrypto_core_message data needed to be part of the HMAC ingest if available.

v1.5.1

1 year ago
  • Supported Serve API: v1.4.3 or newer

Added

  • Support for big-int Key IDs in PSSH. All integer values are converted to a UUID and are loaded big-endian.
  • Import path shortcuts in the __init__.py package constructor to all the user classes.
    • Now you can do e.g., from pywidevine import PSSH instead of from pywidevine.pssh import PSSH.
    • You can still do it the full direct way if you want.
  • Parsing check to the raw DrmCertificate in Cdm.set_service_certificate().

Changed

  • Service Certificates are now stored in the session as a SignedDrmCertificate.
    • This is to keep the signature with the Certificate, without wrapping it in a SignedMessage unnecessarily.
  • Reduced the maximum concurrent Cdm sessions from 50 to 16 as it seems to be a more common limit on more up-to-date devices and versions of OEMCrypto. This also helps encourage people to close their sessions when they are no longer required.

Fixed

  • Acquisition of the Certificate's provider_id in Cdm.set_service_certificate() in some edge cases, but also when you try to remove the certificate by setting it to None.
  • When exporting a PSSH object it will now do so in the same version it was initially loaded or created in. Previously it would always dump as a v1 PSSH box due to a cascading check in pymp4. It now also honors the currently set version in the case it gets overridden.
  • Improved reliability of computing License Signatures by verifying the signature against the original raw License message instead of the re-serialized version of the message.
    • Some license messages when parsed would be slightly different when re-serialized against my protobuf, therefore the computed signature would have always mismatched.

v1.5.0

1 year ago
  • Supported Serve API: v1.4.3 or newer

Changed

  • Updated protobuf dependency to v4.x branch with recompiled proto-buffers, specifically v4.21.6.

v1.4.4

1 year ago
  • Supported Serve API: v1.4.3 or newer

Security

v1.4.3

1 year ago
  • Supported Serve API: v1.4.3 or newer

Added

  • Serve's /get_license_challenge endpoint can now disable privacy mode per-request, even if a service certificate is set, as long as privacy mode is not enforced in the Serve API config.
  • New Cdm method get_service_certificate() to get the currently set service certificate of a Session.

Changed

  • All f-string formatting in log statements have been replaced with logging formatting to save performance when that log wouldn't have been printed.
  • The Serve APIs /open endpoint's function has been renamed from open() to open_() to prevent shadowing the built-in open.

Security

v1.4.2

1 year ago
  • Supported Serve API: v1.4.0 to v1.4.2

Changed

  • Sessions in Cdm.open() are now initialized with a unique session number.
  • Android Cdm Devices now use a Request ID formula similar to OEMCrypto library when generating a Challenge. This formula has yet to be fully confirmed and ironed out, but it is closer than the Chrome Cdm formula.
  • Device no longer throws ValueError exceptions on DecodeErrors if it fails to parse the provided Client ID, or it's VMP data if any. It will now re-raise DecodeError.

Fixed

  • Parsed Proto Messages now go through an elaborate yet efficient verification, it must parse and serialize back to it's received form, byte-for-byte, or it will be rejected.
    • This prevents protobuf from parsing a message that could be a different message depending on the starting bytes.
    • It was possible to bypass some minor checks by providing specially crafted messages that parsed as other messages. However, I haven't noticed any way where this would lead to a vulnerability or anything bad. It mostly just lead to Serve API crashes or just rejected messages down the chain as they wouldn't have the right data within them.