diff --git a/Android.bp b/Android.bp new file mode 100644 index 0000000..64cbc9b --- /dev/null +++ b/Android.bp @@ -0,0 +1,41 @@ +// ----------------------------------------------------------------------------- +// CAS top level makefile +// + +subdirs = ["wvutil", "protos", "plugin"] + +// ----------------------------------------------------------------------------- +// Builds libwvmediacas.so +// Generates *.a, *.pb.h and *.pb.cc for *.proto files. +// +cc_library_shared { + + name: "libwvmediacas", + + proprietary: true, + relative_install_path: "mediacas", + + shared_libs: [ + "libcrypto", + "libcutils", + "liblog", + "libprotobuf-cpp-lite", + "libutils", + "libhidlbase", + ], + + header_libs: ["media_plugin_headers"], + + static_libs: [ + "//vendor/widevine/libwvmediacas/protos:libcas_protos", + "//vendor/widevine/libwvmediacas/wvutil:libcasutil", + ], + + whole_static_libs: [ + "//vendor/widevine/libwvmediacas/plugin:libwvcasplugins", + ], + + proto: { + type: "lite", + }, +} diff --git a/README b/README index e69de29..01214e0 100644 --- a/README +++ b/README @@ -0,0 +1 @@ +This repo contains Widevine MediaCas client code that works with Android U. diff --git a/oemcrypto/Android.bp b/oemcrypto/Android.bp new file mode 100644 index 0000000..7617298 --- /dev/null +++ b/oemcrypto/Android.bp @@ -0,0 +1,5 @@ +cc_library_headers { + name: "oemcastroheaders", + export_include_dirs: ["include"], + proprietary: true, +} diff --git a/oemcrypto/include/OEMCryptoCENC.h b/oemcrypto/include/OEMCryptoCENC.h new file mode 100644 index 0000000..7a10cfc --- /dev/null +++ b/oemcrypto/include/OEMCryptoCENC.h @@ -0,0 +1,5869 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine +// License Agreement. + +/** + * @mainpage OEMCrypto API v18.4 + * + * OEMCrypto is the low level library implemented by the OEM to provide key and + * content protection, usually in a separate secure memory or process space. The + * term *OEMCrypto* refers to both the API described here and the library + * implementing the API. + * + * For an overview of OEMCrypto functionality, please see + * [Widevine Modular DRM Security Integration Guide for Common + * Encryption](../index) + * + * The OEMCrypto API is divided into several sections. + * + * @defgroup initcontrol Initialization and Control API + * Initialization and set up OEMCrypto. + * + * @defgroup keyladder Crypto Key Ladder API + * The crypto key ladder is a mechanism for staging crypto keys for use by the + * hardware crypto engine. + * + * Keys are always encrypted for transmission. Before + * a key can be used, it must be decrypted (typically using the top key in the + * key ladder) and then added to the key ladder for upcoming decryption + * operations. The Crypto Key Ladder API requires the device to provide + * hardware support for AES-128 CTR and CBC modes and prevent clear keys from + * being exposed to the insecure OS. + * + * @defgroup decryption Decryption API + * Devices that implement the Key Ladder API must also support a secure decode + * or secure decode and rendering implementation. + * + * This can be done by either + * decrypting into buffers secured by hardware protections and providing these + * secured buffers to the decoder/renderer or by implementing decrypt operations + * in the decoder/renderer. + * + * In a Security Level 2 implementation where the video path is not protected, + * the audio and video streams are decrypted using OEMCrypto_DecryptCENC() and + * buffers are returned to the media player in the clear. + * + * Generic Modular DRM allows an application to encrypt, decrypt, sign and + * verify arbitrary user data using a content key. This content key is + * securely delivered from the server to the client device using the same + * factory installed root of trust as a media content keys. + * + * + * ![Generic Encrypt and Decrypt flow](fig2.svg) + * + * ![Generic Sign flow](fig3.svg) + * + * ![Generic Verify flow](fig4.svg) + * + * @defgroup factory_provision Factory Provisioning API + * Functions that are used to install the root of trust. This could be either a + * keybox or an OEM Certificate. + * + * Widevine keyboxes are used to establish a root of trust to secure content on + * a device that uses Provisioning 2.0. OEM Certificates are used to establish + * a root of trust to secure content on a device that uses Provisioning + * 3.0. Factory Provisioning a device is related to manufacturing methods. This + * section describes the API that installs the Widevine Keybox and the + * recommended methods for the OEM's factory provisioning procedure. + * + * Devices should support both a production keybox and a temporary test + * keybox. The production keybox is usually installed in the factory, or using + * OEMCrypto_WrapKeyboxOrOEMCert() and OEMCrypto_InstallKeyboxOrOEMCert(). The + * temporary keybox is a test keybox, and is loaded via + * OEMCrypto_LoadTestKeybox() by the unit tests. The test keybox will only be + * used temporarily while the unit tests are running, and will not be used by + * the general public. After the unit tests have been run, and + * OEMCrypto_Terminate() has been called, the production keybox should be active + * again. + * + * API functions marked as optional may be used by the OEM's factory + * provisioning procedure and implemented in the library, but are not called + * from the Widevine DRM Plugin during normal operation. + * + * @defgroup keybox Keybox and Provisioning 2.0 API + * Functions that are needed to for a device with a keybox. + * + * The OEMCrypto API allows for a device to be initially provisioned with a + * keybox or with an OEM certificate. See the section + * [Provisioning](../../index#provisioning) in the integration guide. In a + * Level 1 or Level 2 implementation, only the security processor may access the + * keys in the keybox. The following functions are for devices that are + * provisioned with a keybox, i.e. Provisioning 2.0. + * + * @defgroup oem_cert OEM Certificate and Provisioning 3.0 API + * Functions that are needed to for a device with an OEM Certificate. + * + * The OEMCrypto API allows for a device to be initially provisioned with a + * keybox or with an OEM certificate. See the section + * Provisioning in the integration guide. + * The functions in this section are for devices that are provisioned with an + * OEM Certificate, i.e. Provisioning 3.0. + * + * API functions marked as optional may be used by the OEM's factory + * provisioning procedure and implemented in the library, but are not called + * from the Widevine DRM Plugin during normal operation. + * + * @defgroup prov40 OEM Certificate and Provisioning 4.0 API + * Functions that are needed process a boot chain certificate. + * + * The OEMCrypto API allows for a device to be initially provisioned with a + * keybox or with an OEM certificate in the factory, or to use a boot chain + * derived by the device using a device specific key. + * See the section Provisioning + * in the integration guide. + * The functions in this section are for devices that are provisioned with a + * boot chain, i.e. Provisioning 4.0. + * + * @defgroup validation Validation and Feature Support API + * The OEMCrypto API is flexible enough to allow different devices to support + * different features. This section has functions that specify the level of + * support for various features. These values are reported to either the + * application or the license server. + * + * @defgroup drm_cert DRM Certificate Provisioning API + * This section of functions are used to provision the device with a DRM + * certificate. This certificate is obtained by a device in the field from a + * Google/Widevine provisioning server, or from a third party server running the + * Google/Widevine provisioning server SDK. Since the DRM certificate may be + * origin or application specific, a device may have several DRM certificates + * installed at a time. The DRM certificate is used to authenticate the device + * to a license server. In order to obtain a DRM certificate from a + * provisioning server, the device may authenticate itself using a keybox or + * using an OEM certificate. + * + * @defgroup usage_table Usage Table API + * The usage table is used to store license usage and allows a persistent + * license to be reloaded. + * + * @defgroup entitled Entitlement License API + * Functions that are needed for entitled and entitlement licenses. + * + * [Entitlement licensing](../../index#entitlement) is a way to provide access + * to content keys that may be stored elsewhere, such as in the content itself. + * This can be used to implement content key rotation without requiring new + * licenses, or access to multiple pieces of content with a single license. + * + * @defgroup test_verify Test and Verification API + * Functions that are designed to help test OEMCrypto and the device. They are + * not used during normal operation. Some functions, like those that test the + * full decrypt data path may be supported on a production device with no added + * risk of security loss. + * + * The following functions are used just for testing and verification of + * OEMCrypto and the CDM code. + * + * @defgroup common_types Common Types + * Enumerations and structures that are used by several OEMCrypto and ODK + * functions. + */ + +#ifndef OEMCRYPTO_CENC_H_ +#define OEMCRYPTO_CENC_H_ + +#include +#include +#include + +#include "OEMCryptoCENCCommon.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/// @addtogroup keyladder +/// @{ + +/// This is the internal session identifier. +typedef uint32_t OEMCrypto_SESSION; + +/// @} + +/// @addtogroup decryption +/// @{ +/** + * The memory referenced by OEMCrypto_SharedMemory* is safe to be placed in + * shared memory. The only data that should be placed into shared + * memory is the contents of input/output buffers, i.e. data that will + * not introduce security vulnerabilities if it is subject to + * modification while being accessed. + */ +typedef uint8_t OEMCrypto_SharedMemory; + +/** Specifies destination buffer type. + */ +typedef enum OEMCryptoBufferType { + OEMCrypto_BufferType_Clear, + OEMCrypto_BufferType_Secure, + OEMCrypto_BufferType_Direct, + OEMCrypto_BufferType_MaxValue = OEMCrypto_BufferType_Direct, +} OEMCryptoBufferType; + +/** + * This structure is used as parameters in the OEMCrypto_DecryptCENC() and + * OEMCrypto_CopyBuffer() functions. This describes the type and access + * information for the memory to receive decrypted data. + * + * The OEMCrypto API supports a range of client device architectures. Different + * architectures have different methods for acquiring and securing buffers that + * will hold portions of the audio or video stream after decryption. Three basic + * strategies are recognized for handling decrypted stream data: + * + * 1. Return the decrypted data in the clear into normal user memory + * (ClearBuffer). The caller uses normal memory allocation methods to + * acquire a buffer, and supplies the memory address of the buffer in + * the descriptor. + * 2. Place the decrypted data into protected memory (SecureBuffer). The + * caller uses a platform-specific method to acquire the protected + * buffer and a user-memory handle that references it. The handle is + * supplied to the decrypt call in the descriptor. If the buffer is + * filled with several OEMCrypto calls, the same handle will be used, + * and the offset will be incremented to indicate where the next write + * should take place. + * 3. Place the decrypted data directly into the audio or video decoder + * fifo (Direct). The caller will use platform-specific methods to + * initialize the fifo and the decoders. The decrypted stream data is + * not accessible to the caller. This is used on some platforms only. + * + * @param[in] type: A tag that indicates which variant of the union is valid for + * this instance of the structure. [variant] clear: This variant is valid + * when the type is OEMCrypto_BufferType_Clear. This OEMCrypto_DestBufferDesc + * indicates output should be written to a clear buffer. + * @param[in] clear_buffer: A pointer to the address in memory to begin writing + * output. + * @param[in] clear_buffer_length: The length of the buffer that is available to + * contain output. [variant] secure: This variant is valid when the type is + * OEMCrypto_BufferType_Secure. This OEMCrypto_DestBufferDesc indicates + * output should be written to a secure buffer. The decrypted output must + * never leave the secure area until it is output from the device. + * @param[in] secure_buffer: An opaque handle to a secure buffer. The meaning of + * this handle is platform-specific. + * @param[in] secure_buffer_length: The length of the data contained in the + * secure buffer. + * @param[in] offset: An offset indicating where in the secure buffer to start + * writing data. [variant] direct: This variant is valid when the type is + * OEMCrypto_BufferType_Direct. This OEMCrypto_DestBufferDesc indicates + * output should be written directly to the decoder. + * @param[in] is_video: A flag indicating if the data is video and should be + * sent to the video decoder. If this is false, the data can be assumed to be + * audio and sent to the audio decoder. + * + * @version + * This struct changed in API version 16. + */ +typedef struct { + OEMCryptoBufferType type; + union { + struct { // type == OEMCrypto_BufferType_Clear + OEMCrypto_SharedMemory* clear_buffer; + size_t clear_buffer_length; + } clear; + struct { // type == OEMCrypto_BufferType_Secure + void* secure_buffer; + size_t secure_buffer_length; + size_t offset; + } secure; + struct { // type == OEMCrypto_BufferType_Direct + bool is_video; + } direct; + } buffer; +} OEMCrypto_DestBufferDesc; + +/** + * This structure is used as parameters in the OEMCrypto_DecryptCENC() function. + * + * @param[in] input_data: An unaligned pointer to this sample from the stream. + * @param[in] input_data_length: The length of this sample in the stream, in + * bytes. + * @param[in] output_descriptor: A caller-owned descriptor that specifies the + * handling of the decrypted byte stream. See OEMCrypto_DestbufferDesc for + * details. + * + * @version + * This struct changed in API version 16. + */ +typedef struct { + const OEMCrypto_SharedMemory* input_data; // source for encrypted data. + size_t input_data_length; // length of encrypted data. + OEMCrypto_DestBufferDesc output_descriptor; // destination for clear data. +} OEMCrypto_InputOutputPair; + +/** + * This structure is used as parameters in the OEMCrypto_DecryptCENC() + * function. In the DASH specification, a sample is composed of multiple + * samples, and each subsample is composed of two regions. The first region is + * clear unprotected data. We also call this clear data or unencrypted + * data. Immediately following the clear region is the protected region. The + * protected region is encrypted or encrypted with a pattern. The pattern and + * number of bytes that are encrypted in the protected region is discussed in + * this document when we talk about the function OEMCryptoDecryptCENC. For + * historic reasons, this document also calls the protected region the encrypted + * region. + * + * @param[in] num_bytes_clear: The number of unprotected bytes in this + * subsample. The clear bytes come before the encrypted bytes. + * @param[in] num_bytes_encrypted: The number of protected bytes in this + * subsample. The protected bytes come after the clear bytes. + * @param[in] subsample_flags: bitwise flags indicating if this is the first, + * middle, or last subsample in a sample. 1 = first subsample, 2 = last + * subsample, 3 = both first and last subsample, 0 = neither first nor last + * subsample. + * @param[in] block_offset: This will only be non-zero for the 'cenc' scheme. + * If it is non-zero, the decryption block boundary is different from the + * start of the data. block_offset should be subtracted from data to compute + * the starting address of the first decrypted block. The bytes between the + * decryption block start address and data are discarded after decryption. It + * does not adjust the beginning of the source or destination data. This + * parameter satisfies 0 <= block_offset < 16. + * + * @version + * This struct changed in API version 16. + */ +typedef struct { + size_t num_bytes_clear; + size_t num_bytes_encrypted; + uint8_t subsample_flags; // is this the first/last subsample in a sample? + size_t block_offset; // used for CTR "cenc" mode only. +} OEMCrypto_SubSampleDescription; + +#define OEMCrypto_FirstSubsample 1 +#define OEMCrypto_LastSubsample 2 + +/** + * This structure is used as parameters in the OEMCrypto_DecryptCENC() function. + * + * @param[in] buffers: A structure containing information about the input and + * output buffers. + * @param[in] iv: A 16-byte array containing the IV for the initial subsample of + * the sample. + * @param[in] subsamples: A caller-owned array of OEMCrypto_SubSampleDescription + * structures. Each entry in this array describes one subsample in the + * sample. + * @param[in] subsamples_length: The length of the array pointed to by the + * subsamples parameter. + * + * @version + * This struct changed in API version 16. + */ +typedef struct { + OEMCrypto_InputOutputPair buffers; // The source and destination buffers. + uint8_t iv[16]; // The IV for the initial subsample. + const OEMCrypto_SubSampleDescription* subsamples; // subsamples array. + size_t subsamples_length; // the number of subsamples in the sample. +} OEMCrypto_SampleDescription; + +/** + * This structure is used as parameters in the OEMCrypto_DecryptCENC() function. + * + * Fields: + * @param[in] encrypt: The number of 16-byte crypto blocks to encrypt. + * @param[in] skip: The number of 16-byte crypto blocks to leave in the clear. + * + * @version + * This struct changed in API version 16. + */ +typedef struct { + size_t encrypt; // number of 16 byte blocks to decrypt. + size_t skip; // number of 16 byte blocks to leave in clear. +} OEMCrypto_CENCEncryptPatternDesc; + +/** + * OEMCryptoCipherMode is used in OEMCrypto_GetKeyHandle() to prepare a key for + * decryption. + */ +typedef enum OEMCryptoCipherMode { + // explicit cipher modes used for modular DRM + OEMCrypto_CipherMode_CENC, + OEMCrypto_CipherMode_CBCS, + // cipher modes used for CAS + OEMCrypto_CipherMode_CTR, + OEMCrypto_CipherMode_CBC, + OEMCrypto_CipherMode_CSA2, + OEMCrypto_CipherMode_CSA3, + OEMCrypto_CipherMode_OFB, + OEMCrypto_CipherMode_SCTE, + OEMCrypto_CipherMode_ECB, + OEMCrypto_CipherMode_MaxValue = OEMCrypto_CipherMode_ECB, +} OEMCryptoCipherMode; + +/** + * This is a list of valid algorithms for OEMCrypto_Generic_* functions. + * Some are valid for encryption/decryption, and some for signing/verifying. + */ +typedef enum OEMCrypto_Algorithm { + OEMCrypto_AES_CBC_128_NO_PADDING = 0, + OEMCrypto_HMAC_SHA256 = 1, + OEMCrypto_Algorithm_MaxValue = 1, +} OEMCrypto_Algorithm; + +/// @} + +/// @addtogroup entitled +/// @{ +/** + * Contains encrypted content key data for loading into the sessions keytable. + * The content key data is encrypted using AES-256-CBC encryption, with PKCS#7 + * padding. + + * @param entitlement_key_id: entitlement key id to be matched to key table. + * @param content_key_id: content key id to be loaded into key table. + * @param key_data_iv: the IV for performing AES-256-CBC decryption of the key + * data. + * @param key_data: encrypted content key data. + * @param content_iv: the IV for decrypting media content. Used by CAS only. + * @param cipher_mode: the encryption mode of the media content. Used by CAS + * only. + */ +typedef struct { + OEMCrypto_Substring entitlement_key_id; + OEMCrypto_Substring content_key_id; + OEMCrypto_Substring content_key_data_iv; + OEMCrypto_Substring content_key_data; + OEMCrypto_Substring content_iv; + OEMCryptoCipherMode cipher_mode; +} OEMCrypto_EntitledContentKeyObject; + +/// @} + +/// @addtogroup usage_table +/// @{ + +#if 0 // If your compiler supports __attribute__((packed)). +/** + * OEMCrypto_PST_Report is used to report an entry from the Usage Table. + * + * Platforms that have compilers that support packed structures, may use the + * following definition. Other platforms may use the header pst_report.h which + * defines a wrapper class. + * + * All fields are in network byte order. + */ +typedef struct { + uint8_t signature[20]; // -- HMAC SHA1 of the rest of the report. + uint8_t status; // current status of entry. (OEMCrypto_Usage_Entry_Status) + uint8_t clock_security_level; + uint8_t pst_length; + uint8_t padding; // make int64's word aligned. + int64_t seconds_since_license_received; // now - time_of_license_received + int64_t seconds_since_first_decrypt; // now - time_of_first_decrypt + int64_t seconds_since_last_decrypt; // now - time_of_last_decrypt + uint8_t pst[]; +} __attribute__((packed)) OEMCrypto_PST_Report; +#endif + +/** + * Valid values for clock_security_level in OEMCrypto_PST_Report. + */ +typedef enum OEMCrypto_Clock_Security_Level { + kInsecureClock = 0, + kMonotonicClock = 1, + kSecureTimer = 1, // DEPRECATED. Do not use. + kSecureClock = 2, + kHardwareSecureClock = 3 +} OEMCrypto_Clock_Security_Level; + +typedef uint8_t RSA_Padding_Scheme; +// RSASSA-PSS with SHA1. +#define kSign_RSASSA_PSS ((RSA_Padding_Scheme)0x1) +// PKCS1 with block type 1 padding (only). +#define kSign_PKCS1_Block1 ((RSA_Padding_Scheme)0x2) + +/// @} + +/// @addtogroup validation +/// @{ +/** + * OEMCrypto_HDCP_Capability is used in the key control block to enforce HDCP + * level, and in GetHDCPCapability for reporting. + */ +typedef enum OEMCrypto_HDCP_Capability { + HDCP_NONE = 0, // No HDCP supported, no secure data path. + HDCP_V1 = 1, // HDCP version 1.x + HDCP_V2 = 2, // HDCP version 2.0 Type 1. + HDCP_V2_1 = 3, // HDCP version 2.1 Type 1. + HDCP_V2_2 = 4, // HDCP version 2.2 Type 1. + HDCP_V2_3 = 5, // HDCP version 2.3 Type 1. + // For backwards compatibility, these values are added after the V2 fields. + // However, it is optional for devices and they can still report HDCP_V1. + HDCP_V1_0 = 6, + HDCP_V1_1 = 7, + HDCP_V1_2 = 8, + HDCP_V1_3 = 9, + HDCP_V1_4 = 10, + HDCP_NO_DIGITAL_OUTPUT = 0xff // No digital output. +} OEMCrypto_HDCP_Capability; + +/** + * OEMCrypto_DTCP2_Capability is used in OEMCrypto_GetDTCP2Capability + * for reporting the level of DTCP2 support for a device. + */ +typedef enum OEMCrypto_DTCP2_Capability { + OEMCrypto_NO_DTCP2 = 0, // DTCP2 is not supported. + OEMCrypto_DTCP2_V1 = 1, // At least v1 of DTCP2 is supported. +} OEMCrypto_DTCP2_Capability; + +/** + Return value for OEMCrypto_GetProvisioningMethod(). + */ +typedef enum OEMCrypto_ProvisioningMethod { + OEMCrypto_ProvisioningError = 0, // Device cannot be provisioned. + // Device has baked in DRM certificate (level 3 only). + OEMCrypto_DrmCertificate = 1, + // Device has factory installed unique keybox. + OEMCrypto_Keybox = 2, + // Device has factory installed OEM certificate. + OEMCrypto_OEMCertificate = 3, + // Device has Boot Certificate Chain (BCC). + OEMCrypto_BootCertificateChain = 4, + // Device has baked in DRM certificate with reprovisioning (level 3 only). + OEMCrypto_DrmReprovisioning = 5 +} OEMCrypto_ProvisioningMethod; + +/** + Return value for OEMCrypto_GetWatermarkingSupport(). + */ +typedef enum OEMCrypto_WatermarkingSupport { + OEMCrypto_WatermarkingError = 0, + OEMCrypto_WatermarkingNotSupported = 1, + OEMCrypto_WatermarkingConfigurable = 2, + OEMCrypto_WatermarkingAlwaysOn = 3, +} OEMCrypto_WatermarkingSupport; + +/** + Return value for OEMCrypto_GetSignatureHashAlgorithm(). + */ +typedef enum OEMCrypto_SignatureHashAlgorithm { + OEMCrypto_SHA1 = 0, + OEMCrypto_SHA2_256 = 1, + OEMCrypto_SHA2_384 = 2, + OEMCrypto_SHA2_512 = 3, +} OEMCrypto_SignatureHashAlgorithm; + +/** + * Flags indicating public/private key types supported. + */ +#define OEMCrypto_Supports_RSA_2048bit 0x1 +#define OEMCrypto_Supports_RSA_3072bit 0x2 +#define OEMCrypto_Supports_RSA_CAST 0x10 +#define OEMCrypto_Supports_ECC_secp256r1 0x100 +#define OEMCrypto_Supports_ECC_secp384r1 0x200 +#define OEMCrypto_Supports_ECC_secp521r1 0x400 + +/** + * Flags indicating full decrypt path hash supported. + */ +#define OEMCrypto_Hash_Not_Supported 0 +#define OEMCrypto_CRC_Clear_Buffer 1 +#define OEMCrypto_Partner_Defined_Hash 2 + +/** + * Return values from OEMCrypto_GetAnalogOutputFlags. + */ +#define OEMCrypto_No_Analog_Output 0x0 +#define OEMCrypto_Supports_Analog_Output 0x1 +#define OEMCrypto_Can_Disable_Analog_Ouptput 0x2 +#define OEMCrypto_Supports_CGMS_A 0x4 +// Unknown_Analog_Output is used only for backwards compatibility. +#define OEMCrypto_Unknown_Analog_Output (1 << 31) + +/// @} + +/** + * Obfuscation Renames. + * + * The function signatures of each oecc obfuscated name should remain static + * across multiple versions. When we want to change the function signature of a + * function, we will give the new signature a new oecc number and keep the + * original oecc name with the original function signature. This allows us to + * maintain backwards compatibility when the CDM loads an older version of + * liboemcrypto.so using dlopen. + */ +// clang-format off +#define OEMCrypto_Initialize _oecc01 +#define OEMCrypto_Terminate _oecc02 +#define OEMCrypto_InstallKeybox _oecc03 +// Rename InstallKeybox to InstallKeyboxOrOEMCert. +#define OEMCrypto_InstallRootKeyCertificate _oecc03 +#define OEMCrypto_InstallKeyboxOrOEMCert _oecc03 +#define OEMCrypto_GetKeyData _oecc04 +#define OEMCrypto_IsKeyboxValid _oecc05 +// Rename IsKeyboxValid to IsKeyboxOrOEMCertValid. +#define OEMCrypto_IsRootKeyCertificateValid _oecc05 +#define OEMCrypto_IsKeyboxOrOEMCertValid _oecc05 +#define OEMCrypto_GetRandom _oecc06 +#define OEMCrypto_GetDeviceID _oecc07 +#define OEMCrypto_WrapKeybox _oecc08 +// Rename WrapKeybox to WrapKeyboxOrOEMCert +#define OEMCrypto_WrapRootKeyCertificate _oecc08 +#define OEMCrypto_WrapKeyboxOrOEMCert _oecc08 +#define OEMCrypto_OpenSession _oecc09 +#define OEMCrypto_CloseSession _oecc10 +#define OEMCrypto_DecryptCTR_V10 _oecc11 +#define OEMCrypto_GenerateDerivedKeys_V15 _oecc12 +#define OEMCrypto_GenerateSignature _oecc13 +#define OEMCrypto_GenerateNonce _oecc14 +#define OEMCrypto_LoadKeys_V8 _oecc15 +#define OEMCrypto_RefreshKeys_V14 _oecc16 +#define OEMCrypto_SelectKey_V13 _oecc17 +#define OEMCrypto_RewrapDeviceRSAKey _oecc18 +#define OEMCrypto_LoadDeviceRSAKey _oecc19 +#define OEMCrypto_GenerateRSASignature_V8 _oecc20 +#define OEMCrypto_DeriveKeysFromSessionKey _oecc21 +#define OEMCrypto_APIVersion _oecc22 +#define OEMCrypto_SecurityLevel_V16 _oecc23 +#define OEMCrypto_Generic_Encrypt_V17 _oecc24 +#define OEMCrypto_Generic_Decrypt_V17 _oecc25 +#define OEMCrypto_Generic_Sign_V17 _oecc26 +#define OEMCrypto_Generic_Verify_V17 _oecc27 +#define OEMCrypto_GetHDCPCapability_V9 _oecc28 +#define OEMCrypto_SupportsUsageTable _oecc29 +#define OEMCrypto_UpdateUsageTable _oecc30 +#define OEMCrypto_DeactivateUsageEntry_V12 _oecc31 +#define OEMCrypto_ReportUsage _oecc32 +#define OEMCrypto_DeleteUsageEntry _oecc33 +#define OEMCrypto_DeleteOldUsageTable _oecc34 +#define OEMCrypto_LoadKeys_V9_or_V10 _oecc35 +#define OEMCrypto_GenerateRSASignature _oecc36 +#define OEMCrypto_GetMaxNumberOfSessions _oecc37 +#define OEMCrypto_GetNumberOfOpenSessions _oecc38 +#define OEMCrypto_IsAntiRollbackHwPresent _oecc39 +#define OEMCrypto_CopyBuffer_V14 _oecc40 +#define OEMCrypto_QueryKeyControl _oecc41 +#define OEMCrypto_LoadTestKeybox_V13 _oecc42 +#define OEMCrypto_ForceDeleteUsageEntry _oecc43 +#define OEMCrypto_GetHDCPCapability _oecc44 +#define OEMCrypto_LoadTestRSAKey _oecc45 +#define OEMCrypto_Security_Patch_Level _oecc46 +#define OEMCrypto_LoadKeys_V11_or_V12 _oecc47 +#define OEMCrypto_DecryptCENC_V15 _oecc48 +#define OEMCrypto_GetProvisioningMethod _oecc49 +#define OEMCrypto_GetOEMPublicCertificate_V15 _oecc50 +#define OEMCrypto_RewrapDeviceRSAKey30 _oecc51 +#define OEMCrypto_SupportedCertificates _oecc52 +#define OEMCrypto_IsSRMUpdateSupported _oecc53 +#define OEMCrypto_GetCurrentSRMVersion _oecc54 +#define OEMCrypto_LoadSRM _oecc55 +#define OEMCrypto_LoadKeys_V13 _oecc56 +#define OEMCrypto_RemoveSRM _oecc57 +#define OEMCrypto_CreateUsageTableHeader _oecc61 +#define OEMCrypto_LoadUsageTableHeader _oecc62 +#define OEMCrypto_CreateNewUsageEntry _oecc63 +#define OEMCrypto_LoadUsageEntry _oecc64 +#define OEMCrypto_UpdateUsageEntry _oecc65 +#define OEMCrypto_DeactivateUsageEntry _oecc66 +#define OEMCrypto_ShrinkUsageTableHeader _oecc67 +#define OEMCrypto_MoveEntry _oecc68 +#define OEMCrypto_CopyOldUsageEntry _oecc69 +#define OEMCrypto_CreateOldUsageEntry _oecc70 +#define OEMCrypto_GetAnalogOutputFlags _oecc71 +#define OEMCrypto_LoadTestKeybox _oecc78 +#define OEMCrypto_LoadEntitledContentKeys_V14 _oecc79 +#define OEMCrypto_SelectKey _oecc81 +#define OEMCrypto_LoadKeys_V14 _oecc82 +#define OEMCrypto_LoadKeys _oecc83 +#define OEMCrypto_SetSandbox _oecc84 +#define OEMCrypto_ResourceRatingTier _oecc85 +#define OEMCrypto_SupportsDecryptHash _oecc86 +#define OEMCrypto_InitializeDecryptHash _oecc87 +#define OEMCrypto_SetDecryptHash _oecc88 +#define OEMCrypto_GetHashErrorCode _oecc89 +#define OEMCrypto_BuildInformation_V16 _oecc90 +#define OEMCrypto_RefreshKeys _oecc91 +#define OEMCrypto_LoadEntitledContentKeys_V16 _oecc92 +#define OEMCrypto_CopyBuffer _oecc93 +#define OEMCrypto_MaximumUsageTableHeaderSize _oecc94 +#define OEMCrypto_GenerateDerivedKeys _oecc95 +#define OEMCrypto_PrepAndSignLicenseRequest _oecc96 +#define OEMCrypto_PrepAndSignRenewalRequest _oecc97 +#define OEMCrypto_PrepAndSignProvisioningRequest _oecc98 +#define OEMCrypto_LoadLicense _oecc99 +#define OEMCrypto_LoadRenewal _oecc101 +#define OEMCrypto_LoadProvisioning _oecc102 +#define OEMCrypto_LoadOEMPrivateKey _oecc103 +#define OEMCrypto_GetOEMPublicCertificate _oecc104 +#define OEMCrypto_DecryptCENC_V17 _oecc105 +#define OEMCrypto_LoadDRMPrivateKey _oecc107 +#define OEMCrypto_MinorAPIVersion _oecc108 +#define OEMCrypto_AllocateSecureBuffer _oecc109 +#define OEMCrypto_FreeSecureBuffer _oecc110 +#define OEMCrypto_CreateEntitledKeySession _oecc111 +#define OEMCrypto_RemoveEntitledKeySession _oecc112 +#define OEMCrypto_GenerateOTARequest _oecc113 +#define OEMCrypto_ProcessOTAKeybox _oecc114 +#define OEMCrypto_OPK_SerializationVersion _oecc115 +#define OEMCrypto_GetBootCertificateChain _oecc116 +#define OEMCrypto_GenerateCertificateKeyPair _oecc117 +#define OEMCrypto_InstallOemPrivateKey _oecc118 +#define OEMCrypto_ReassociateEntitledKeySession _oecc119 +#define OEMCrypto_LoadCasECMKeys _oecc120 +#define OEMCrypto_LoadEntitledContentKeys _oecc121 +#define OEMCrypto_ProductionReady _oecc122 +#define OEMCrypto_Idle _oecc123 +#define OEMCrypto_Wake _oecc124 +#define OEMCrypto_BuildInformation _oecc125 +#define OEMCrypto_SecurityLevel _oecc126 +#define OEMCrypto_ReuseUsageEntry _oecc127 +#define OEMCrypto_GetDTCP2Capability _oecc128 +#define OEMCrypto_GetWatermarkingSupport _oecc129 +#define OEMCrypto_GetOEMKeyToken _oecc130 +#define OEMCrypto_GetDeviceInformation _oecc131 +#define OEMCrypto_SetMaxAPIVersion _oecc132 +#define OEMCrypto_GetKeyHandle _oecc133 +#define OEMCrypto_DecryptCENC _oecc134 +#define OEMCrypto_Generic_Encrypt _oecc135 +#define OEMCrypto_Generic_Decrypt _oecc136 +#define OEMCrypto_Generic_Sign _oecc137 +#define OEMCrypto_Generic_Verify _oecc138 +#define OEMCrypto_GetSignatureHashAlgorithm _oecc139 +#define OEMCrypto_EnterTestMode _oecc140 +#define OEMCrypto_GetDeviceSignedCsrPayload _oecc141 +#define OEMCrypto_FactoryInstallBCCSignature _oecc142 +#define OEMCrypto_GetEmbeddedDrmCertificate _oecc143 +// clang-format on + +/// @addtogroup initcontrol +/// @{ +/** Specifies whether system is in idle mode. + */ +typedef enum OEMCrypto_IdleState { + OEMCrypto_NoCryptoActivity = 0, // The system is not idle, but OEMCrypto is. + OEMCrypto_CpuSuspend = 1, +} OEMCrypto_IdleState; + +/** + * This tells OEMCrypto which sandbox the current process belongs to. Any + * persistent memory used to store the generation number should be associated + * with this sandbox id. OEMCrypto can assume that this sandbox will be tied + * to the current process or VM until OEMCrypto_Terminate() is called. See the + * section [VM and Sandbox Support](../../index#sandbox) for more details. + * + * If OEMCrypto does not support sandboxes, it will return + * OEMCrypto_ERROR_NOT_IMPLEMENTED. On most platforms, this function will + * just return OEMCrypto_ERROR_NOT_IMPLEMENTED. If OEMCrypto supports + * sandboxes, this function returns OEMCrypto_SUCCESS on success, and + * OEMCrypto_ERROR_UNKNOWN_FAILURE on failure. + * + * The CDM layer will call OEMCrypto_SetSandbox() once before + * OEMCrypto_Initialize(). After this function is called and returns success, + * it will be OEMCrypto's responsibility to keep calls to usage table + * functions separate, and to accept a call to OEMCrypto_Terminate() for each + * sandbox. + * + * @param[in] sandbox_id: a short string unique to the current sandbox. + * @param[in] sandbox_id_length: length of sandbox_id. + * + * @retval OEMCrypto_SUCCESS success + * @retval OEMCrypto_ERROR_INIT_FAILED failed to initialize crypto hardware + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED sandbox functionality not supported + * + * @threading + * This is an "Initialization and Termination Function" and will not be + * called simultaneously with any other function, as if the CDM holds a write + * lock on the OEMCrypto system. It is called once before + * OEMCrypto_Initialize(). + * + * @version + * This method is new in version 15 of the API. + */ +OEMCryptoResult OEMCrypto_SetSandbox(const uint8_t* sandbox_id, + size_t sandbox_id_length); + +/** + * Initialize the crypto firmware/hardware. + * + * @retval OEMCrypto_SUCCESS success + * @retval OEMCrypto_ERROR_INIT_FAILED failed to initialize crypto hardware + * + * @threading + * This is an "Initialization and Termination Function" and will not be + * called simultaneously with any other function, as if the CDM holds a write + * lock on the OEMCrypto system. + * + * @version + * This method is supported by all API versions. + */ +OEMCryptoResult OEMCrypto_Initialize(void); + +/** + * Specify the maximum OEMCrypto API version supported by the CDM layer above + * OEMCrypto. If OEMCrypto can support multiple versions then it must restrict + * itself to this version number. If OEMCrypto only supports one version, then + * it may ignore this function and return + * ERROR_NOT_IMPLEMENTED. OEMCrypto_SetMaxAPIVersion will be called after + * OEMCrypto_Initialize() and before any other functions. + * + * @param[in] max_version: the maximum version of OEMCrypto supported by CDM + * + * @retval OEMCrypto_SUCCESS success + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED function not implemented + * @retval other any other error + * + * @threading + * This is an "Initialization and Termination Function" and will not be + * called simultaneously with any other function, as if the CDM holds a write + * lock on the OEMCrypto system. + * + * @version + * This method is new in API version 18. + */ +OEMCryptoResult OEMCrypto_SetMaxAPIVersion(uint32_t max_version); + +/** + * Closes the crypto operation and releases all related resources. + * + * @retval OEMCrypto_SUCCESS success + * @retval OEMCrypto_ERROR_TERMINATE_FAILED failed to de-initialize crypto + * hardware + * + * @threading + * This is an "Initialization and Termination Function" and will not be + * called simultaneously with any other function, as if the CDM holds a write + * lock on the OEMCrypto system. No other functions will be called before the + * system is re-initialized. + * + * @version + * This method is supported by all API versions. + */ +OEMCryptoResult OEMCrypto_Terminate(void); + +/** + * If possible, OEMCrypto may reduce power consumption or other resources. For + * example, it may be possible to reduce the CPU clock rates. When the system + * is in idle mode, then the CDM will not call OEMCrypto_GetHDCPCapability. + * + * This function is not required -- OEMCrypto may ignore this function. It is + * only used to improve performance. This function may return + * OEMCrypto_ERROR_NOT_IMPLEMENTED to indicate it is not supported. + * + * OEMCrypto_Idle may be called multiple times with no call to OEMCrypto_Wake + * in between. A call to OEMCrypto_Idle with different values of + * OEMCrypto_IdleState or os_specific_code may happen in any order. It is + * OEMCrypto’s responsibility to choose the appropriate behavior for improving + * power consumption. In particular, OEMCrypto may be notified of the + * OEMCrypto_NoCryptoActivity state before or after a notification of the + * OEMCrypto_CpuSuspend state, or OEMCrypto_NoCryptoActivity may not be notified + * at all. On some platforms, a call with OEMCrypto_CpuSuspend may never + * happen. + * + * The acceptable values of `os_specific_code` must be coordinated between the + * OS and the OEMCrypto vendor. On some platforms, for example Android, only + * the value of 0 will be used. For other platforms, it is the responsibility + * of the OEM device maker to coordinate with the SOC to define acceptable + * values of `os_specific_code`. + * + * @param[in] state: The idle state. This indicates if the call came from + * the OS or the CDM. + * @param[in] os_specific_code: Specified by the platform or OS for extra + * sleep information. + * + * @retval OEMCrypto_SUCCESS success + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED + * + * @threading + * This is an "Initialization and Termination Function" and will not be + * called simultaneously with any other function, as if the CDM holds a write + * lock on the OEMCrypto system. No other functions will be called before the + * system is re-initialized. + * + * @version + * This method is supported by all API versions. + */ +OEMCryptoResult OEMCrypto_Idle(OEMCrypto_IdleState state, + uint32_t os_specific_code); + +/** + * The new function OEMCrypto_Wake will be called to indicate that crypto + * operations will resume. A call to OEMCrypto_Wake after the system is already + * awake shall have no effect. If OEMCrypto cannot recover from being idle, it + * may return OEMCrypto_ERROR_SESSION_LOST_STATE or + * OEMCrypto_ERROR_SYSTEM_INVALIDATED. + * + * The CDM layer may postpone a call to OEMCrypto_Wake until Widevine activity + * is starting. This may happen long after the CPU wakes up. + * + * @retval OEMCrypto_SUCCESS success + * @retval OEMCrypto_ERROR_SESSION_LOST_STATE + * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED + * + * @threading + * This is an "Initialization and Termination Function" and will not be + * called simultaneously with any other function, as if the CDM holds a write + * lock on the OEMCrypto system. No other functions will be called before the + * system is re-initialized. + * + * @version + * This method is supported by all API versions. + */ +OEMCryptoResult OEMCrypto_Wake(void); + +/// @} + +/// @addtogroup keyladder +/// @{ +/** + * Open a new crypto security engine context. The security engine hardware + * and firmware shall acquire resources that are needed to support the + * session, and return a session handle that identifies that session in + * future calls. + * + * This function shall call ODK_InitializeSessionValues to initialize the + * session's clock values, timer values, and nonce values. + * ODK_InitializeSessionValues is described in the document "License Duration + * and Renewal", to initialize the session's clock values. + * + * @param[out] session: an opaque handle that the crypto firmware uses to + * identify the session. + * + * @retval OEMCrypto_SUCCESS success + * @retval OEMCrypto_ERROR_TOO_MANY_SESSIONS failed because too many sessions + * are open + * @retval OEMCrypto_ERROR_OPEN_SESSION_FAILED there is a resource issue or the + * security engine is not properly initialized. + * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED + * + * @threading + * This is a "Session Initialization Function" and will not be called + * simultaneously with any other function, as if the CDM holds a write lock + * on the OEMCrypto system. + * + * @version + * This method changed in API version 16. + */ +OEMCryptoResult OEMCrypto_OpenSession(OEMCrypto_SESSION* session); + +/** + * Closes the crypto security engine session and frees any associated + * resources. If this session is associated with a Usage Entry, all resident + * memory associated with it will be freed. It is the CDM layer's + * responsibility to call OEMCrypto_UpdateUsageEntry() before closing the + * session. + * + * @param[in] session: handle for the session to be closed. + * + * @retval OEMCrypto_SUCCESS success + * @retval OEMCrypto_ERROR_INVALID_SESSION no open session with that id. + * @retval OEMCrypto_ERROR_CLOSE_SESSION_FAILED illegal/unrecognized handle or + * the security engine is not properly initialized. + * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED + * + * @threading + * This is a "Session Initialization Function" and will not be called + * simultaneously with any other function, as if the CDM holds a write lock + * on the OEMCrypto system. + * + * @version + * This method changed in API version 13. + */ +OEMCryptoResult OEMCrypto_CloseSession(OEMCrypto_SESSION session); + +/** + * Generates three secondary keys, mac_key[server], mac_key[client], and + * encrypt_key, for handling signing and content key decryption under the + * license server protocol for CENC. + * + * Refer to the Key Derivation section above for more details. This function + * computes the AES-128-CMAC of the enc_key_context and stores it in secure + * memory as the encrypt_key. It then computes four cycles of AES-128-CMAC of + * the mac_key_context and stores it in the mac_keys -- the first two cycles + * generate the mac_key[server] and the second two cycles generate the + * mac_key[client]. These two keys will be stored until the next call to + * OEMCrypto_LoadLicense(). The device key from the keybox is used as the key + * for the AES-128-CMAC. + * + * @param[in] session: handle for the session to be used. + * @param[in] mac_key_context: pointer to memory containing context data for + * computing the HMAC generation key. + * @param[in] mac_key_context_length: length of the HMAC key context data, in + * bytes. + * @param[in] enc_key_context: pointer to memory containing context data for + * computing the encryption key. + * @param[in] enc_key_context_length: length of the encryption key context data, + * in bytes. + * + * Results: + * mac_key[server]: the 256 bit mac key is generated and stored in secure + * memory. + * mac_key[client]: the 256 bit mac key is generated and stored in secure + * memory. + * enc_key: the 128 bit encryption key is generated and stored in secure + * memory. + * + * @retval OEMCrypto_SUCCESS success + * @retval OEMCrypto_ERROR_NO_DEVICE_KEY + * @retval OEMCrypto_ERROR_INVALID_SESSION + * @retval OEMCrypto_ERROR_INVALID_CONTEXT + * @retval OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE + * @retval OEMCrypto_ERROR_BUFFER_TOO_LARGE + * @retval OEMCrypto_ERROR_SESSION_LOST_STATE + * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED + * + * @buffer_size + * OEMCrypto shall support mac_key_context and enc_key_context sizes as + * described in the section OEMCrypto_ResourceRatingTier() for messages. The + * key derivation context is about 25 bytes prepended to the request message. + * OEMCrypto shall return OEMCrypto_ERROR_BUFFER_TOO_LARGE if the buffers are + * too large. + * + * @threading + * This is a "Session Function" and may be called simultaneously with session + * functions for other sessions but not simultaneously with other functions + * for this session. It will not be called simultaneously with initialization + * or usage table functions. It is as if the CDM holds a write lock for this + * session, and a read lock on the OEMCrypto system. + * + * @version + * This method changed in API version 12. + */ +OEMCryptoResult OEMCrypto_GenerateDerivedKeys( + OEMCrypto_SESSION session, const OEMCrypto_SharedMemory* mac_key_context, + size_t mac_key_context_length, + const OEMCrypto_SharedMemory* enc_key_context, + size_t enc_key_context_length); + +/** + * Generates three secondary keys, mac_key[server], mac_key[client] and + * encrypt_key, for handling signing and content key decryption under the + * license server protocol for CENC. + * + * This function is similar to OEMCrypto_GenerateDerivedKeys(), except that it + * uses a session key to generate the secondary keys instead of the Widevine + * Keybox device key. These three keys will be stored in secure memory until + * the next call to LoadLicense or LoadProvisioning. + * + * If the session's private key is an RSA key, then the session key is passed + * in encrypted by the device RSA public key as the derivation_key, and must + * be decrypted with the RSA private key before use. + * + * If the sesion's private key is an ECC key, then the session key is the + * SHA256 of the shared secret key calculated by ECDH between the device's + * ECC private key and the derivation_key. See the document "OEMCrypto + * Elliptic Curve Support" for details. + * + * Once the enc_key and mac_keys have been generated, all calls to + * OEMCrypto_LoadLicense() proceed in the same manner for license requests using + * RSA or using a Widevine keybox token. + * + * This function is also used to derive keys before processing a Cast + * Certificate provisioning response in OEMCrypto_LoadProvisioning(). + * See [Cast Receiver](../../cast) for more details. + * + * @verification + * If the RSA key's allowed_schemes is not kSign_RSASSA_PSS, then no keys are + * derived and the error OEMCrypto_ERROR_INVALID_KEY is returned. An RSA + * key cannot be used for both deriving session keys and also for PKCS1 + * signatures. + * + * @param[in] session: handle for the session to be used. + * @param[in] derivation_key: session key, encrypted with the public RSA key + * (from the DRM certifcate) using RSA-OAEP. + * @param[in] derivation_key_length: length of derivation_key, in bytes. + * @param[in] mac_key_context: pointer to memory containing context data for + * computing the HMAC generation key. + * @param[in] mac_key_context_length: length of the HMAC key context data, in + * bytes. + * @param[in] enc_key_context: pointer to memory containing context data for + * computing the encryption key. + * @param[in] enc_key_context_length: length of the encryption key context data, + * in bytes. + * + * Results: + * mac_key[server]: the 256 bit mac key is generated and stored in secure + * memory. + * mac_key[client]: the 256 bit mac key is generated and stored in secure + * memory. + * enc_key: the 128 bit encryption key is generated and stored in secure + * memory. + * + * @retval OEMCrypto_SUCCESS success + * @retval OEMCrypto_ERROR_DEVICE_NOT_RSA_PROVISIONED + * @retval OEMCrypto_ERROR_INVALID_SESSION + * @retval OEMCrypto_ERROR_INVALID_CONTEXT + * @retval OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE + * @retval OEMCrypto_ERROR_BUFFER_TOO_LARGE + * @retval OEMCrypto_ERROR_SESSION_LOST_STATE + * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED + * + * @buffer_size + * OEMCrypto shall support mac_key_context and enc_key_context sizes as + * described in the section OEMCrypto_ResourceRatingTier() for messages. The + * key derivation context is about 25 bytes prepended to the request message. + * OEMCrypto shall return OEMCrypto_ERROR_BUFFER_TOO_LARGE if the buffers are + * too large. + * + * @threading + * This is a "Session Function" and may be called simultaneously with session + * functions for other sessions but not simultaneously with other functions + * for this session. It will not be called simultaneously with initialization + * or usage table functions. It is as if the CDM holds a write lock for this + * session, and a read lock on the OEMCrypto system. + * + * @version + * This method changed in API version 16. + */ +OEMCryptoResult OEMCrypto_DeriveKeysFromSessionKey( + OEMCrypto_SESSION session, const uint8_t* derivation_key, + size_t derivation_key_length, const OEMCrypto_SharedMemory* mac_key_context, + size_t mac_key_context_length, + const OEMCrypto_SharedMemory* enc_key_context, + size_t enc_key_context_length); + +/** + * Generates a 32-bit nonce to detect possible replay attack on the key + * control block. The nonce is stored in secure memory and will be used in + * the license or provisioning request. + * + * Because the nonce will be used to prevent replay attacks, it is desirable + * that a rogue application cannot rapidly call this function until a + * repeated nonce is created randomly. This is called a nonce flood. With + * this in mind, if more than 200 nonces are requested within one second, + * OEMCrypto will return an error after the 200th and not generate any more + * nonces for the rest of the second. After an error, if the application + * waits at least one second before requesting more nonces, then OEMCrypto + * will reset the error condition and generate valid nonces again. + * + * The nonce should be stored in the session's ODK_NonceValue field by + * calling the function ODK_SetNonceValue(&nonce_values, nonce). The ODK + * functions are documented in "Widevine Core Message Serialization". + * + * This function shall only be called at most once per open session. It shall + * only be called before signing either a provisioning request or a license + * request. If an attempt is made to generate a nonce while in the wrong + * state, an error of OEMCrypto_ERROR_INVALID_CONTEXT is returned. + * + * @param[in] session: handle for the session to be used. + * @param[out] nonce: pointer to memory to receive the computed nonce. + * + * Results: + * nonce: the nonce is also stored in secure memory. + * + * @retval OEMCrypto_SUCCESS success + * @retval OEMCrypto_ERROR_INVALID_SESSION + * @retval OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE + * @retval OEMCrypto_ERROR_SESSION_LOST_STATE + * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED + * + * @threading + * This is a "Session Initialization Function" and will not be called + * simultaneously with any other function, as if the CDM holds a write lock + * on the OEMCrypto system. + * + * @version + * This method changed in API version 16. + */ +OEMCryptoResult OEMCrypto_GenerateNonce(OEMCrypto_SESSION session, + uint32_t* nonce); + +/** + * OEMCrypto will use ODK_PrepareCoreLicenseRequest to prepare the core + * message. If it returns OEMCrypto_SUCCESS, then OEMCrypto shall sign the + * message body using the DRM certificate's private key. If it returns an + * error, the error should be returned by OEMCrypto to the CDM layer. + * ODK_PrepareCoreLicenseRequest is described in the document "Widevine Core + * Message Serialization". + * + * The message body is the buffer starting at message + core_message_size, + * and with length message_length - core_message_size. The reason OEMCrypto + * only signs the message body and not the entire message is to allow a v16 + * device to request a license from a v15 license server. + * + * If the session's private RSA key has an "allowed_schemes" bit field, then + * it must be 0x1 (RSASSA-PSS with SHA1). If not, then an error of + * OEMCrypto_ERROR_SIGNATURE_FAILURE shall be returned. + * + * OEMCrypto shall also call the function ODK_InitializeClockValues, + * described in the document "License Duration and Renewal", to initialize + * the session's clock values. + * + * Refer to the Signing Messages Sent to a Server section above for more + * details about the signature algorithm. + * + * NOTE: if signature pointer is null and/or input signature_length is zero, + * this function returns OEMCrypto_ERROR_SHORT_BUFFER and sets output + * signature_length to the size needed to receive the output signature. + * + * @param[in] session: handle for the session to be used. + * @param[in,out] message: Pointer to memory for the entire message. Modified by + * OEMCrypto via the ODK library. + * @param[in] message_length: length of the entire message buffer. + * @param[in,out] core_message_size: length of the core message at the beginning + * of the message. (in) size of buffer reserved for the core message, in + * bytes. (out) actual length of the core message, in bytes. + * @param[out] signature: pointer to memory to receive the computed signature. + * @param[in,out] signature_length: (in) length of the signature buffer, in + * bytes. (out) actual length of the signature, in bytes. + * + * @retval OEMCrypto_SUCCESS success + * @retval OEMCrypto_ERROR_INVALID_SESSION + * @retval OEMCrypto_ERROR_SHORT_BUFFER if signature buffer is not large enough + * to hold the signature. + * @retval OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE + * @retval OEMCrypto_ERROR_SIGNATURE_FAILURE + * @retval OEMCrypto_ERROR_BUFFER_TOO_LARGE + * @retval OEMCrypto_ERROR_SESSION_LOST_STATE + * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED + * @retval OEMCrypto_ERROR_INVALID_KEY if the session's private key is not a + * DRM key. + * + * @buffer_size + * OEMCrypto shall support message sizes as described in the section + * OEMCrypto_ResourceRatingTier(). + * OEMCrypto shall return OEMCrypto_ERROR_BUFFER_TOO_LARGE if the buffer is + * larger than the supported size. + * + * @threading + * This is a "Session Function" and may be called simultaneously with session + * functions for other sessions but not simultaneously with other functions + * for this session. It will not be called simultaneously with initialization + * or usage table functions. It is as if the CDM holds a write lock for this + * session, and a read lock on the OEMCrypto system. + * + * @version + * This method changed in API version 16. + */ +OEMCryptoResult OEMCrypto_PrepAndSignLicenseRequest( + OEMCrypto_SESSION session, uint8_t* message, size_t message_length, + size_t* core_message_size, uint8_t* signature, size_t* signature_length); + +/** + * OEMCrypto will use ODK_PrepareCoreRenewalRequest, as described in the + * document "Widevine Core Message Serialization", to prepare the core + * message. + * + * If it returns an error, the error should be returned by OEMCrypto to the + * CDM layer. If it returns OEMCrypto_SUCCESS, then OEMCrypto computes the + * signature using the renewal mac key which was delivered in the license via + * LoadLicense. + * + * If nonce_values.api_level is 16, then OEMCrypto shall compute the + * signature of the entire message using the session's client renewal mac + * key. The entire message is the buffer starting at message with length + * message_length. + * + * If nonce_values.api_major_version is 15, then OEMCrypto shall compute the + * signature of the message body using the session's client renewal mac key. + * The message body is the buffer starting at message+core_message_size with + * length message_length - core_message_size. If the session has not had a + * license loaded, it will use the usage entries client mac key to sign the + * message body. + * + * This function generates a HMAC-SHA256 signature using the mac_key[client] + * for license request signing under the license server protocol for CENC. + * + * The key used for signing should be the mac_key[client] that was generated + * for this session or loaded for this session by + * OEMCrypto_LoadLicense() or OEMCrypto_LoadUsageEntry(). + * + * Refer to the Signing Messages Sent to a Server section above for more + * details. + * + * If a usage entry has been loaded, but keys have not been loaded through + * OEMCrypto_LoadLicense(), then the derived mac keys and the keys in the usage + * entry may be different. In this case, the mac keys specified in the usage + * entry should be used. + * + * NOTE: if signature pointer is null and/or input signature_length is zero, + * this function returns OEMCrypto_ERROR_SHORT_BUFFER and sets output + * signature_length to the size needed to receive the output signature. + * + * @param[in] session: handle for the session to be used. + * @param[in,out] message: Pointer to memory for the entire message. Modified by + * OEMCrypto via the ODK library. + * @param[in] message_length: length of the entire message buffer. + * @param[in,out] core_message_size: length of the core message at the beginning + * of the message. (in) size of buffer reserved for the core message, in + * bytes. (out) actual length of the core message, in bytes. + * @param[out] signature: pointer to memory to receive the computed signature. + * @param[in,out] signature_length: (in) length of the signature buffer, in + * bytes. (out) actual length of the signature, in bytes. + * + * @retval OEMCrypto_SUCCESS success + * @retval OEMCrypto_ERROR_INVALID_SESSION + * @retval OEMCrypto_ERROR_SHORT_BUFFER if signature buffer is not large enough + * to hold the signature. + * @retval OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE + * @retval OEMCrypto_ERROR_BUFFER_TOO_LARGE + * @retval OEMCrypto_ERROR_SESSION_LOST_STATE + * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED + * + * @buffer_size + * OEMCrypto shall support message sizes as described in the section + * OEMCrypto_ResourceRatingTier(). + * OEMCrypto shall return OEMCrypto_ERROR_BUFFER_TOO_LARGE if the buffer is + * larger than the supported size. + * + * @threading + * This is a "Session Function" and may be called simultaneously with session + * functions for other sessions but not simultaneously with other functions + * for this session. It will not be called simultaneously with initialization + * or usage table functions. It is as if the CDM holds a write lock for this + * session, and a read lock on the OEMCrypto system. + * + * @version + * This method changed in API version 16. + */ +OEMCryptoResult OEMCrypto_PrepAndSignRenewalRequest( + OEMCrypto_SESSION session, uint8_t* message, size_t message_length, + size_t* core_message_size, uint8_t* signature, size_t* signature_length); + +/** + * Install a set of keys for performing decryption in the current session. + * + * First, OEMCrypto shall verify the signature of the message using + * HMAC-SHA256 with the derived mac_key[server]. The signature verification + * shall use a constant-time algorithm (a signature mismatch will always take + * the same time as a successful comparison). The signature is over the + * entire message buffer starting at message with length message_length. If + * the signature verification fails, ignore all other arguments and return + * OEMCrypto_ERROR_SIGNATURE_FAILURE. Otherwise, add the keys to the session + * context. + * + * NOTE: The calling software must have previously established the mac_keys + * and encrypt_key with a call to OEMCrypto_DeriveKeysFromSessionKey(). + * + * Refer to the Verification of Messages from a Server section above for more + * details. + * + * The function ODK_ParseLicense is called to parse the message. If it + * returns an error, OEMCrypto shall return that error to the CDM layer. The + * function ODK_ParseLicense is described in the document "Widevine Core + * Message Serialization". + * + * Below, all fields are found in the struct ODK_ParsedLicense parsed_license + * returned by ODK_ParseLicense. + * + * The keys will be decrypted using the current encrypt_key (AES-128-CBC) and + * the IV given in the KeyObject. If the API version of the license is less + * than 17, each key control block will be decrypted using the first 128 bits + * of the corresponding content key (AES-128-CBC) and the IV given in the + * KeyObject. In v17 licenses, the key control block is not decrypted. + * + * If its length is not zero, enc_mac_keys will be used to create new + * mac_keys. After all keys have been decrypted and validated, the new + * mac_keys are decrypted with the current encrypt_key and the offered IV. + * The new mac_keys replaces the current mac_keys for future signing renewal + * requests and loading renewal responses. The first 256 bits of the mac_keys + * become the mac_key[server] and the following 256 bits of the mac_keys + * become the mac_key[client]. If enc_mac_keys is null, then there will not + * be a call to OEMCrypto_LoadRenewal() for this session and the current + * mac_keys may be deleted. + * + * If the field license_type is OEMCrypto_ContentLicense, then the fields + * key_id and key_data in an OEMCrypto_KeyObject are loaded in to the + * content_key_id and content_key_data fields of the key table entry. In this + * case, entitlement key ids and entitlement key data is left blank. + * + * If the field license_type is OEMCrypto_EntitlementLicense, then the + * fields key_id and key_data in an OEMCrypto_KeyObject are loaded in to the + * entitlement_key_id and entitlement_key_data fields of the key table entry. + * In this case, content key ids and content key data will be loaded later + * with a call to OEMCrypto_LoadEntitledContentKeys(). + * + * OEMCrypto may assume that the key_id_length is at most 16. However, + * OEMCrypto shall correctly handle key id lengths from 1 to 16 bytes. + * + * OEMCrypto shall handle multiple keys, as described in the document + * [Resource Rating Tiers](/widevine/drm/feature/resource-rating). + * + * After a call to OEMCrypto_LoadLicense(), oemcrypto should clear the + * encrypt_key for the session. + * + * @verification + * The following checks should be performed. If any check fails, an error is + * returned, and none of the keys are loaded. + * 13. The signature of the message shall be computed, and the API shall + * verify the computed signature matches the signature passed in. If + * not, return OEMCrypto_ERROR_SIGNATURE_FAILURE. The signature + * verification shall use a constant-time algorithm (a signature + * mismatch will always take the same time as a successful comparison). + * 14. If there already is a license loaded into this session, return + * OEMCrypto_ERROR_LICENSE_RELOAD. + * 15. The enc_mac_keys substring must either have zero length, or satisfy + * the range check. I.e. (offset < message_length) && (offset + length + * <= message_length) && (offset <= offset + length), and offset + length + * does not cause an integer overflow. If it does not have zero length, + * then enc_mac_keys_iv must not have zero length, and must also satisfy + * the range check. If not, return OEMCrypto_ERROR_INVALID_CONTEXT. If + * the length is zero, then OEMCrypto may assume that the offset is also + * zero. + * 16. The API shall verify that each substring in each KeyObject points to + * a location in the message. I.e. (offset < message_length) && + * (offset + length <= message_length) && (offset <= offset + length), + * and offset + length does not cause an integer overflow, for each of + * key_id, key_data_iv, key_data, key_control_iv, key_control. If not, + * return OEMCrypto_ERROR_INVALID_CONTEXT. + * 17. Each key's control block, after decryption, shall have a valid + * verification field. If not, return OEMCrypto_ERROR_INVALID_CONTEXT. + * 18. If any key control block has the Nonce_Enabled bit set, that key's + * Nonce field shall match a nonce in the cache. If not, return + * OEMCrypto_ERROR_INVALID_NONCE. If there is a match, remove that + * nonce from the cache. Note that all the key control blocks in a + * particular call shall have the same nonce value. + * 19. If any key control block has the Require_AntiRollback_Hardware bit + * set, and the device does not protect the usage table from rollback, + * then do not load the keys and return OEMCrypto_ERROR_UNKNOWN_FAILURE. + * 20. If the key control block has a nonzero Replay_Control, then the + * verification described below is also performed. + * 21. If the key control block has the bit SRMVersionRequired is set, then + * the verification described below is also performed. If the SRM + * requirement is not met, then the key control block's HDCP_Version + * will be changed to 0xF - local display only. + * 22. If key_array_length == 0, then return + * OEMCrypto_ERROR_INVALID_CONTEXT. + * 23. If this session is associated with a usage table entry, and that + * entry is marked as "inactive" (either kInactiveUsed or + * kInactiveUnused), then the keys are not loaded, and the error + * OEMCrypto_ERROR_LICENSE_INACTIVE is returned. + * 24. The data in enc_mac_keys_iv is not identical to the 16 bytes before + * enc_mac_keys. If it is, return OEMCrypto_ERROR_INVALID_CONTEXT. + * 25. IF ODK_ParseLicense returns ODK_TIMER_EXPIRED, return + * OEMCrypto_ERROR_KEY_EXPIRED. If ODK_ParseLicense returns ODK_SET_TIMER + * or ODK_DISABLE_TIMER, the playback timer has started and OEMCrypto + * should treat this as if a Decrypt call has been made. + * + * Usage Table and Provider Session Token (pst) + * The function ODK_ParseLicense takes several parameters that may need more + * explanation. + * The parameter usage_entry_present shall be set to true if a usage entry + * was created or loaded for this session. This parameter is used by + * ODK_ParseLicense for usage entry verification. + * The parameter initial_license_load shall be false if the usage entry was + * loaded. If there is no usage entry or if the usage entry was created with + * OEMCrypto_CreateNewUsageEntry(), then initial_license_load shall be true. + * If a usage entry is present, then it shall be verified after the call to + * ODK_ParseLicense. + * If initial_license_load is true: + * 1. OEMCrypto shall copy the PST from the parsed license to the usage + * entry. + * 2. OEMCrypto shall verify that the server and client mac keys were + * updated by the license. The server and client mac keys shall be + * copied to the usage entry. + * If initial_license_load is false: + * 1. OEMCrypto shall verify the PST from the parsed license matches that + * in the usage entry. If not, then an error OEMCrypto_ERROR_WRONG_PST + * is returned. + * 2. OEMCrypto shall verify that the server and client mac keys were + * updated by the license. OEMCrypto shall verify that the server and + * client mac keys match those in the usage entry. If not the error + * OEMCrypto_ERROR_WRONG_KEYS is returned. + * If a key control block has a nonzero value for Replay_Control, then all + * keys in this license will have the same value for Replay_Control. In this + * case, the following additional checks are performed. + * - The substring pst must have nonzero length and must satisfy the range + * check described above. If not, return + * OEMCrypto_ERROR_INVALID_CONTEXT. + * - The session must be associated with a usage table entry, either + * created via OEMCrypto_CreateNewUsageEntry() or loaded via + * OEMCrypto_LoadUsageEntry(). + * - If Replay_Control is 1 = Nonce_Required, then OEMCrypto will perform a + * nonce check as described above. OEMCrypto will verify that the + * usage entry is newly created with OEMCrypto_CreateNewUsageEntry(). If + * an existing entry was reloaded, an error + * OEMCrypto_ERROR_INVALID_CONTEXT is returned and no keys are loaded. + * OEMCrypto will then copy the pst and the mac keys to the usage entry, + * and set the status to Unused. The license received time of the entry + * will be updated to the current time, and the status will be set to + * Unused. This Replay_Control prevents the license from being loaded + * more than once, and will be used for online streaming. + * - If Replay_Control is 2 = "Require existing Session Usage table entry + * or Nonce", then OEMCrypto will behave slightly differently on the + * first call to LoadLicense for this license. + * * If the usage entry was created with OEMCrypto_CreateNewUsageEntry() + * for this session, then OEMCrypto will verify the nonce for each + * key. OEMCrypto will copy the pst and mac keys to the usage + * entry. The license received time of the entry will be updated + * to the current time, and the status will be set to Unused. + * * If the usage entry was loaded with OEMCrypto_LoadUsageEntry() for + * this session, then OEMCrypto will NOT verify the nonce for each + * key. Instead, it will verify that the pst passed in matches + * that in the entry. Also, the entry's mac keys will be verified + * against the current session's mac keys. This allows an offline + * license to be reloaded but maintain continuity of the playback + * times from one session to the next. + * * If the nonce is not valid and a usage entry was not loaded, the + * return error is OEMCrypto_ERROR_INVALID_NONCE. + * * If the loaded usage entry has a pst that does not match, + * OEMCrypto returns the error OEMCrypto_ERROR_WRONG_PST. + * * If the loaded usage entry has mac keys that do not match the + * license, OEMCrypto returns the error OEMCrypto_ERROR_WRONG_KEYS. + * Note: If LoadLicense updates the mac keys, then the new updated mac keys will + * be used with the Usage Entry -- i.e. the new keys are stored in the + * usage table when creating a new entry, or the new keys are verified + * against those in the usage table if there is an existing entry. If + * LoadLicense does not update the mac keys, the existing session mac keys are + * used. + * Sessions that are associated with an entry will need to be able to update + * and verify the status of the entry, and the time stamps in the entry. + * Devices that do not support the Usage Table will return + * OEMCrypto_ERROR_INVALID_CONTEXT if the Replay_Control is nonzero. + * SRM Restriction Data + * If any key control block has the flag SRMVersionRequired set, then the + * following verification is also performed. + * 4. The substring srm_restriction_data must have nonzero length and must + * satisfy the range check described above. If not, return + * OEMCrypto_ERROR_INVALID_CONTEXT. + * 5. The first 8 bytes of srm_restriction_data must match the string + * "HDCPDATA". If not, return OEMCrypto_ERROR_INVALID_CONTEXT. + * 6. The next 4 bytes of srm_restriction_data will be converted from + * network byte order. If the current SRM installed on the device has a + * version number less than this, then the SRM requirement is not met. + * If the device does not support SRM files, or OEMCrypto cannot + * determine the current SRM version number, then the SRM requirement is + * not met. + * Note: if the current SRM version requirement is not met, LoadLicense will + * still succeed and the keys will be loaded. However, those keys with the + * SRMVersionRequired bit set will have their HDCP_Version increased to 0xF - + * local display only. Any future call to OEMCrypto_GetKeyHandle() for these + * keys while there is an external display will return + * OEMCrypto_ERROR_INSUFFICIENT_HDCP at that time. + * + * @param[in] session: crypto session identifier. + * @param[in] message: pointer to memory containing data. + * @param[in] message_length: length of the message, in bytes. + * @param[in] core_message_length: length of the core submessage, in bytes. + * @param[in] signature: pointer to memory containing the signature. + * @param[in] signature_length: length of the signature, in bytes. + * + * @retval OEMCrypto_SUCCESS success OEMCrypto_ERROR_NO_DEVICE_KEY + * @retval OEMCrypto_SUCCESS success + * @retval OEMCrypto_ERROR_NO_DEVICE_KEY + * @retval OEMCrypto_ERROR_INVALID_SESSION + * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE + * @retval OEMCrypto_ERROR_INVALID_CONTEXT + * @retval OEMCrypto_ERROR_SIGNATURE_FAILURE + * @retval OEMCrypto_ERROR_INVALID_NONCE + * @retval OEMCrypto_ERROR_TOO_MANY_KEYS + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED + * @retval OEMCrypto_ERROR_BUFFER_TOO_LARGE + * @retval OEMCrypto_ERROR_SESSION_LOST_STATE + * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED + * @retval OEMCrypto_ERROR_LICENSE_RELOAD + * @retval OEMCrypto_ERROR_KEY_EXPIRED + * + * @buffer_size + * OEMCrypto shall support message sizes as described in the section + * OEMCrypto_ResourceRatingTier(). + * OEMCrypto shall return OEMCrypto_ERROR_BUFFER_TOO_LARGE if the buffer is + * larger than the supported size. + * + * @threading + * This is a "Session Function" and may be called simultaneously with session + * functions for other sessions but not simultaneously with other functions + * for this session. It will not be called simultaneously with initialization + * or usage table functions. It is as if the CDM holds a write lock for this + * session, and a read lock on the OEMCrypto system. + * + * @version + * This method changed in API version 16. + */ +OEMCryptoResult OEMCrypto_LoadLicense(OEMCrypto_SESSION session, + const uint8_t* message, + size_t message_length, + size_t core_message_length, + const uint8_t* signature, + size_t signature_length); + +/** + * Updates the clock values and resets the renewal timer for the current + * session. + * + * OEMCrypto shall verify the signature of the entire message using the + * session's renewal mac key for the server. The entire message is the buffer + * starting at message with length message_length. If the signature does not + * match, OEMCrypto returns OEMCrypto_ERROR_SIGNATURE_FAILURE. + * + * OEMCrypto shall verify that nonce_values.api_major_version is 16. If not, + * return the error OEMCrypto_ERROR_INVALID_CONTEXT. + * + * If the signature passes, OEMCrypto shall use the function + * ODK_ParseRenewal, as described in the document "Widevine Core Message + * Serialization" to parse and verify the message. If ODK_ParseRenewal + * returns an error OEMCrypto returns the error to the CDM layer. + * + * The function ODK_ParseRenewal updates the clock values for the session, + * and may return ODK_SET_TIMER, ODK_DISABLE_TIMER or ODK_TIMER_EXPIRED on + * success. These values shall be handled by OEMCrypto, as discussed in the + * document "License Duration and Renewal". + * + * NOTE: OEMCrypto_LoadLicense() must be called first to load the keys into + * the session. + * + * @verification + * The signature of the message shall be computed using mac_key[server], and + * the API shall verify the computed signature matches the signature passed + * in. If not, return OEMCrypto_ERROR_SIGNATURE_FAILURE. The signature + * verification shall use a constant-time algorithm (a signature mismatch + * will always take the same time as a successful comparison). + * + * @param[in] session: handle for the session to be used. + * @param[in] message: pointer to memory containing message to be verified. + * @param[in] message_length: length of the message, in bytes. + * @param[in] core_message_length: length of the core submessage, in bytes. + * @param[in] signature: pointer to memory containing the signature. + * @param[in] signature_length: length of the signature, in bytes. + * + * @retval OEMCrypto_SUCCESS success + * @retval OEMCrypto_ERROR_NO_DEVICE_KEY + * @retval OEMCrypto_ERROR_INVALID_SESSION + * @retval OEMCrypto_ERROR_INVALID_CONTEXT + * @retval OEMCrypto_ERROR_SIGNATURE_FAILURE + * @retval OEMCrypto_ERROR_INVALID_NONCE + * @retval OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE + * @retval OEMCrypto_ERROR_BUFFER_TOO_LARGE + * @retval OEMCrypto_ERROR_SESSION_LOST_STATE + * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED + * @retval ODK_STALE_RENEWAL + * + * @buffer_size + * OEMCrypto shall support message sizes as described in the section + * OEMCrypto_ResourceRatingTier(). + * OEMCrypto shall return OEMCrypto_ERROR_BUFFER_TOO_LARGE if the buffer is + * larger than the supported size. + * + * @threading + * This is a "Session Function" and may be called simultaneously with session + * functions for other sessions but not simultaneously with other functions + * for this session. It will not be called simultaneously with initialization + * or usage table functions. It is as if the CDM holds a write lock for this + * session, and a read lock on the OEMCrypto system. + * + * @version + * This method changed in API version 12. + */ +OEMCryptoResult OEMCrypto_LoadRenewal(OEMCrypto_SESSION session, + const uint8_t* message, + size_t message_length, + size_t core_message_length, + const uint8_t* signature, + size_t signature_length); + +/** + * Returns the decrypted key control block for the given content_key_id. This + * function is for application developers to debug license server and key + * timelines. It only returns a key control block if LoadLicense was successful, + * otherwise it returns OEMCrypto_ERROR_NO_CONTENT_KEY. The developer of the + * OEMCrypto library must be careful that the keys themselves are not + * accidentally revealed. + * + * Note: returns control block in original, network byte order. If OEMCrypto + * converts fields to host byte order internally for storage, it should + * convert them back. Since OEMCrypto might not store the nonce or validation + * fields, values of 0 may be used instead. + * + * @verification + * The following checks should be performed. + * 1. If key_id is null, return OEMCrypto_ERROR_INVALID_CONTEXT. + * 2. If key_control_block_length is null, return + * OEMCrypto_ERROR_INVALID_CONTEXT. + * 3. If *key_control_block_length is less than the length of a key control + * block, set it to the correct value, and return + * OEMCrypto_ERROR_SHORT_BUFFER. + * 4. If key_control_block is null, return OEMCrypto_ERROR_INVALID_CONTEXT. + * 5. If the specified key has not been loaded, return + * OEMCrypto_ERROR_NO_CONTENT_KEY. + * + * @param[in] session: handle for the crypto or entitled key session to be used. + * @param[in] content_key_id: The unique id of the key of interest. + * @param[in] content_key_id_length: The length of key_id, in bytes. From 1 to + * 16, inclusive. + * @param[out] key_control_block: A caller-owned buffer. + * @param[in,out] key_control_block_length. The length of key_control_block + * buffer. + * + * @retval OEMCrypto_SUCCESS + * @retval OEMCrypto_ERROR_INVALID_CONTEXT + * @retval OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE + * @retval OEMCrypto_ERROR_SESSION_LOST_STATE + * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED + * @retval OEMCrypto_ERROR_INVALID_ENTITLED_KEY_SESSION + * + * @threading + * This is a "Session Function" and may be called simultaneously with session + * functions for other sessions but not simultaneously with other functions + * for this session. It will not be called simultaneously with initialization + * or usage table functions. It is as if the CDM holds a write lock for this + * session, and a read lock on the OEMCrypto system. + * + * @version + * This method is changed in API version 17. + */ +OEMCryptoResult OEMCrypto_QueryKeyControl(OEMCrypto_SESSION session, + const uint8_t* content_key_id, + size_t content_key_id_length, + uint8_t* key_control_block, + size_t* key_control_block_length); + +/// @} + +/// @addtogroup entitled +/// @{ + +/** + * This method creates an entitled key session. + * OEMCrypto is required to support at least one entitled key session per + * license. For CAS support, we also require that OEMCrypto support at least + * six entitled key sessions per license. + * + * @param[in] oec_session: handle for the OEMCrypto session to be associated + * with the created entitled key session. + * @param[out] key_session: id of the created entitled key session. + * + * @retval OEMCrypto_SUCCESS success + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED + * @retval OEMCrypto_ERROR_INVALID_SESSION + * @retval OEMCrypto_ERROR_TOO_MANY_SESSIONS + * + * @threading + * This is a "Session Function" and may be called simultaneously with session + * functions for other sessions but not simultaneously with other functions + * for this oec_session. It will not be called simultaneously with + * initialization or usage table functions. It is as if the CDM holds a write + * lock for this session, and a read lock on the OEMCrypto system. + * + * @version + * This method is new in API version 17. + */ +OEMCryptoResult OEMCrypto_CreateEntitledKeySession( + OEMCrypto_SESSION oec_session, OEMCrypto_SESSION* key_session); + +/** + * This method which removes an entitled key session. + * + * @param[in] key_session: id of the entitled key session to be removed. + * + * Returns: + * @retval OEMCrypto_SUCCESS success + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED + * @retval OEMCrypto_ERROR_INVALID_ENTITLED_KEY_SESSION + * + * @threading + * This is a "Session Function" and may be called simultaneously with session + * functions for other sessions but not simultaneously with other functions + * for this session. It will not be called simultaneously with initialization + * or usage table functions. It is as if the CDM holds a write lock for this + * session, and a read lock on the OEMCrypto system. + * + * @version + * This method is new in API version 17. + */ +OEMCryptoResult OEMCrypto_RemoveEntitledKeySession( + OEMCrypto_SESSION key_session); + +/** + * Load content keys into an entitled session which is associated with an + * entitlement sessions. This function will only be called for an entitled + * session after a call to OEMCrypto_LoadLicense() has been called on the + * associated entitlement session. This function may be called multiple times + * for the same session. + * + * If the session is not an entitled session, return + * OEMCrypto_ERROR_INVALID_CONTEXT and perform no work. + * + * For each key object in key_array, OEMCrypto shall look up the entry in the + * key table for the entitlement session with the corresponding + * entitlement_key_id. + * + * 1. If no entry is found, return OEMCrypto_KEY_NOT_ENTITLED. + * 2. If the entry already has a content_key_id and content_key_data, that + * id and data are erased. + * 3. The content_key_id from the key_array is copied to the entry's + * content_key_id. + * 4. The content_key_data decrypted using the entitlement_key_data as a + * key for AES-256-CBC with an IV of content_key_data_iv. Notice that + * the entitlement key will be an AES 256 bit key. The clear content key + * data will be stored in the entry's content_key_data. + * Entries in the key table that do not correspond to anything in the + * key_array are not modified or removed. + * + * For devices that use a hardware key ladder, it may be more convenient to + * store the encrypted content key data in the key table, and decrypt it when + * the function OEMCrypto_GetKeyHandle() is called. + * + * @param[in] session: handle for the entitled key session to be used. + * @param[in] message: pointer to memory containing message to be verified. + * @param[in] message_length: length of the message, in bytes. + * @param[in] key_array_length: number of keys present. + * @param[in] key_array: set of key updates. + * + * @retval OEMCrypto_SUCCESS success + * @retval OEMCrypto_ERROR_INVALID_SESSION + * @retval OEMCrypto_ERROR_INVALID_CONTEXT + * @retval OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE + * @retval OEMCrypto_KEY_NOT_ENTITLED + * @retval OEMCrypto_ERROR_SESSION_LOST_STATE + * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED + * @retval OEMCrypto_ERROR_INVALID_ENTITLED_KEY_SESSION + * + * @buffer_size + * OEMCrypto shall support message sizes as described in the section + * OEMCrypto_ResourceRatingTier(). + * OEMCrypto shall return OEMCrypto_ERROR_BUFFER_TOO_LARGE if the buffer is + * larger than the supported size. + * + * @threading + * This is a "Session Function" and may be called simultaneously with session + * functions for other sessions but not simultaneously with other functions + * for this session, or its entitlement session. It will not be called + * simultaneously with initialization or usage table functions. It is as if + * the CDM holds a write lock for this session, and a read lock on the + * OEMCrypto system. + * + * @version + * This method changed in API version 17. + */ +OEMCryptoResult OEMCrypto_LoadEntitledContentKeys( + OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, + size_t key_array_length, + const OEMCrypto_EntitledContentKeyObject* key_array); + +/** + * This method associates an existing entitled key session to the specified + * OEMCrypto session. + * + * @param[in] key_session: id of the entitled key session. + * @param[in] oec_session: handle for the OEMCrypto session to be associated + * with the entitled key session. + * + * @retval OEMCrypto_SUCCESS success + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED + * @retval OEMCrypto_ERROR_INVALID_ENTITLED_KEY_SESSION + * @retval OEMCrypto_ERROR_INVALID_SESSION + * + * @threading + * This is a "Session Initialization Function" and will not be called + * simultaneously with any other function, as if the CDM holds a write lock + * on the OEMCrypto system. + * + * @version + * This method is new in API version 17. + */ +OEMCryptoResult OEMCrypto_ReassociateEntitledKeySession( + OEMCrypto_SESSION key_session, OEMCrypto_SESSION oec_session); + +/** + * The OEMCrypto_LoadCasECMKeys method is added to load content keys into an + * entitled key session, which already has entitlement keys loaded. Used only by + * CAS. + * + * This function will only be called for a session after a call to + * OEMCrypto_LoadLicense with the license_type equal to + * OEMCrypto_EntitlementLicense, and a call to + * OEMCrypto_CreateEntitledKeySession initializing the entitled key session. + * This function may be called multiple times for the same session. + * + * For each key object, odd and even, OEMCrypto shall look up the entry in the + * key table with the corresponding entitlement_key_id. Before the + * entitlement_key is used: + * 1) If no entry is found, return OEMCrypto_KEY_NOT_ENTITLED. + * 2) Check the entitlement key’s key control block use. If failed, return + * corresponding error code such as OEMCrypto_ERROR_ANALOG_OUTPUT, + * OEMCrypto_ERROR_INSUFFICIENT_HDCP. + * 3) If the entitlement key’s control block has a nonzero Duration field, + * then the API shall verify that the duration is greater than the + * session’s elapsed time clock before the key is used. OEMCrypto will + * return OEMCrypto_ERROR_KEY_EXPIRED. + * 4) The content_key_data decrypted using the entitlement_key_data as a key + * for AES-256-CBC with an IV of content_key_data_iv. Wrapped content is + * padded using PKCS#7 padding. Notice that the entitlement key will be an + * AES 256 bit key. The clear content key data will be stored in the + * entry’s content_key_data. + * 5) The decrypted content key data may be set in a hardware KeySlot, + * together with content iv and cipher mode information, which can be used + * by the Descrambler in TunerHal. The entitled key session ID may be used + * as the key token to uniquely identify the content key in KeySlot. + * + * @param[in] session: handle for the entitled key session to be used. + * @param[in] message: pointer to memory containing message to be verified. + * @param[in] message_length: length of the message, in bytes. + * @param[in] even_key: key update for the even ecm key. May be null if the key + * does not change. + * @param[in] odd_key: key update for the odd ecm key. May be null if the key + * does not change. + * + * @retval OEMCrypto_SUCCESS success + * @retval OEMCrypto_ERROR_INVALID_SESSION + * @retval OEMCrypto_ERROR_INVALID_CONTEXT + * @retval OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE + * @retval OEMCrypto_KEY_NOT_ENTITLED + * @retval OEMCrypto_ERROR_INVALID_ENTITLED_KEY_SESSION + * @retval OEMCrypto_ERROR_KEY_EXPIRED + * @retval OEMCrypto_ERROR_ANALOG_OUTPUT + * @retval OEMCrypto_ERROR_INSUFFICIENT_HDCP + * + * @threading + * This is a "Session Function" and may be called simultaneously with session + * functions for other sessions but not simultaneously with other functions + * for this session. It will not be called simultaneously with initialization + * or usage table functions. It is as if the CDM holds a write lock for this + * session, and a read lock on the OEMCrypto system. + * + * @version + * This method is new in API version 17. + */ +OEMCryptoResult OEMCrypto_LoadCasECMKeys( + OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, + const OEMCrypto_EntitledContentKeyObject* even_key, + const OEMCrypto_EntitledContentKeyObject* odd_key); + +/** + * Retrieves the key token associated with the input entitled key session. This + * method is currently used only by CAS, where key token is a means to share + * vendor specific crypto info with other frameworks (e.g. Descrambler in + * Android TunerHAL) that are also under control of the vendor. + * + * @param[in] key_session: handle for the entitled key session to be used. + * @param[out] key_token: where the key token is stored. + * @param[in,out] key_token_length: length of the key token, in bytes. + * + * @retval OEMCrypto_SUCCESS on success + * @retval OEMCrypto_ERROR_SHORT_BUFFER if buffer_length is too small. + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED + * + * @threading + * This is an "Initialization and Termination Function" and will not be called + * simultaneously with any other function, as if the CDM holds a write lock on + * the OEMCrypto system. + * + * @version + * This method is new in API version 17. + */ +OEMCryptoResult OEMCrypto_GetOEMKeyToken(OEMCrypto_SESSION key_session, + uint8_t* key_token, + size_t* key_token_length); + +/// @} + +/// @addtogroup decryption +/// @{ + +/** + * Select a content key and install it in the hardware key ladder for + * subsequent decryption operations. (e.g. OEMCrypto_DecryptCENC(), generic + * crypto functions) The specified key must have been previously imported via + * OEMCrypto_LoadLicense() or OEMCrypto_LoadEntitledContentKeys(). Write a + * handle that can be used to refer to the installed key into the buffer pointed + * to by the key_handle parameter and set key_handle_length to the size of the + * data written to key_handle. + * + * If key_handle is NULL or key_handle_length is too small to hold the handle, + * write the number of bytes needed to hold a key handle to key_handle_length + * and return OEMCrypto_ERROR_SHORT_BUFFER. Do not install the key in this case. + * + * If the session has an entry in the Usage Table and the status of the entry is + * "unused", then change the status to "active" and set the + * time_of_first_decrypt. + * + * A key control block is associated with the key and the session, and is + * used to configure the session context. The Key Control data is documented + * in "Key Control Block Definition". + * + * Step 1: Lookup the content key data via the offered key_id. The key data + * includes the key value, and the key control block. + * + * Step 2: Latch the content key into the hardware key ladder. Set permission + * flags based on the key's control block. + * + * Step 3: use the latched content key to decrypt (AES-128-CTR or + * AES-128-CBC) buffers passed in via OEMCrypto_DecryptCENC(). If the key is + * 256 bits it will be used for OEMCrypto_Generic_Sign() or + * OEMCrypto_Generic_Verify() as specified in the key control block. If the key + * will be used for OEMCrypto_Generic_Encrypt() or OEMCrypto_Generic_Decrypt() + * then the cipher mode will always be OEMCrypto_CipherMode_CBCS. + * + * #### Bypass Decrypt + * + * Platforms that wish to support Bypass Decrypt should latch the key into + * secure crypto hardware such that it can be fed by the chosen bypass method. + * For more information on Bypass Decrypt, see the + * [Bypass Decrypt](../../bypass) documentation. + * + * If the device is bypassing, it must update the ODK clock values in this + * function call. If this is the first use of a key for this session, then + * OEMCrypto shall call ODK_AttemptFirstPlayback to update the session's clock + * values and verify playback is allowed. If this is not the first use of a key + * for this session, then OEMCrypto shall call ODK_UpdateLastPlaybackTime. See + * [ODK Clocks and Timers](../../odk-timers) for handling the return value of + * these ODK functions. The hardware that the key is latched into must be able + * to enforce that the key expires and becomes unusable after the amount of + * time returned by ODK in the timer_value field. + * + * The format of the key handle is opaque to Widevine and platform-specific. It + * should contain whatever information the platform will need to find the key in + * the hardware on the bypass decryption path, as well as in + * OEMCrypto_DecryptCENC() and the generic crypto functions. A key slot number + * is generally not sufficient, as this could lead to the wrong key being used + * if that slot later has a different key loaded into it. + * + * The key handle must not contain the actual cryptographic key. + * + * #### Non-Bypass Decrypt + * + * For platforms that do not need to support Bypass Decrypt, a mode compatible + * with previous versions of OEMCrypto is available. These devices may latch the + * key to the session and continue to use this key for this session until + * OEMCrypto_GetKeyHandle() is called again, or until OEMCrypto_CloseSession() + * is called. + * + * The "key handle" in this mode is the session ID. Platforms should request a + * 4-byte key handle buffer and copy the session ID into it. + * + * @verification + * 1. If the key id is not found in the keytable for this session, then the + * key state is not changed and OEMCrypto shall return + * OEMCrypto_ERROR_NO_CONTENT_KEY. + * 2. If the key control block has the bit Disable_Analog_Output set, then + * the device should disable analog video output. If the device has + * analog video output that cannot be disabled, then the key is not + * selected, and OEMCrypto_ERROR_ANALOG_OUTPUT is returned. This step is + * optional -- OEMCrypto_GetKeyHandle() may return OEMCrypto_SUCCESS and + * delay the error until a call to OEMCrypto_DecryptCENC(). + * 3. If the key control block has HDCP required, and the device cannot + * enforce HDCP, then the key is not selected, and + * OEMCrypto_ERROR_INSUFFICIENT_HDCP is returned. This step is optional + * -- OEMCrypto_GetKeyHandle() may return OEMCrypto_SUCCESS and delay the + * error until a call to OEMCrypto_DecryptCENC(). + * 4. If the key control block has a nonzero value for HDCP_Version, and + * the device cannot enforce at least that version of HDCP, then the key + * is not selected, and OEMCrypto_ERROR_INSUFFICIENT_HDCP is returned. + * + * @param[in] session: handle for the crypto or entitled key session to be used. + * @param[in] content_key_id: pointer to the content Key ID. + * @param[in] content_key_id_length: length of the content Key ID, in bytes. + * From 1 to 16, inclusive. + * @param[in] cipher_mode: whether the key should be prepared for CTR mode or + * CBC mode when used in later calls to DecryptCENC. This should be ignored + * when the key is used for Generic Crypto calls. + * @param[out] key_handle: pointer to a buffer in which the key handle should be + * stored. May be NULL on the first call in order to find required buffer + * size. + * @param[in,out] key_handle_length: (in) length of the key_handle buffer, in + * bytes. (out) actual length of the key handle written to the key_handle + * buffer, in bytes. May not be NULL. + * + * @retval OEMCrypto_SUCCESS success + * @retval OEMCrypto_ERROR_SHORT_BUFFER if the buffer is NULL or too small + * @retval OEMCrypto_ERROR_KEY_EXPIRED if the session's timer has expired + * @retval OEMCrypto_ERROR_INVALID_SESSION crypto session ID invalid or not open + * @retval OEMCrypto_ERROR_NO_DEVICE_KEY failed to decrypt device key + * @retval OEMCrypto_ERROR_NO_CONTENT_KEY failed to decrypt content key + * @retval OEMCrypto_ERROR_CONTROL_INVALID invalid or unsupported control input + * @retval OEMCrypto_ERROR_KEYBOX_INVALID cannot decrypt and read from Keybox + * @retval OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE + * @retval OEMCrypto_ERROR_ANALOG_OUTPUT + * @retval OEMCrypto_ERROR_INSUFFICIENT_HDCP + * @retval OEMCrypto_ERROR_SESSION_LOST_STATE + * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED + * @retval OEMCrypto_ERROR_INVALID_ENTITLED_KEY_SESSION + * + * @threading + * This is a "Session Function" and may be called simultaneously with session + * functions for other sessions but not simultaneously with other functions + * for this session. It will not be called simultaneously with initialization + * or usage table functions. It is as if the CDM holds a write lock for this + * session, and a read lock on the OEMCrypto system. + * + * @version + * This method is new in API version 18. + */ +OEMCryptoResult OEMCrypto_GetKeyHandle(OEMCrypto_SESSION session, + const uint8_t* content_key_id, + size_t content_key_id_length, + OEMCryptoCipherMode cipher_mode, + uint8_t* key_handle, + size_t* key_handle_length); + +/** + * Decrypts or copies a series of input payloads into output buffers using + * the installed key indicated by the key handle parameter. The input payload + * is delivered in the form of samples. The samples are subdivided into + * subsamples. "Samples" and "subsamples" are defined as in the ISO Common + * Encryption standard (ISO/IEC 23001-7:2016). The samples parameter contains + * a list of samples, each of which has its own input and output buffers. + * Each sample contains a buffers field that contains the input and output + * buffers in its input_data and output fields, respectively. + * + * Each sample contains an array of subsample descriptions in its subsamples + * field. Each subsample is defined as a number of clear bytes followed by a + * number of encrypted bytes. Subsamples are consecutive inside the sample; + * the clear bytes of the second subsample begin immediately after the + * encrypted bytes of the first subsample. This follows the definition in the + * ISO-CENC standard. + * + * Decryption mode is AES-128-CTR or AES-128-CBC depending on the value of + * cipher_mode previously passed in to OEMCrypto_GetKeyHandle(). For the + * encrypted portion of subsamples, the content key associated with the handle + * is latched in the active hardware key ladder and is used for the decryption + * operation. For the clear portion of subsamples, the data is simply copied. + * + * After decryption, all the input_data bytes are copied to the location + * described by the output field. The output field is an + * OEMCrypto_DestBufferDesc, which could be one of: + * + * 1. The structure OEMCrypto_DestBufferDesc contains a pointer to a clear + * text buffer. The OEMCrypto library shall verify that key control + * allows data to be returned in clear text. If it is not authorized, + * this method should return an error. + * 2. The structure OEMCrypto_DestBufferDesc contains a handle to a secure + * buffer. + * 3. The structure OEMCrypto_DestBufferDesc indicates that the data should + * be sent directly to the decoder and renderer. + * + * Depending on your platform's needs, you may not need to support all three + * of these options. + * + * SINGLE-SAMPLE DECRYPTION AND SINGLE-SUBSAMPLE DECRYPTION: + * + * If the OEMCrypto implementation is not able to handle the amount of + * samples and subsamples passed into it, it should return + * OEMCrypto_ERROR_BUFFER_TOO_LARGE, in which case the CDM can respond by + * breaking the samples up into smaller pieces and trying to decrypt each of + * them individually. It is possible that the CDM will break the samples + * array up into pieces that are still too large, in which case OEMCrypto may + * return OEMCrypto_ERROR_BUFFER_TOO_LARGE again. + * + * If the OEMCrypto implementation cannot handle multiple samples at once, it + * may return OEMCrypto_ERROR_BUFFER_TOO_LARGE any time it receives more than + * one sample in a single call to OEMCrypto_DecryptCENC(). + * + * Similarly, if the OEMCrypto implementation cannot handle multiple + * subsamples at once, it may return OEMCrypto_ERROR_BUFFER_TOO_LARGE any + * time it receives more than one subsample in a single call to + * OEMCrypto_DecryptCENC(). + * + * The exact way that the CDM code breaks up the samples array is not + * guaranteed by this specification. The CDM may break down the array of + * samples into many arrays each containing one sample. The CDM may break + * down samples into subsamples and pass individual subsamples into + * OEMCrypto, just like in OEMCrypto v15. The CDM may break down individual + * subsamples into smaller subsamples, just like in OEMCrypto v15. + * + * If OEMCrypto requests that the CDM break samples into subsamples, the + * "samples" passed into OEMCrypto_DecryptCENC() will no longer be full + * samples. When a full sample is passed into OEMCrypto_DecryptCENC(), the + * first subsample in the subsample array will have the + * OEMCrypto_FirstSubsample flag set in its subsample_flags field and the + * last subsample array will have the OEMCrypto_LastSubsample flag set in its + * subsample_flags field. If this is not the case, OEMCrypto will need to + * accumulate more subsamples from successive calls to OEMCrypto_DecryptCENC + * to receive the full sample. + * + * The first subsample in the sample will always have OEMCrypto_FirstSubsample + * set and the last subsample will always have the OEMCrypto_LastSubsample flag + * set, even if those subsamples are passed in separate calls to + * OEMCrypto_DecryptCENC(). This is the same as in OEMCrypto v15. The decrypted + * data will not be used until after the subsample with the flag + * OEMCrypto_LastSubsample has been sent to OEMCrypto. This can be relied on by + * OEMCrypto for optimization by not doing decrypt until the last subsample has + * been received. However, a device that can do decrypt of more than one + * subsample at a time will always have better performance if it can receive + * those subsamples in one OEMCrypto_DecryptCENC() call rather than as + * individual subsamples. + * + * Although the exact way that the CDM code breaks up the samples array when + * it receives OEMCrypto_ERROR_BUFFER_TOO_LARGE is not guaranteed by this + * specification, here is a sample way it might work: + * + * 1. It tries to pass the array of samples to OEMCrypto_DecryptCENC(). + * 2. If OEMCrypto returns OEMCrypto_ERROR_BUFFER_TOO_LARGE, it tries to + * pass each sample individually into OEMCrypto_DecryptCENC(). + * 3. If OEMCrypto returns OEMCrypto_ERROR_BUFFER_TOO_LARGE, it tries to + * pass the clear and encrypted parts of each subsample individually + * into OEMCrypto_DecryptCENC(). At this point, (and in the subsequent + * steps) it is replicating the behavior of OEMCrypto v15 and lower. + * 4. If OEMCrypto returns OEMCrypto_ERROR_BUFFER_TOO_LARGE, it breaks each + * piece of a subsample into smaller pieces, down to the minimum + * subsample size required by the device's resource rating tier. It + * passes these pieces into OEMCrypto_DecryptCENC(). + * 5. If OEMCrypto returns OEMCrypto_ERROR_BUFFER_TOO_LARGE, the device has + * failed to meet its resource rating tier requirements. It returns an + * error. + * Because this process requires a lot of back-and-forth between the CDM and + * OEMCrypto, partners are strongly recommended to support decrypting full + * samples or even multiple samples in their OEMCrypto implementation. + * + * ISO-CENC SCHEMES: + * + * The ISO Common Encryption standard (ISO/IEC 23001-7:2016) defines four + * "schemes" that may be used to encrypt content: 'cenc', 'cens', 'cbc1', and + * 'cbcs'. Starting with v16, OEMCrypto only supports 'cenc' and 'cbcs'. The + * schemes 'cens' and 'cbc1' are not supported. + * + * The decryption mode, either OEMCrypto_CipherMode_CENC or + * OEMCrypto_CipherMode_CBCS, was already specified in the call to + * OEMCrypto_GetKeyHandle(). The encryption pattern is specified by the fields + * in the parameter pattern. A description of partial encryption patterns for + * 'cbcs' can be found in the ISO-CENC standard, section 10.4. + * + * 'cenc' SCHEME: + * + * The 'cenc' scheme is OEMCrypto_CipherMode_CENC without an encryption + * pattern. All the bytes in the encrypted portion of each subsample are + * encrypted. In the pattern parameter, both the encrypt and skip fields will + * be zero. + * + * The length of a crypto block in AES-128 is 16 bytes. In the 'cenc' scheme, + * if an encrypted subsample has a length that is not a multiple of 16 bytes, + * then all the bytes of the encrypted subsample must be decrypted, but the + * next encrypted subsample will begin by completing the incomplete crypto + * block from the previous encrypted subsample. The following diagram + * provides an example: + * + * ![Drawing of block offset](fig5.svg) + * + * To help with this, the block_offset field of each subsample will contain + * the number of bytes the initial crypto block of that subsample should be + * offset by. In the example above, the block_offset for the first subsample + * would be 0 and the block_offset for the second subsample would be 12. + * 'cenc' is the only mode that allows for a nonzero block_offset. This field + * satisfies 0 <= block_offset < 16. + * + * 'cbcs' SCHEME: + * + * The 'cbcs' scheme is OEMCrypto_CipherMode_CBCS with an encryption pattern. + * Only some of the bytes in the encrypted portion of each subsample are + * encrypted. In the pattern parameter, the encrypt and skip fields will + * usually be non-zero. This mode allows devices to decrypt FMP4 HLS content, + * SAMPLE-AES HLS content, as well as content using the DASH 'cbcs' scheme. + * + * The skip field of OEMCrypto_CENCEncryptPatternDesc may also be zero. If + * the skip field is zero, then patterns are not in use and all crypto blocks + * in the encrypted part of the subsample are encrypted. It is not valid for + * the encrypt field to be zero. + * + * The length of a crypto block in AES-128 is 16 bytes. In the 'cbcs' scheme, + * if the encrypted part of a subsample has a length that is not a multiple + * of 16 bytes, then the final bytes that do not make up a full crypto block + * are clear and should never be decrypted. The following diagram provides an + * example: + * + * ![CBCS Scheme](fig6.svg) + * + * Whether any given protected block is actually encrypted also depends on + * the pattern. But the bytes at the end that do not make up a full crypto + * block will never be encrypted, regardless of what the pattern is. Even if + * the pattern says to decrypt every protected block, these bytes are clear + * and should not be decrypted. + * + * Of course, if the encrypted subsample has a length that is a multiple of + * 16 bytes, all the bytes in it are protected, and they may need to be + * decrypted following the pattern. The following diagram provides an example: + * + * ![CBCS Scheme - final partial block](fig7.svg) + * + * INITIALIZATION VECTOR BETWEEN SUBSAMPLES: + * + * The IV is specified for the initial subsample in a sample in the iv field + * of the OEMCrypto_SampleDescription. OEMCrypto is responsible for correctly + * updating the IV for subsequent subsamples according to the ISO Common + * Encryption standard (ISO/IEC 23001-7:2016). Section 9.5.2.3 covers 'cenc' + * and section 9.5.2.5 covers 'cbcs'. A summary of the ISO-CENC behavior + * follows: + * + * For 'cenc', the IV at the end of each subsample carries forward to the + * next subsample and becomes the IV at the beginning of the next subsample. + * If the subsample ends on a crypto block boundary, then the IV should be + * incremented as normal at the end of the crypto block. If the subsample + * ends in the middle of a crypto block, the same IV should continue to be + * used until the crypto block is completed in the next subsample. Only + * increment the IV after the partial crypto block is completed. + * + * For 'cbcs', the IV is reset at the beginning of each subsample. Each + * subsample should start with the IV that was passed into + * OEMCrypto_DecryptCENC(). + * + * To phrase it another way: In 'cenc', the encrypted portions of the + * subsamples can be concatenated to form one continuous ciphertext. In + * 'cbcs', each encrypted portion of a subsample is a separate ciphertext. + * Each separate ciphertext begins with the IV specified in the iv field of + * the OEMCrypto_SampleDescription. + * + * INITIALIZATION VECTOR WITHIN SUBSAMPLES: + * + * Once it has the IV for each subsample, OEMCrypto is responsible for + * correctly updating the IV for each crypto block of each encrypted + * subsample portion, as outlined in the ISO Common Encryption standard + * (ISO/IEC 23001-7:2016). Section 9.5.1 includes general information about + * IVs in subsample decryption. A summary of the ISO-CENC behavior follows: + * + * For 'cenc', the subsample's IV is the counter value to be used for the + * initial encrypted block of the subsample. The IV length is the AES block + * size. For subsequent encrypted AES blocks, OEMCrypto must calculate the IV + * by incrementing the lower 64 bits (byte 8-15) of the IV value used for the + * previous block. The counter rolls over to zero when it reaches its maximum + * value (0xFFFFFFFFFFFFFFFF). The upper 64 bits (byte 0-7) of the IV do not + * change. + * + * For 'cbcs', the subsample's IV is the initialization vector for the + * initial encrypted block of the subsample. Within each subsample, each + * crypto block is used as the IV for the next crypto block, as prescribed by + * AES-CBC. + * + * NOTES: + * + * If the destination buffer is secure, an offset may be specified. + * OEMCrypto_DecryptCENC() begins storing data buffers.output.secure.offset + * bytes after the beginning of the secure buffer. + * + * OEMCrypto cannot assume that the buffers of consecutive samples are + * consecutive in memory. + * + * A subsample may consist entirely of encrypted bytes or clear bytes. In + * this case, the clear or the encrypted part of the subsample will be zero, + * indicating that no bytes of that kind appear in the subsample. + * + * The ISO-CENC spec implicitly limits both the skip and encrypt values to be + * 4 bits, so they are at most 15. + * + * ![CTR Mode - no skip pattern](fig8.svg) + * + * If OEMCrypto assembles all of the encrypted subsample portions into a + * single buffer and then decrypts it in one pass, it can assume that the + * block offset is 0. + * + * ![CTR Mode - with skip pattern](fig9.svg) + * + * @verification + * The total size of all the subsamples cannot exceed the total size of the + * input buffer. OEMCrypto integrations should validate this and return + * OEMCrypto_ERROR_UNKNOWN_FAILURE if the subsamples are larger than the + * input buffer. No decryption should be performed in this case. + * If the subsamples all contain only clear bytes, then no further + * verification is performed. + * The following checks should be performed if any subsamples contain any + * encrypted bytes. If any check fails, an error is returned, and no + * decryption is performed. + * 1. If the current key's control block has the Data_Path_Type bit set, + * then the API shall verify that the output buffer is secure or direct. + * If not, return OEMCrypto_ERROR_DECRYPT_FAILED. + * 2. If the current key control block has the bit Disable_Analog_Output + * set, then the device should disable analog video output. If the + * device has analog video output that cannot be disabled, then + * OEMCrypto_ERROR_ANALOG_OUTPUT is returned. (See note on delayed + * error conditions below) + * 3. If the current key's control block has the HDCP bit set, then the API + * shall verify that the buffer will be displayed locally, or output + * externally using HDCP only. If not, return + * OEMCrypto_ERROR_INSUFFICIENT_HDCP. (See note on delayed error + * conditions below) + * 4. If the current key's control block has a nonzero value for + * HDCP_Version, then the current version of HDCP for the device and the + * display combined will be compared against the version specified in + * the control block. If the current version is not at least as high as + * that in the control block, and the device is not able to restrict + * displays with HDCP levels lower than what's in the control block, + * return OEMCrypto_ERROR_INSUFFICIENT_HDCP. If the device is able to + * restrict those displays, return + * OEMCrypto_WARNING_MIXED_OUTPUT_PROTECTION. (See note on delayed + * error conditions below) + * 5. If the current key has an entry in the Usage Table, and the status of + * that entry is either kInactiveUsed or kInactiveUnused, then return the + * error OEMCrypto_ERROR_LICENSE_INACTIVE. + * 6. If a Decrypt Hash has been initialized via OEMCrypto_SetDecryptHash(), + * and the current key's control block does not have the + * Allow_Hash_Verification bit set, then do not compute a hash and + * return OEMCrypto_ERROR_UNKNOWN_FAILURE. + * + * #### Delayed Error Conditions + * + * On some devices, the HDCP subsystem is not directly connected to the + * OEMCrypto TA. This means that returning the error + * OEMCrypto_ERROR_INSUFFICIENT_HDCP at the time of the decrypt call is a + * performance hit. However, some devices have the ability to tag output + * buffers with security requirements, such as the required HDCP level. + * For those devices, when a call to OEMCrypto_DecryptCENC() is made using a + * key that requires HDCP output, and if the HDCP level on the output does + * not meet the required level. + * - OEMCrypto may tag the output buffer as requiring HDCP at the required + * level and return OEMCrypto_SUCCESS. + * - Output shall not be sent to the display. + * - On the second or third call to OEMCrypto_DecryptCENC() with the same + * key, OEMCrypto shall return OEMCrypto_ERROR_INSUFFICIENT_HDCP. + * For those devices, when a call to OEMCrypto_DecryptCENC() is made using a + * key that requires HDCP output, and if the HDCP level on some of the + * displays does not meet the required level. + * - OEMCrypto may tag the output buffer as requiring HDCP at the required + * level and return OEMCrypto_SUCCESS. + * - Output shall only be sent to the display with sufficient output + * control, e.g. the local display. + * - On the second or third call to OEMCrypto_DecryptCENC() with the same + * key, OEMCrypto shall return OEMCrypto_WARNING_MIXED_OUTPUT_PROTECTION. + * In either case, a call to OEMCrypto_GetHDCPCapability() shall return the + * current HDCP level. + * + * #### Bypass Decrypt + * + * Platforms that wish to support Bypass Decrypt are still required to implement + * this function so that the decrypt path can be tested. It is acceptable for + * this function to invoke the bypass mechanism instead of calling into the + * OEMCrypto TA. For more information on Bypass Decrypt, see the + * [Bypass Decrypt](../../bypass) documentation. + * + * #### Non-Bypass Decrypt + * + * For platforms that do not need to support Bypass Decrypt, a mode compatible + * with previous versions of OEMCrypto is available. The "key handle" created by + * OEMCrypto_GetKeyHandle() is the session ID, as described above, and can be + * used the same as the session ID previously passed to OEMCrypto_DecryptCENC(). + * + * If the device is not bypassing, it must update the ODK clock values in this + * function call. If this is the first use of a key for this session, then + * OEMCrypto shall call ODK_AttemptFirstPlayback to update the session's clock + * values and verify playback is allowed. If this is not the first use of a key + * for this session, then OEMCrypto shall call ODK_UpdateLastPlaybackTime. See + * [ODK Clocks and Timers](../../odk-timers) for handling the return value of + * these ODK functions. + * + * @param[in] key_handle: pointer to a buffer containing the key handle for a + * key previously installed with OEMCrypto_GetKeyHandle(). + * @param[in] key_handle_length: length of the data in the key_handle buffer, in + * bytes. + * @param[in] samples: A caller-owned array of OEMCrypto_SampleDescription + * structures. Each entry in this array contains one sample of the content. + * @param[in] samples_length: The length of the array pointed to by the samples + * parameter. + * @param[in] pattern: A caller-owned structure indicating the encrypt/skip + * pattern as specified in the ISO-CENC standard. + * + * @retval OEMCrypto_SUCCESS + * @retval OEMCrypto_ERROR_NO_DEVICE_KEY + * @retval OEMCrypto_ERROR_INVALID_SESSION + * @retval OEMCrypto_ERROR_INVALID_CONTEXT + * @retval OEMCrypto_ERROR_DECRYPT_FAILED + * @retval OEMCrypto_ERROR_KEY_EXPIRED + * @retval OEMCrypto_ERROR_INSUFFICIENT_HDCP + * @retval OEMCrypto_ERROR_ANALOG_OUTPUT + * @retval OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE + * @retval OEMCrypto_ERROR_BUFFER_TOO_LARGE if the input buffer is too large, + * and should be partitioned. + * @retval OEMCrypto_ERROR_SHORT_BUFFER if the destination buffer is shorter + * than the source + * @retval OEMCrypto_ERROR_OUTPUT_TOO_LARGE + * @retval OEMCrypto_ERROR_SESSION_LOST_STATE + * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED + * @retval OEMCrypto_ERROR_INVALID_ENTITLED_KEY_SESSION + * @retval OEMCrypto_ERROR_UNSUPPORTED_CIPHER + * + * @buffer_size + * OEMCrypto shall support subsample sizes and total input buffer sizes as + * specified by its resource rating tier. + * OEMCrypto shall return OEMCrypto_ERROR_BUFFER_TOO_LARGE if the buffer is + * larger than the supported size. If OEMCrypto returns + * OEMCrypto_ERROR_BUFFER_TOO_LARGE, the CDM will break the buffer into + * smaller chunks. For high performance devices, OEMCrypto should handle + * larger buffers. We encourage OEMCrypto implementers not to artificially + * restrict the maximum buffer size. + * If OEMCrypto detects that the output data is too large, and breaking the + * buffer into smaller subsamples will not work, then it returns + * OEMCrypto_ERROR_OUTPUT_TOO_LARGE. This error will bubble up to the + * application, which can decide to skip the current frame of video or to + * switch to a lower resolution. + * + * @threading + * This is a "Session Function" and may be called simultaneously with session + * functions for other sessions but not simultaneously with other functions + * for the session containing the key. It will not be called simultaneously + * with initialization or usage table functions. It is as if the CDM holds a + * write lock for the key's session, and a read lock on the OEMCrypto system. + * + * The threading guarantees for this function are only guaranteed when the + * function is called through the Widevine CDM. If the platform uses Bypass + * Decrypt in a way that still calls this function, the OS may call this + * function in ways that violate these threading guarantees. + * + * @version + * This method changed in API version 18. This method changed its name in API + * version 11. + */ +OEMCryptoResult OEMCrypto_DecryptCENC( + const uint8_t* key_handle, size_t key_handle_length, + const OEMCrypto_SampleDescription* samples, // an array of samples. + size_t samples_length, // the number of samples. + const OEMCrypto_CENCEncryptPatternDesc* pattern); + +/** + * Copies the payload in the buffer referenced by the *data parameter into + * the buffer referenced by the out_buffer parameter. The data is simply + * copied. The definition of OEMCrypto_DestBufferDesc and subsample_flags are + * the same as in OEMCrypto_DecryptCENC(), above. + * + * The main difference between this and DecryptCENC is that this function may be + * used before a license is loaded into a session. In particular, an application + * will use this to copy the clear leader of a video to a secure buffer while + * the license request is being generated, sent to the server, and the response + * is being processed. This functionality is needed because an application may + * not have read or write access to a secure destination buffer. + * + * NOTES: + * + * This method may be called several times before the data is used. The first + * buffer in a chunk of data will have the OEMCrypto_FirstSubsample bit set + * in subsample_flags. The last buffer in a chunk of data will have the + * OEMCrypto_LastSubsample bit set in subsample_flags. The data will not be + * used until after OEMCrypto_LastSubsample has been set. If an + * implementation copies data immediately, it may ignore subsample_flags. + * + * If the destination buffer is secure, an offset may be specified. + * CopyBuffer begins storing data out_buffer->secure.offset bytes after the + * beginning of the secure buffer. + * + * @verification + * The following checks should be performed. + * 1. If either data or out_buffer is null, return + * OEMCrypto_ERROR_INVALID_CONTEXT. + * + * @param[in] session: crypto session identifier. + * @param[in] data_addr: An unaligned pointer to the buffer to be copied. + * @param[in] data_addr_length: The length of the buffer, in bytes. + * @param[in] out_buffer_descriptor: A caller-owned descriptor that specifies + * the handling of the byte stream. See OEMCrypto_DestBufferDesc for details. + * @param[in] subsample_flags: bitwise flags indicating if this is the first, + * middle, or last subsample in a chunk of data. 1 = first subsample, 2 = + * last subsample, 3 = both first and last subsample, 0 = neither first nor + * last subsample. + * + * @retval OEMCrypto_SUCCESS + * @retval OEMCrypto_ERROR_INVALID_CONTEXT + * @retval OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE + * @retval OEMCrypto_ERROR_BUFFER_TOO_LARGE if the input buffer is too large, + * and should be partitioned. + * @retval OEMCrypto_ERROR_SHORT_BUFFER if the destination buffer is shorter + * than the source + * @retval OEMCrypto_ERROR_OUTPUT_TOO_LARGE + * @retval OEMCrypto_ERROR_SESSION_LOST_STATE + * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED + * + * @buffer_size + * OEMCrypto shall support subsample sizes and sample sizes as specified in + * OEMCrypto_ResourceRatingTier(). This function will only be given a single + * sample. OEMCrypto shall return OEMCrypto_ERROR_BUFFER_TOO_LARGE if the + * buffer is larger than the supported size. If OEMCrypto returns + * OEMCrypto_ERROR_BUFFER_TOO_LARGE, the calling function must break the + * buffer into smaller chunks. For high performance devices, OEMCrypto should + * handle larger buffers. We encourage OEMCrypto implementers not to + * artificially restrict the maximum buffer size. If OEMCrypto detects that + * the output data is too large, and breaking the buffer into smaller + * subsamples will not work, then it returns + * OEMCrypto_ERROR_OUTPUT_TOO_LARGE. This error will bubble up to the + * application, which can decide to skip the current frame of video or to + * switch to a lower resolution. + * + * @threading + * This is a "Session Function" and may be called simultaneously with session + * functions for other sessions but not simultaneously with other functions + * for this session. It will not be called simultaneously with initialization + * or usage table functions. It is as if the CDM holds a write lock for this + * session, and a read lock on the OEMCrypto system. + * + * @version + * This method is changed in API version 15. + */ +OEMCryptoResult OEMCrypto_CopyBuffer( + OEMCrypto_SESSION session, const OEMCrypto_SharedMemory* data_addr, + size_t data_addr_length, + const OEMCrypto_DestBufferDesc* out_buffer_descriptor, + uint8_t subsample_flags); + +/** + * This function encrypts a generic buffer of data using the given key. + * + * OEMCrypto shall be able to handle buffers at least 100 KiB long. + * + * #### Bypass Decrypt + * + * Platforms that wish to support Bypass Decrypt are still required to implement + * this function. For more information on Bypass Decrypt, see the + * [Bypass Decrypt](../../bypass) documentation. + * + * #### Non-Bypass Decrypt + * + * For platforms that do not need to support Bypass Decrypt, a mode compatible + * with previous versions of OEMCrypto is available. The "key handle" created by + * OEMCrypto_GetKeyHandle() is the session ID, as described above, and can be + * used the same as the session ID previously passed to OEMCrypto_DecryptCENC(). + * + * If the device is not bypassing, it must update the ODK clock values in this + * function call. If this is the first use of a key for this session, then + * OEMCrypto shall call ODK_AttemptFirstPlayback to update the session's clock + * values and verify playback is allowed. If this is not the first use of a key + * for this session, then OEMCrypto shall call ODK_UpdateLastPlaybackTime. See + * [ODK Clocks and Timers](../../odk-timers) for handling the return value of + * these ODK functions. + * + * @verification + * The following checks should be performed. If any check fails, an error is + * returned, and the data is not encrypted. + * 1. The control bit for the key shall have the Allow_Encrypt set. If not, + * return OEMCrypto_ERROR_UNKNOWN_FAILURE. + * 2. If the key has an entry in the Usage Table, and the status of that + * entry is either kInactiveUsed or kInactiveUnused, then return the + * error OEMCrypto_ERROR_LICENSE_INACTIVE. + * + * @param[in] key_handle: pointer to a buffer containing the key handle for a + * key previously installed with OEMCrypto_GetKeyHandle(). + * @param[in] key_handle_length: length of the data in the key_handle buffer, in + * bytes. + * @param[in] in_buffer: pointer to memory containing data to be encrypted. + * @param[in] in_buffer_length: length of the buffer, in bytes. The algorithm + * may restrict in_buffer_length to be a multiple of block size. + * @param[in] iv: IV for encrypting data. Size is 128 bits. + * @param[in] algorithm: Specifies which encryption algorithm to use. + * Currently, only CBC 128 mode is allowed for encryption. + * @param[out] out_buffer: pointer to buffer in which encrypted data should be + * stored. + * + * @retval OEMCrypto_SUCCESS success + * @retval OEMCrypto_ERROR_KEY_EXPIRED + * @retval OEMCrypto_ERROR_NO_DEVICE_KEY + * @retval OEMCrypto_ERROR_INVALID_SESSION + * @retval OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE + * @retval OEMCrypto_ERROR_BUFFER_TOO_LARGE + * @retval OEMCrypto_ERROR_SESSION_LOST_STATE + * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED + * @retval OEMCrypto_ERROR_INVALID_ENTITLED_KEY_SESSION + * + * @buffer_size + * OEMCrypto shall support buffer sizes of at least 100 KiB for generic + * crypto operations. + * OEMCrypto shall return OEMCrypto_ERROR_BUFFER_TOO_LARGE if the buffer is + * larger than the supported size. + * + * @threading + * This is a "Session Function" and may be called simultaneously with session + * functions for other sessions but not simultaneously with other functions + * for the session containing the key. It will not be called simultaneously + * with initialization or usage table functions. It is as if the CDM holds a + * write lock for the key's session, and a read lock on the OEMCrypto system. + * + * @version + * This method changed in API version 18. + */ +OEMCryptoResult OEMCrypto_Generic_Encrypt( + const uint8_t* key_handle, size_t key_handle_length, + const OEMCrypto_SharedMemory* in_buffer, size_t in_buffer_length, + const uint8_t* iv, OEMCrypto_Algorithm algorithm, + OEMCrypto_SharedMemory* out_buffer); + +/** + * This function decrypts a generic buffer of data using the given key. + * + * OEMCrypto should be able to handle buffers at least 100 KiB long. + * + * #### Bypass Decrypt + * + * Platforms that wish to support Bypass Decrypt are still required to implement + * this function. For more information on Bypass Decrypt, see the + * [Bypass Decrypt](../../bypass) documentation. + * + * #### Non-Bypass Decrypt + * + * For platforms that do not need to support Bypass Decrypt, a mode compatible + * with previous versions of OEMCrypto is available. The "key handle" created by + * OEMCrypto_GetKeyHandle() is the session ID, as described above, and can be + * used the same as the session ID previously passed to OEMCrypto_DecryptCENC(). + * + * If the device is not bypassing, it must update the ODK clock values in this + * function call. If this is the first use of a key for this session, then + * OEMCrypto shall call ODK_AttemptFirstPlayback to update the session's clock + * values and verify playback is allowed. If this is not the first use of a key + * for this session, then OEMCrypto shall call ODK_UpdateLastPlaybackTime. See + * [ODK Clocks and Timers](../../odk-timers) for handling the return value of + * these ODK functions. + * + * @verification + * The following checks should be performed. If any check fails, an error is + * returned, and the data is not decrypted. + * 1. The control bit for the key shall have the Allow_Decrypt set. If not, + * return OEMCrypto_ERROR_DECRYPT_FAILED. + * 2. If the key's control block has the Data_Path_Type bit set, then return + * OEMCrypto_ERROR_DECRYPT_FAILED. + * 3. If the key has an entry in the Usage Table, and the status of that + * entry is either kInactiveUsed or kInactiveUnused, then return the + * error OEMCrypto_ERROR_LICENSE_INACTIVE. + * + * @param[in] key_handle: pointer to a buffer containing the key handle for a + * key previously installed with OEMCrypto_GetKeyHandle(). + * @param[in] key_handle_length: length of the data in the key_handle buffer, in + * bytes. + * @param[in] in_buffer: pointer to memory containing data to be encrypted. + * @param[in] in_buffer_length: length of the buffer, in bytes. The algorithm + * may restrict in_buffer_length to be a multiple of block size. + * @param[in] iv: IV for encrypting data. Size is 128 bits. + * @param[in] algorithm: Specifies which encryption algorithm to use. + * Currently, only CBC 128 mode is allowed for decryption. + * @param[out] out_buffer: pointer to buffer in which decrypted data should be + * stored. + * + * @retval OEMCrypto_SUCCESS success + * @retval OEMCrypto_ERROR_KEY_EXPIRED + * @retval OEMCrypto_ERROR_DECRYPT_FAILED + * @retval OEMCrypto_ERROR_NO_DEVICE_KEY + * @retval OEMCrypto_ERROR_INVALID_SESSION + * @retval OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE + * @retval OEMCrypto_ERROR_BUFFER_TOO_LARGE + * @retval OEMCrypto_ERROR_SESSION_LOST_STATE + * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED + * @retval OEMCrypto_ERROR_INVALID_ENTITLED_KEY_SESSION + * + * @buffer_size + * OEMCrypto shall support buffer sizes of at least 100 KiB for generic + * crypto operations. + * OEMCrypto shall return OEMCrypto_ERROR_BUFFER_TOO_LARGE if the buffer is + * larger than the supported size. + * + * @threading + * This is a "Session Function" and may be called simultaneously with session + * functions for other sessions but not simultaneously with other functions + * for the session containing the key. It will not be called simultaneously + * with initialization or usage table functions. It is as if the CDM holds a + * write lock for the key's session, and a read lock on the OEMCrypto system. + * + * @version + * This method changed in API version 18. + */ +OEMCryptoResult OEMCrypto_Generic_Decrypt( + const uint8_t* key_handle, size_t key_handle_length, + const OEMCrypto_SharedMemory* in_buffer, size_t in_buffer_length, + const uint8_t* iv, OEMCrypto_Algorithm algorithm, + OEMCrypto_SharedMemory* out_buffer); + +/** + * This function signs a generic buffer of data using the given key. + * + * #### Bypass Decrypt + * + * Platforms that wish to support Bypass Decrypt are still required to implement + * this function. For more information on Bypass Decrypt, see the + * [Bypass Decrypt](../../bypass) documentation. + * + * #### Non-Bypass Decrypt + * + * For platforms that do not need to support Bypass Decrypt, a mode compatible + * with previous versions of OEMCrypto is available. The "key handle" created by + * OEMCrypto_GetKeyHandle() is the session ID, as described above, and can be + * used the same as the session ID previously passed to OEMCrypto_DecryptCENC(). + * + * If the device is not bypassing, it must update the ODK clock values in this + * function call. If this is the first use of a key for this session, then + * OEMCrypto shall call ODK_AttemptFirstPlayback to update the session's clock + * values and verify playback is allowed. If this is not the first use of a key + * for this session, then OEMCrypto shall call ODK_UpdateLastPlaybackTime. See + * [ODK Clocks and Timers](../../odk-timers) for handling the return value of + * these ODK functions. + * + * @verification + * The following checks should be performed. If any check fails, an error is + * returned, and the data is not signed. + * 1. The control bit for the key shall have the Allow_Sign set. + * 2. If the key has an entry in the Usage Table, and the status of that + * entry is either kInactiveUsed or kInactiveUnused, then return the + * error OEMCrypto_ERROR_LICENSE_INACTIVE. + * + * @param[in] key_handle: pointer to a buffer containing the key handle for a + * key previously installed with OEMCrypto_GetKeyHandle(). + * @param[in] key_handle_length: length of the data in the key_handle buffer, in + * bytes. + * @param[in] buffer: pointer to memory containing data to be encrypted. + * @param[in] buffer_length: length of the buffer, in bytes. + * @param[in] algorithm: Specifies which algorithm to use. + * @param[out] signature: pointer to buffer in which signature should be + * stored. May be null on the first call in order to find required buffer + * size. + * @param[in,out] signature_length: (in) length of the signature buffer, in + * bytes. (out) actual length of the signature + * + * @retval OEMCrypto_SUCCESS success + * @retval OEMCrypto_ERROR_KEY_EXPIRED + * @retval OEMCrypto_ERROR_SHORT_BUFFER if signature buffer is not large enough + * to hold the output signature. + * @retval OEMCrypto_ERROR_NO_DEVICE_KEY + * @retval OEMCrypto_ERROR_INVALID_SESSION + * @retval OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE + * @retval OEMCrypto_ERROR_BUFFER_TOO_LARGE + * @retval OEMCrypto_ERROR_SESSION_LOST_STATE + * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED + * @retval OEMCrypto_ERROR_INVALID_ENTITLED_KEY_SESSION + * + * @buffer_size + * OEMCrypto shall support buffer sizes of at least 100 KiB for generic + * crypto operations. + * OEMCrypto shall return OEMCrypto_ERROR_BUFFER_TOO_LARGE if the buffer is + * larger than the supported size. + * + * @threading + * This is a "Session Function" and may be called simultaneously with session + * functions for other sessions but not simultaneously with other functions + * for the session containing the key. It will not be called simultaneously + * with initialization or usage table functions. It is as if the CDM holds a + * write lock for the key's session, and a read lock on the OEMCrypto system. + * + * @version + * This method changed in API version 18. + */ +OEMCryptoResult OEMCrypto_Generic_Sign(const uint8_t* key_handle, + size_t key_handle_length, + const OEMCrypto_SharedMemory* buffer, + size_t buffer_length, + OEMCrypto_Algorithm algorithm, + OEMCrypto_SharedMemory* signature, + size_t* signature_length); + +/** + * This function verifies the signature of a generic buffer of data using the + * given key. + * + * #### Bypass Decrypt + * + * Platforms that wish to support Bypass Decrypt are still required to implement + * this function. For more information on Bypass Decrypt, see the + * [Bypass Decrypt](../../bypass) documentation. + * + * #### Non-Bypass Decrypt + * + * For platforms that do not need to support Bypass Decrypt, a mode compatible + * with previous versions of OEMCrypto is available. The "key handle" created by + * OEMCrypto_GetKeyHandle() is the session ID, as described above, and can be + * used the same as the session ID previously passed to OEMCrypto_DecryptCENC(). + * + * If the device is not bypassing, it must update the ODK clock values in this + * function call. If this is the first use of a key for this session, then + * OEMCrypto shall call ODK_AttemptFirstPlayback to update the session's clock + * values and verify playback is allowed. If this is not the first use of a key + * for this session, then OEMCrypto shall call ODK_UpdateLastPlaybackTime. See + * [ODK Clocks and Timers](../../odk-timers) for handling the return value of + * these ODK functions. + * + * @verification + * The following checks should be performed. If any check fails, an error is + * returned. + * 1. The control bit for the key shall have the Allow_Verify set. + * 2. The signature of the message shall be computed, and the API shall + * verify the computed signature matches the signature passed in. If + * not, return OEMCrypto_ERROR_SIGNATURE_FAILURE. + * 3. The signature verification shall use a constant-time algorithm (a + * signature mismatch will always take the same time as a successful + * comparison). + * 4. If the key has an entry in the Usage Table, and the status of that + * entry is either kInactiveUsed or kInactiveUnused, then return the + * error OEMCrypto_ERROR_LICENSE_INACTIVE. + * + * @param[in] key_handle: pointer to a buffer containing the key handle for a + * key previously installed with OEMCrypto_GetKeyHandle(). + * @param[in] key_handle_length: length of the data in the key_handle buffer, in + * bytes. + * @param[in] buffer: pointer to memory containing data to be encrypted. + * @param[in] buffer_length: length of the buffer, in bytes. + * @param[in] algorithm: Specifies which algorithm to use. + * @param[in] signature: pointer to buffer in which signature resides. + * @param[in] signature_length: length of the signature buffer, in bytes. + * + * @retval OEMCrypto_SUCCESS success + * @retval OEMCrypto_ERROR_KEY_EXPIRED + * @retval OEMCrypto_ERROR_SIGNATURE_FAILURE + * @retval OEMCrypto_ERROR_NO_DEVICE_KEY + * @retval OEMCrypto_ERROR_INVALID_SESSION + * @retval OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE + * @retval OEMCrypto_ERROR_BUFFER_TOO_LARGE + * @retval OEMCrypto_ERROR_SESSION_LOST_STATE + * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED + * @retval OEMCrypto_ERROR_INVALID_ENTITLED_KEY_SESSION + * + * @buffer_size + * OEMCrypto shall support buffer sizes of at least 100 KiB for generic + * crypto operations. + * OEMCrypto shall return OEMCrypto_ERROR_BUFFER_TOO_LARGE if the buffer is + * larger than the supported size. + * + * @threading + * This is a "Session Function" and may be called simultaneously with session + * functions for other sessions but not simultaneously with other functions + * for the session containing the key. It will not be called simultaneously + * with initialization or usage table functions. It is as if the CDM holds a + * write lock for the key's session, and a read lock on the OEMCrypto system. + * + * @version + * This method changed in API version 18. + */ +OEMCryptoResult OEMCrypto_Generic_Verify( + const uint8_t* key_handle, size_t key_handle_length, + const OEMCrypto_SharedMemory* buffer, size_t buffer_length, + OEMCrypto_Algorithm algorithm, const OEMCrypto_SharedMemory* signature, + size_t signature_length); + +/// @} + +/// @addtogroup factory_provision +/// @{ + +/** + * A device should be provisioned at the factory with either an OEM + * Certificate or a keybox. We will call this data the root of trust. During + * manufacturing, the root of trust should be encrypted with the OEM root key + * and stored on the file system in a region that will not be erased during + * factory reset. This function may be used by legacy systems that use the + * two-step WrapKeyboxOrOEMCert()/InstallKeyboxOrOEMCert() approach. When the + * Widevine DRM plugin initializes, it will look for a wrapped root of trust + * in the file /factory/wv.keys and install it into the security processor by + * calling OEMCrypto_InstallKeyboxOrOEMCert(). + * + * ![OEMCrypto_WrapKeyboxOrOEMCert Operation](fig1.svg) + * + * OEMCrypto_WrapKeyboxOrOEMCert() is used to generate an OEM-encrypted root + * of trust that may be passed to OEMCrypto_InstallKeyboxOrOEMCert() for + * provisioning. The root of trust may be either passed in the clear or + * previously encrypted with a transport key. If a transport key is supplied, + * the keybox is first decrypted with the transport key before being wrapped + * with the OEM root key. This function is only needed if the root of trust + * provisioning method involves saving the keybox or OEM Certificate to the + * file system. + * + * @param[in] keybox_or_cert: pointer to root of trust data to encrypt -- this + * is either a keybox or an OEM Certificate private key. May be NULL on the + * first call to test the size of the wrapped keybox. The keybox may either + * be clear or previously encrypted. + * @param[in] keybox_or_cert_length: length the keybox or cert data in bytes + * @param[out] wrapped_keybox_or_cert: Pointer to wrapped keybox or cert + * @param[in,out] wrapped_keybox_or_cert_length: Pointer to the length of the + * wrapped keybox or certificate key in bytes + * @param[in] transport_key: Optional. AES transport key. If provided, the + * keybox_or_cert parameter was previously encrypted with this key. The + * keybox will be decrypted with the transport key using AES-CBC and a null + * IV. + * @param[in] transport_key_length: Optional. Number of bytes in the + * transport_key, if used. + * + * @retval OEMCrypto_SUCCESS success + * @retval OEMCrypto_ERROR_WRITE_KEYBOX failed to encrypt the keybox + * @retval OEMCrypto_ERROR_SHORT_BUFFER if keybox is provided as NULL, to + * determine the size of the wrapped keybox + * @retval OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED + * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED + * + * @threading + * This is an "Initialization and Termination Function" and will not be + * called simultaneously with any other function, as if the CDM holds a write + * lock on the OEMCrypto system. + * + * @version + * This method is supported in all API versions. + */ +OEMCryptoResult OEMCrypto_WrapKeyboxOrOEMCert( + const uint8_t* keybox_or_cert, size_t keybox_or_cert_length, + uint8_t* wrapped_keybox_or_cert, size_t* wrapped_keybox_or_cert_length, + const uint8_t* transport_key, size_t transport_key_length); + +/** + * Decrypts a wrapped root of trust and installs it in the security + * processor. The root of trust is unwrapped then encrypted with the OEM root + * key. This function is called from the Widevine DRM plugin at + * initialization time if there is no valid root of trust installed. It looks + * for wrapped data in the file /factory/wv.keys and if it is present, will + * read the file and call OEMCrypto_InstallKeyboxOrOEMCert() with the + * contents of the file. This function is only needed if the factory + * provisioning method involves saving the keybox or OEM Certificate to the + * file system. + * + * ![InstallKeyboxOrOEMCert](fig10.svg) + * + * @param[in] keybox_or_cert: pointer to encrypted data as input + * @param[in] keybox_or_cert_length: length of the data in bytes + * + * @retval OEMCrypto_SUCCESS success + * @retval OEMCrypto_ERROR_BAD_MAGIC + * @retval OEMCrypto_ERROR_BAD_CRC + * @retval OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED + * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED + * + * @threading + * This is an "Initialization and Termination Function" and will not be + * called simultaneously with any other function, as if the CDM holds a write + * lock on the OEMCrypto system. + * + * @version + * This method is supported in all API versions. + */ +OEMCryptoResult OEMCrypto_InstallKeyboxOrOEMCert(const uint8_t* keybox_or_cert, + size_t keybox_or_cert_length); + +/** + * Install a factory generated signature for the BCC. This is for devices that + * use Provisioning 4.0, with the signing option in the factory. With the + * signing option, the BCC is extracted from the device in the factory. Instead + * of being uploaded to the Widevine server, the BCC is signed by a certificate + * that the manufacturer shares with Widevine. The signature is then installed + * on the device is a secure location. The signature must not be erased during + * factory reset. + * + * This signature should be returned as `addition_signature` in a call to the + * function `OEMCrypto_GetBootCertificateChain()`. + * + * Devices that do not support Provisioning 4.0, or only support Provisioning + * 4.0 Option 1 should return OEMCrypto_ERROR_NOT_IMPLEMENTED. + * + * + * @param[in] signature: pointer to data as input + * @param[in] signature_length: length of the data in bytes + * + * @retval OEMCrypto_SUCCESS success + * @retval OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED + * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED + * + * @threading + * This is an "Initialization and Termination Function" and will not be + * called simultaneously with any other function, as if the CDM holds a write + * lock on the OEMCrypto system. + * + * @version + * This method is new in API version 18.3. + */ +OEMCryptoResult OEMCrypto_FactoryInstallBCCSignature(const uint8_t* signature, + size_t signature_length); + +/** + * This function is for OEMCrypto to tell the layer above what provisioning + * method it uses: keybox or OEM certificate. + * + * @retval OEMCrypto_DrmCertificate means the device has a DRM certificate built + * into the system. This cannot be used by level 1 devices. This + * provisioning method is deprecated and should not be used on new + * devices. OEMCertificate provisioning should be used instead. + * @retval OEMCrypto_Keybox means the device has a unique keybox. For level 1 + * devices this keybox must be securely installed by the device + * manufacturer. + * @retval OEMCrypto_OEMCertificate means the device has a factory installed OEM + * certificate. This is also called Provisioning 3.0. + * @retval OEMCrypto_ProvisioningError indicates a serious problem with the + * OEMCrypto library. + * + * @threading + * This is a "Property Function" and may be called simultaneously with any + * other property function or session function, but not any initialization or + * usage table function, as if the CDM holds a read lock on the OEMCrypto + * system. + * + * @version + * This method is new API version 12. + */ +OEMCrypto_ProvisioningMethod OEMCrypto_GetProvisioningMethod(void); + +/** + * If the device has a keybox, this validates the Widevine Keybox loaded into + * the security processor device. This method verifies two fields in the + * keybox: + * + * - Verify the MAGIC field contains a valid signature (such as, + * 'k''b''o''x'). + * - Compute the CRC using CRC-32-POSIX-1003.2 standard and compare the + * checksum to the CRC stored in the Keybox. + * The CRC is computed over the entire Keybox excluding the 4 bytes of the + * CRC (for example, Keybox[0..123]). For a description of the fields stored + * in the keybox, see Keybox Definition in the + * [Provisioning 2.0](../../index#prov20) section of the integration guide. + * + * If the device has an OEM Certificate, this validates the certificate + * private key. + * + * On devices that support OEMCrypto_GenerateOTARequest() and + * OEMCrypto_ProcessOTAKeybox(), this function may return + * OEMCrypto_ERROR_NEEDS_KEYBOX_PROVISIONING when a valid keybox is not present. + * + * @retval OEMCrypto_SUCCESS + * @retval OEMCrypto_ERROR_BAD_MAGIC + * @retval OEMCrypto_ERROR_BAD_CRC + * @retval OEMCrypto_ERROR_KEYBOX_INVALID + * @retval OEMCrypto_ERROR_INVALID_KEY + * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED + * @retval OEMCrypto_ERROR_NEEDS_KEYBOX_PROVISIONING + * + * @threading + * This is a "Property Function" and may be called simultaneously with any + * other property function or session function, but not any initialization or + * usage table function, as if the CDM holds a read lock on the OEMCrypto + * system. + * + * @version + * This method is supported in all API versions. + */ +OEMCryptoResult OEMCrypto_IsKeyboxOrOEMCertValid(void); + +/** + * Return a device unique id. For devices with a keybox, retrieve the + * DeviceID from the Keybox. For devices that have an OEM Certificate, or if + * provisioning 4 is used, it should set the device ID to a device-unique + * string, such as the device serial number or a hash of the device public key + * in boot certificate chain. The ID should be device-unique and it should be + * stable -- i.e. it should not change across a device reboot or a system + * upgrade. This shall match the device id found in the core provisioning + * request message. The maximum length of the device id is 64 bytes. The + * device ID field in a keybox is 32 bytes. + * + * @param[out] device_id: pointer to the buffer that receives the Device ID. + * @param[in,out] device_id_length - on input, size of the caller's device ID + * buffer. On output, the number of bytes written into the buffer. + * + * @retval OEMCrypto_SUCCESS success + * @retval OEMCrypto_ERROR_SHORT_BUFFER if the buffer is too small to return + * device ID + * @retval OEMCrypto_ERROR_NO_DEVICEID failed to return Device Id + * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED + * + * @threading + * This is a "Property Function" and may be called simultaneously with any + * other property function or session function, but not any initialization or + * usage table function, as if the CDM holds a read lock on the OEMCrypto + * system. + * + * @version + * This method is supported in all API versions. + */ +OEMCryptoResult OEMCrypto_GetDeviceID(uint8_t* device_id, + size_t* device_id_length); + +/// @} + +/// @addtogroup keybox +/// @{ + +/** + * Return the Key Data field from the Keybox. + * + * @param[out] key_data: pointer to the buffer to hold the Key Data field from + * the Keybox + * @param[in,out] key_data_length: on input, the allocated buffer size. On + * output, the number of bytes in Key Data + * + * @retval OEMCrypto_SUCCESS success + * @retval OEMCrypto_ERROR_SHORT_BUFFER if the buffer is too small to return + * KeyData + * @retval OEMCrypto_ERROR_NO_KEYDATA + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED: this function is for + * Provisioning 2.0 only. + * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED + * + * @threading + * This is a "Property Function" and may be called simultaneously with any + * other property function or session function, but not any initialization or + * usage table function, as if the CDM holds a read lock on the OEMCrypto + * system. + * + * @version + * This method is supported in all API versions. + */ +OEMCryptoResult OEMCrypto_GetKeyData(uint8_t* key_data, + size_t* key_data_length); + +/// @} + +/// @addtogroup test_verify +/// @{ +/** + * Temporarily use the specified test keybox until the next call to + * OEMCrypto_Terminate(). This allows a standard suite of unit tests to be run + * on a production device without permanently changing the keybox. Using the + * test keybox is not persistent. OEMCrypto cannot assume that this keybox is + * the same as previous keyboxes used for testing. + * + * Devices that use an OEM Certificate instead of a keybox (i.e. Provisioning + * 3.0) do not need to support this functionality, and may return + * OEMCrypto_ERROR_NOT_IMPLEMENTED. + * + * @param[in] buffer: pointer to memory containing test keybox, in binary form. + * @param[in] buffer_length: length of the buffer, in bytes. + * + * @retval OEMCrypto_SUCCESS success + * @retval OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED this function is for + * Provisioning 2.0 only. + * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED + * + * @threading + * This is an "Initialization and Termination Function" and will not be + * called simultaneously with any other function, as if the CDM holds a write + * lock on the OEMCrypto system. It is called after OEMCrypto_Initialize() and + * after OEMCrypto_GetProvisioningMethod() and only if the provisoining method + * is OEMCrypto_Keybox, + * + * @version + * This method changed in API version 14. + */ +OEMCryptoResult OEMCrypto_LoadTestKeybox(const uint8_t* buffer, + size_t buffer_length); + +/// @} + +/// @addtogroup oem_cert +/// @{ +/** + * After a call to this function, all session functions using an RSA key + * should use the OEM certificate's private RSA key. See the section + * discussing [Provisioning 3.0](../../index#prov30) section of the integration + * guide. + * + * @param[in] session: this function affects the specified session only. + * + * @retval OEMCrypto_SUCCESS + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED this function is for + * Provisioning 3.0 only. + * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED + * + * @threading + * This is a "Session Function" and may be called simultaneously with session + * functions for other sessions but not simultaneously with other functions + * for this session. It will not be called simultaneously with initialization + * or usage table functions. It is as if the CDM holds a write lock for this + * session, and a read lock on the OEMCrypto system. + * + * @version + * This method is new API version 16. + */ +OEMCryptoResult OEMCrypto_LoadOEMPrivateKey(OEMCrypto_SESSION session); + +/** + * This function should place the OEM public certificate in the buffer + * public_cert. See the section discussing + * [Provisioning 3.0](../../index#prov30) section of the integration guide. + * + * If the buffer is not large enough, OEMCrypto should update + * public_cert_length and return OEMCrypto_ERROR_SHORT_BUFFER. + * + * @param[out] public_cert: the buffer where the public certificate is stored. + * @param[in,out] public_cert_length: on input, this is the available size of + * the buffer. On output, this is the number of bytes needed for the + * certificate. + * + * @retval OEMCrypto_SUCCESS + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED this function is for + * Provisioning 3.0 only. + * @retval OEMCrypto_ERROR_SHORT_BUFFER + * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED + * + * @threading + * This is a "Property Function" and may be called simultaneously with any + * other property function or session function, but not any initialization or + * usage table function, as if the CDM holds a read lock on the OEMCrypto + * system. + * + * @version + * This method is new API version 16. + */ +OEMCryptoResult OEMCrypto_GetOEMPublicCertificate(uint8_t* public_cert, + size_t* public_cert_length); + +/// @} + +/// @addtogroup validation +/// @{ +/** Specifies OEMCrypto security level. + */ +typedef enum OEMCrypto_Security_Level { + OEMCrypto_Level_Unknown = 0, + OEMCrypto_Level1 = 1, + OEMCrypto_Level2 = 2, + OEMCrypto_Level3 = 3, +} OEMCrypto_Security_Level; + +/** + * This function returns the current API version number. The version number + * allows the calling application to avoid version mis-match errors, because + * this API is part of a shared library. + * + * There is a possibility that some API methods will be backwards compatible, + * or backwards compatible at a reduced security level. + * + * There is no plan to introduce forward-compatibility. Applications will + * reject a library with a newer version of the API. + * + * The version specified in this document is 16. Any OEM that returns this + * version number guarantees it passes all unit tests associated with this + * version. + * + * @return + * The supported API, as specified in the header file OEMCryptoCENC.h. + * + * @threading + * This is a "Property Function" and may be called simultaneously with any + * other property function or session function, but not any initialization or + * usage table function, as if the CDM holds a read lock on the OEMCrypto + * system. + * + * @version + * This method changed in each API version. + */ +uint32_t OEMCrypto_APIVersion(void); + +/** + * This function returns the current API minor version number. The version + * number allows the calling application to avoid version mis-match errors, + * because this API is part of a shared library. + * + * The minor version specified in this document is 2. Any OEM that returns + * this version number guarantees it passes all unit tests associated with + * this version. + * + * @return + * The supported API, as specified in the header file OEMCryptoCENC.h. + * + * @threading + * This is a "Property Function" and may be called simultaneously with any + * other property function or session function, but not any initialization or + * usage table function, as if the CDM holds a read lock on the OEMCrypto + * system. + * + * @version + * This method changed in each API version. + */ +uint32_t OEMCrypto_MinorAPIVersion(void); + +/** + * Stores the build information of the OEMCrypto library in a buffer. This + * string should be updated with each release or OEMCrypto build. + * + * It may be used for logging or bug tracking and may be bubbled up to the + * app so that it may track metrics on errors. + * + * The returned string must be JSON formatted. It shall also contain the + * following top level values [data types in brackets]: + * - "soc_vendor" [string]: SOC manufacturer name + * - "soc_model" [string]: SOC model name + * - "ta_ver" [string]: TA version in string format eg "1.12.3+tag", "2.0" + * - "uses_opk" [bool]: Whether TA was built with Widevine's OPK + * - "tee_os" [string]: Trusted OS intended to run the TA, eg "Trusty", "QSEE", + * "OP-TEE" + * - "tee_os_ver" [string]: Version of Trusted OS intended to run the TA + * - "is_debug" [bool]: Whether this is a debug build of the TA. Debug builds + * can enter Test Mode via OEMCrypto_EnterTestMode(), while production builds + * cannot. Debug builds are not released to the public. + * + * While not required, the following top level fields are recommended: + * - "implementer" [string]: Name of company or entity that provides OEMCrypto. + * Important if not SOC vendor. + * - "git_commit" [string]: Git commit hash of the code repository that + * produced the TA build. Useful for implementers to distinguish the state of + * different TA builds. + * - "build_timestamp" [string]: ISO 8601 formatted timestamp of the time the + * TA was compiled, eg "YYYY-MM-DDTHH:MM:SS" + * - "is_factory_mode" [bool]: Whether this was built with FACTORY_MODE_ONLY + * defined + * + * While not required, another optional top level struct can be added to the + * build information string to provide information about liboemcrypto.so: + * - "ree" { + * - "liboemcrypto_ver" [string]: liboemcrypto.so version in string format + * eg "2.15.0+tag". Note that this is separate from the "ta_ver" field + * above, since this section is specific to the liboemcrypto.so binary. + * - "git_commit" [string]: git hash of code that compiled liboemcrypto.so + * - "build_timestamp" [string]: ISO 8601 timestamp for when + * liboemcrypto.so was built + * } + * + * The JSON string can contain other values, structs, arrays, etc in addition to + * the above, if desired. + * + * If buffer_length is not enough, the function will return + * OEMCrypto_ERROR_SHORT_BUFFER. Before returning OEMCrypto_ERROR_SHORT_BUFFER, + * the function should set buffer_length to the length of buffer needed. If the + * write is successful, buffer_length will be set to the number of bytes + * written. + * + * The returned data shall be no larger than 1024 bytes. If the buffer length is + * larger, this function will return OEMCrypto_ERROR_BUFFER_TOO_LARGE and set + * |buffer_length| to 1024. + * + * @param[out] buffer: pointer to the buffer that receives build information + * @param[in,out] buffer_length: length of the data buffer in bytes + * + * @retval OEMCrypto_SUCCESS + * @retval OEMCrypto_ERROR_SHORT_BUFFER if the buffer is too small. + * @retval OEMCrypto_ERROR_BUFFER_TOO_LARGE if the buffer is too large. + * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE any other failure. + * + * @threading + * This is a "Property Function" and may be called simultaneously with any + * other property function or session function, but not any initialization or + * usage table function, as if the CDM holds a read lock on the OEMCrypto + * system. + * + * @version + * This method changed in each API version. + */ +OEMCryptoResult OEMCrypto_BuildInformation(char* buffer, size_t* buffer_length); + +/** + * This function returns the current patch level of the software running in + * the trusted environment. The patch level is defined by the OEM, and is + * only incremented when a security update has been added. + * + * See the section [Security Patch Level](../../index#security_patch_level) + * for more details. + * + * @return + * The OEM defined version number. + * + * @threading + * This is a "Property Function" and may be called simultaneously with any + * other property function or session function, but not any initialization or + * usage table function, as if the CDM holds a read lock on the OEMCrypto + * system. + * + * @version + * This method was introduced in API version 11. + */ +uint8_t OEMCrypto_Security_Patch_Level(void); + +/** + * Returns a string specifying the security level of the library. + * + * Since this function is spoofable, it is not relied on for security + * purposes. It is for information only. + * + * @return A security level enum. Values are OEMCrypto_Level_Unknown, + * OEMCrypto_Level1, OEMCrypto_Level2 and OEMCrypto_Level3. + * + * @threading + * This is a "Property Function" and may be called simultaneously with any + * other property function or session function, but not any initialization or + * usage table function, as if the CDM holds a read lock on the OEMCrypto + * system. + * + * @version + * This method changed in API version 17. + */ +OEMCrypto_Security_Level OEMCrypto_SecurityLevel(void); + +/** + * Returns the maximum HDCP version supported by the device, and the HDCP + * version supported by the device and any connected display. + * + * Valid values for HDCP_Capability are: + * + * The value 0xFF means the device is using a local, secure, data path + * instead of HDMI output. Notice that HDCP must use flag Type 1: all + * downstream devices will also use the same version or higher. + * + * The maximum HDCP level should be the maximum value that the device can + * enforce. For example, if the device has an HDCP 1.0 port and an HDCP 2.0 + * port, and the first port can be disabled, then the maximum is HDCP 2.0. If + * the first port cannot be disabled, then the maximum is HDCP 1.0. The + * maximum value can be used by the application or server to decide if a + * license may be used in the future. For example, a device may be connected + * to an external display while an offline license is downloaded, but the + * user intends to view the content on a local display. The user will want to + * download the higher quality content. + * + * The current HDCP level should be the level of HDCP currently negotiated + * with any connected receivers or repeaters either through HDMI or a + * supported wireless format. If multiple ports are connected, the current + * level should be the minimum HDCP level of all ports. If the key control + * block requires an HDCP level equal to or lower than the current HDCP + * level, the key is expected to be usable. If the key control block requires + * a higher HDCP level, the key is expected to be forbidden. + * + * When a key has version HDCP_V2_3 required in the key control block, the + * transmitter must have HDCP version 2.3 and have negotiated a connection + * with a version 2.2 or 2.3 receiver or repeater. The transmitter must + * configure the content stream to be Type 1. Since the transmitter cannot + * distinguish between 2.2 and 2.3 downstream receivers when connected to a + * repeater, it may transmit to both 2.2 and 2.3 receivers, but not 2.1 + * receivers. + * + * For example, if the transmitter is 2.3, and is connected to a receiver + * that supports 2.3 then the current level is HDCP_V2_3. If the transmitter + * is 2.3 and is connected to a 2.3 repeater, the current level is HDCP_V2_3 + * even though the repeater can negotiate a connection with a 2.2 downstream + * receiver for a Type 1 Content Stream. + * + * As another example, if the transmitter can support 2.3, but a receiver + * supports 2.0, then the current level is HDCP_V2. + * + * When a license requires HDCP, a device may use a wireless protocol to + * connect to a display only if that protocol supports the version of HDCP as + * required by the license. Both WirelessHD (formerly WiFi Display) and + * Miracast support HDCP. + * + * @param[out] current: this is the current HDCP version, based on the device + * itself, and the display to which it is connected. + * @param[out] maximum: this is the maximum supported HDCP version for the + * device, ignoring any attached device. + * + * @retval OEMCrypto_SUCCESS + * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE + * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED + * + * @threading + * This is a "Property Function" and may be called simultaneously with any + * other property function or session function, but not any initialization or + * usage table function, as if the CDM holds a read lock on the OEMCrypto + * system. + * + * @version + * This method changed in API version 10. + */ +OEMCryptoResult OEMCrypto_GetHDCPCapability(OEMCrypto_HDCP_Capability* current, + OEMCrypto_HDCP_Capability* maximum); + +/** + * Returns the DTCP2 support for a device. + * + * @param[out] capability: this will be set to 0 if DTCP2 is not supported, + * and 1 if the device supports at least v1 of DTCO2. + * + * @retval OEMCrypto_SUCCESS + * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE + * + * @threading + * This is a "Property Function" and may be called simultaneously with any + * other property function or session function, but not any initialization or + * usage table function, as if the CDM holds a read lock on the OEMCrypto + * system. + * + * @version + * This method is new in API version 17. + */ +OEMCryptoResult OEMCrypto_GetDTCP2Capability( + OEMCrypto_DTCP2_Capability* capability); + +/** + * This is used to determine if the device can support a usage table. Since + * this function is spoofable, it is not relied on for security purposes. It + * is for information only. The usage table is described in the section above. + * + * @return + * Returns true if the device can maintain a usage table. Returns false + * otherwise. + * + * @threading + * This is a "Property Function" and may be called simultaneously with any + * other property function or session function, but not any initialization or + * usage table function, as if the CDM holds a read lock on the OEMCrypto + * system. + * + * @version + * This method changed in API version 9. + */ +bool OEMCrypto_SupportsUsageTable(void); + +/** + * Estimates the maximum usage table size. If the device does not have a + * fixed size, this returns an estimate. A maximum size of 0 means the header + * is constrained only by dynamic memory allocation. + * + * Widevine requires the size to be at least 300 entries. + * + * @return + * Returns an estimate for the maximum size of the usage table header. + * + * @threading + * This is a "Property Function" and may be called simultaneously with any + * other property function or session function, but not any initialization or + * usage table function, as if the CDM holds a read lock on the OEMCrypto + * system. + * + * @version + * This method changed in API version 16. + */ +size_t OEMCrypto_MaximumUsageTableHeaderSize(void); + +/** + * Indicate whether there is hardware protection to detect and/or prevent the + * rollback of the usage table. For example, if the usage table contents is + * stored entirely on a secure file system that the user cannot read or write + * to. Another example is if the usage table has a generation number and the + * generation number is stored in secure memory that is not user accessible. + * + * @return Returns true if oemcrypto uses anti-rollback hardware. Returns false + * otherwise. + * + * @threading + * This is a "Property Function" and may be called simultaneously with any + * other property function or session function, but not any initialization or + * usage table function, as if the CDM holds a read lock on the OEMCrypto + * system. + * + * @version + * This method is new in API version 10. + */ +bool OEMCrypto_IsAntiRollbackHwPresent(void); + +/** + * Returns the current number of open sessions. The CDM and OEMCrypto + * consumers can query this value so they can use resources more effectively. + * + * @param[out] count: this is the current number of opened sessions. + * + * @retval OEMCrypto_SUCCESS + * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE + * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED + * + * @threading + * This is a "Property Function" and may be called simultaneously with any + * other property function or session function, but not any initialization or + * usage table function, as if the CDM holds a read lock on the OEMCrypto + * system. + * + * @version + * This method is new in API version 10. + */ +OEMCryptoResult OEMCrypto_GetNumberOfOpenSessions(size_t* count); + +/** + * Returns the maximum number of concurrent OEMCrypto sessions supported by + * the device. The CDM and OEMCrypto consumers can query this value so they + * can use resources more effectively. If the maximum number of sessions + * depends on a dynamically allocated shared resource, the returned value + * should be a best estimate of the maximum number of sessions. + * + * OEMCrypto shall support a minimum of 10 sessions. Some applications use + * multiple sessions to pre-fetch licenses, so high end devices should + * support more sessions -- we recommend a minimum of 50 sessions. + * + * @param[out] max: this is the max number of supported sessions. + * + * @retval OEMCrypto_SUCCESS + * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE + * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED + * + * @threading + * This is a "Property Function" and may be called simultaneously with any + * other property function or session function, but not any initialization or + * usage table function, as if the CDM holds a read lock on the OEMCrypto + * system. + * + * @version + * This method changed in API version 12. + */ +OEMCryptoResult OEMCrypto_GetMaxNumberOfSessions(size_t* max); + +/** + * Returns the type of certificates keys that this device supports. With very + * few exceptions, all devices should support at least 2048 bit RSA keys. + * High end devices should also support 3072 bit RSA keys. Devices that are + * cast receivers should also support RSA cast receiver certificates. + * + * Beginning with OEMCrypto v14, the provisioning server may deliver to the + * device an RSA key that uses the Carmichael totient. This does not change + * the RSA algorithm -- however the product of the private and public keys is + * not necessarily the Euler number \phi (n). OEMCrypto should not reject + * such keys. + * + * @return + * Returns the bitwise or of the following flags. It is likely that high end + * devices will support both 2048 and 3072 bit keys while the widevine + * servers transition to new key sizes. + * - 0x1 = OEMCrypto_Supports_RSA_2048bit - the device can load a DRM + * certificate with a 2048 bit RSA key. + * - 0x2 = OEMCrypto_Supports_RSA_3072bit - the device can load a DRM + * certificate with a 3072 bit RSA key. + * - 0x10 = OEMCrypto_Supports_RSA_CAST - the device can load a CAST + * certificate. These certificates are used with + * OEMCrypto_GenerateRSASignature() with padding type set to 0x2, PKCS1 + * with block type 1 padding. + * - 0x100 = OEMCrypto_Supports_ECC_secp256r1 - Elliptic Curve secp256r1 + * - 0x200 = OEMCrypto_Supports_ECC_secp384r1 - Elliptic Curve secp384r1 + * - 0x400 = OEMCrypto_Supports_ECC_secp521r1 - Elliptic Curve secp521r1 + * + * @threading + * This is a "Property Function" and may be called simultaneously with any + * other property function or session function, but not any initialization or + * usage table function, as if the CDM holds a read lock on the OEMCrypto + * system. + * + * @version + * This method changed in API version 16. + */ +uint32_t OEMCrypto_SupportedCertificates(void); + +/** + * Returns the version number of the current SRM file. If the device does not + * support SRM files, this will return OEMCrypto_ERROR_NOT_IMPLEMENTED. If + * the device only supports local displays, it would return + * OEMCrypto_LOCAL_DISPLAY_ONLY. If the device has an SRM, but cannot use + * OEMCrypto to update the SRM, then this function would set version to be + * the current version number, and return OEMCrypto_SUCCESS, but it would + * return false from OEMCrypto_IsSRMUpdateSupported(). + * + * @param[out] version: current SRM version number. + * + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED + * @retval OEMCrypto_SUCCESS + * @retval OEMCrypto_LOCAL_DISPLAY_ONLY to indicate version was not set, and + * is not needed. + * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED + * + * @threading + * This is a "Property Function" and may be called simultaneously with any + * other property function or session function, but not any initialization or + * usage table function, as if the CDM holds a read lock on the OEMCrypto + * system. + * + * @version + * This method changed in API version 13. + */ +OEMCryptoResult OEMCrypto_GetCurrentSRMVersion(uint16_t* version); + +/** + * Returns whether the device supports analog output or not. This information + * will be sent to the license server, and may be used to determine the type + * of license allowed. This function is for reporting only. It is paired with + * the key control block flags Disable_Analog_Output and CGMS. + * + * @return + * Returns a bitwise OR of all possible return values. + * * 0x0 = OEMCrypto_No_Analog_Output: the device has no analog output. + * * 0x1 = OEMCrypto_Supports_Analog_Output: the device does have analog + * output. + * * 0x2 = OEMCrypto_Can_Disable_Analog_Ouptput: the device does have + * analog output, but it will disable analog output if required by the + * key control block. + * * 0x4 = OEMCrypto_Supports_CGMS_A: the device supports signaling 2-bit + * CGMS-A, if required by the key control block + * + * @threading + * This is a "Property Function" and may be called simultaneously with any + * other property function or session function, but not any initialization or + * usage table function, as if the CDM holds a read lock on the OEMCrypto + * system. + * + * @version + * This method is new in API version 14. + */ +uint32_t OEMCrypto_GetAnalogOutputFlags(void); + +/** + * This function returns a positive number indicating which resource rating + * it supports. This value will bubble up to the application level as a + * property. This will allow applications to estimate what resolution and + * bandwidth the device is expected to support. + * + * OEMCrypto unit tests and Android GTS tests will verify that devices do + * support the resource values specified in the table below at the tier + * claimed by the device. If a device claims to be a low end device, the + * OEMCrypto unit tests will only verify the low end performance values. + * + * OEMCrypto implementers should consider the numbers in the table to be minimum + * values. + * + * These performance parameters are for OEMCrypto only. In particular, + * bandwidth and codec resolution are determined by the platform. + * + * See the document [Resource Rating] + * (https://developers.google.com/widevine/drm/feature/resource-rating) + * for more information and for the table of parameters. + * + * + * Here is an additional note on the number of subsamples: + * + * The table specifies the number of subsamples that partition the content when + * it is encrypted. However, if OEMCrypto_DecryptCENC() returns + * OEMCrypto_ERROR_BUFFER_TOO_LARGE, the layer above OEMCrypto will break the + * sample into more subsamples. + * + * The minimum subsample buffer size is the smallest buffer that the CDM layer + * above OEMCrypto will use when breaking a sample into subsamples. As mentioned + * above, the CDM layer will only break a sample into smaller subsamples if + * OEMCrypto returns OEMCrypto_ERROR_BUFFER_TOO_LARGE. Because this might be a + * performance problem, OEMCrypto implementers are encouraged to process larger + * subsamples and to process multiple subsamples in a single call to + * DecryptCENC. + * + * The message size limit applies to all functions that sign or verify a + * message: OEMCrypto_PrepAndSignLicenseRequest(), + * OEMCrypto_PrepAndSignRenewalRequest(), + * OEMCrypto_PrepAndSignProvisioningRequest(), and OEMCrypto_LoadLicense(). A + * request message is also used as the context buffer in + * OEMCrypto_DeriveKeysFromSessionKey() and OEMCrypto_GenerateDerivedKeys(). + * + * + * @return + * Returns an integer indicating which resource tier the device supports. + * + * @threading + * This is a "Property Function" and may be called simultaneously with any + * other property function or session function, but not any initialization or + * usage table function, as if the CDM holds a read lock on the OEMCrypto + * system. + * + * @version + * This method is new in API version 15. + */ +uint32_t OEMCrypto_ResourceRatingTier(void); + +/** + * Returns OEMCrypto_SUCCESS if the device is production ready. This is a + * new reporting mechanism that reports that OEMCrypto is production ready. + * For example, the SOC delivers OEMCrypto to the OEM which functions + * correctly whether debugging or antirollback is turned on or not. The OEM + * has the option to turn on TEE software antirollback if they wish. If anti + * rollback is off, or if debugging is enabled, then this function will + * return failure. + * + * The OEMCrypto implementer may choose any other error code if the device + * is not production ready. The motivation for this new feature is to allow + * SOCs to signal to OEMs that hardening has not been done on a system. + * During development of a device, it is fine for this function to return an + * error. During development, we expect devices to have debugging turned on. + * However, once the device is ready for production, all hardening should be + * turned on. + * + * The intention is that certification tests, such as Android’s GTS test suite, + * will verify that a device is production ready. Being production ready will + * not be a requirement to pass OEMCrypto unit tests, but the status will be + * logged as part of the tests. + * + * @retval OEMCrypto_SUCCESS + * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE + * + * @threading + * This is a "Property Function" and may be called simultaneously with any + * other property function or session function, but not any initialization or + * usage table function, as if the CDM holds a read lock on the OEMCrypto + * system. + * + * @version + * This method is new in API version 17. + */ +OEMCryptoResult OEMCrypto_ProductionReady(void); + +/** + * Returns OEMCrypto_WatermarkingAlwaysOn or OEMCrypto_WatermarkingConfigurable + * if the device supports watermarking. If the device does not support + * watermarking but the license has watermarking set to accept it, + * OEMCrypto_LoadLicense should return the error + * OEMCrypto_ERROR_INSUFFICIENT_PRIVILEGE, which is a new error code in v17. + * + * If watermarking can be turned on or off for individual streams, then + * OEMCrypto should honor the settings for each license individually. + * + * If watermarking can only be turned on or off on a system wide level, then + * the most recent license should be honored. The watermarking feature should + * be turned on or off when a license is loaded. If this conflicts with a + * license that had been loaded earlier, then keys from the earlier license may + * not be used. In this case, either OEMCrypto_GetKeyHandle or + * OEMCrypto_DecryptCENC will return OEMCrypto_ERROR_INSUFFICIENT_PRIVILEGE to + * indicate that the watermarking status has changed and the license is no + * longer usable. + * + * @retval OEMCrypto_WatermarkingError + * @retval OEMCrypto_WatermarkingNotSupported + * @retval OEMCrypto_WatermarkingConfigurable + * @retval OEMCrypto_WatermarkingAlwaysOn + * + * @threading + * This is a "Property Function" and may be called simultaneously with any + * other property function or session function, but not any initialization or + * usage table function, as if the CDM holds a read lock on the OEMCrypto + * system. + * + * @version + * This method is new in API version 17. + */ +OEMCrypto_WatermarkingSupport OEMCrypto_GetWatermarkingSupport(void); + +/** + * Queries the hash algorithm that the device will use when performing + * RSASSA-PSS or ECDSA with the private key currently loaded in the given + * session. + * + * For RSA keys, SHA-1 was used for all OEMCrypto versions prior to 18, but + * SHA-256 is strongly recommended for all devices. SHA-384 and SHA-512 are not + * supported with RSA keys. + * + * For ECC keys, the algorithm chosen depends on the curve used to generate the + * key, as outlined in the OEMCrypto Integration Guide. SHA-1 is not supported + * with ECC keys. + * + * For devices that do not support ECC, it is acceptable for this function to + * return a hardcoded value, since the answer does not depend on the currently + * loaded private key. + * + * @param[in] session: crypto session identifier. + * @param[out] algorithm: the algorithm the device will use. + * + * @retval OEMCrypto_SUCCESS + * @retval OEMCrypto_ERROR_INVALID_SESSION + * @retval OEMCrypto_ERROR_INVALID_CONTEXT + * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE + * + * @threading + * This is a "Session Function" and may be called simultaneously with session + * functions for other sessions but not simultaneously with other functions + * for this session. It will not be called simultaneously with initialization + * or usage table functions. It is as if the CDM holds a write lock for this + * session, and a read lock on the OEMCrypto system. + * + * @version + * This method is new in API version 18. + */ +OEMCryptoResult OEMCrypto_GetSignatureHashAlgorithm( + OEMCrypto_SESSION session, OEMCrypto_SignatureHashAlgorithm* algorithm); + +/// @} + +/// @addtogroup drm_cert +/// @{ + +/** + * Load and parse a provisioning response, and then rewrap the private key + * for storage on the filesystem. We recommend that the OEM use an encryption + * key and signing key generated using an algorithm at least as strong as + * that in GenerateDerivedKeys. + * + * First, OEMCrypto shall verify the signature of the message using the correct + * algorithm depending on if the device supports Provisioning 2.0, 3.0 or 4.0. + * + * For Provisioning 2.0, OEMCrypto shall verify the signature of the message + * using HMAC-SHA256 with the derived mac_key[server]. The signature + * verification shall use a constant-time algorithm (a signature mismatch will + * always take the same time as a successful comparison). The signature is over + * the entire message buffer starting at message with length message_length. If + * the signature verification fails, ignore all other arguments and return + * OEMCrypto_ERROR_SIGNATURE_FAILURE. + * + * NOTE: The calling software must have previously established the mac_keys + * and encrypt_key with a call to OEMCrypto_DeriveKeysFromSessionKey() or + * OEMCrypto_GenerateDerivedKeys(). + * + * For Provisioning 3.0 and 4.0, the signature is not verified. + * + * After the signature is verified, + * the function ODK_ParseProvisioning is called to parse the message. If it + * returns an error, OEMCrypto shall return that error to the CDM layer. The + * function ODK_ParseProvisioning is described in the document "Widevine Core + * Message Serialization". + * + * Below, all fields are found in the struct ODK_ParsedLicense parsed_license + * returned by ODK_ParsedProvisioning. + * + * After decrypting `parsed_response->enc_private_key`, If the first four bytes + * of the buffer are the string "SIGN", then the actual RSA key begins on the + * 9th byte of the buffer. The second four bytes of the buffer is the 32 bit + * field "allowed_schemes" of type RSA_Padding_Scheme, which is used in + * OEMCrypto_GenerateRSASignature(). The value of allowed_schemes must also be + * wrapped with RSA key. We recommend storing the magic string "SIGN" with + * the key to distinguish keys that have a value for allowed_schemes from + * those that should use the default allowed_schemes. Devices that do not + * support the alternative signing algorithms may refuse to load these keys + * and return an error of OEMCrypto_ERROR_NOT_IMPLEMENTED. The main use case + * for these alternative signing algorithms is to support devices that use + * X509 certificates for authentication when acting as a ChromeCast receiver. + * This is not needed for devices that wish to send data to a ChromeCast. + * + * If the first four bytes of the buffer `enc_private_key` are not the string + * "SIGN", then this key may not be used with OEMCrypto_GenerateRSASignature(). + * + * Verification and Algorithm: + * The following checks should be performed. If any check fails, an error is + * returned, and the key is not loaded. + * 1. Check that all the pointer values passed into it are within the + * buffer specified by message and message_length. + * 2. Verify that (in) wrapped_private_key_length is large enough to hold + * the rewrapped key, returning OEMCrypto_ERROR_SHORT_BUFFER otherwise. + * 3. Verify the message signature, using the derived signing key + * (mac_key[server]) from a previous call to + * OEMCrypto_GenerateDerivedKeys() or OEMCrypto_DeriveKeysFromSessionKey(). + * 4. The function ODK_ParseProvisioning is called to parse the message. + * 5. Decrypt enc_private_key in the buffer private_key using the session's + * derived encryption key (enc_key). Use enc_private_key_iv as the initial + * vector for AES_128-CBC mode, with PKCS#5 padding. The private_key should + * be kept in secure memory and protected from the user. + * 6. If the first four bytes of the buffer private_key are the string "SIGN", + * then the actual RSA key begins on the 9th byte of the buffer. The + * second four bytes of the buffer is the 32 bit field + * "allowed_schemes", of type RSA_Padding_Scheme, which is used in + * OEMCrypto_GenerateRSASignature(). The value of allowed_schemes must + * also be wrapped with RSA key. We recommend storing the magic string + * "SIGN" with the key to distinguish keys that have a value for + * allowed_schemes from those that should use the default + * allowed_schemes. Devices that do not support the alternative signing + * algorithms may refuse to load these keys and return an error of + * OEMCrypto_ERROR_NOT_IMPLEMENTED. The main use case for these + * alternative signing algorithms is to support devices that use X.509 + * certificates for authentication when acting as a ChromeCast receiver. + * This is not needed for devices that wish to send data to a ChromeCast. + * 7. If the first four bytes of the buffer private_key are not the string + * "SIGN", this key may not be used with OEMCrypto_GenerateRSASignature(). + * 8. After possibly skipping past the first 8 bytes signifying the allowed + * signing algorithm, the rest of the buffer private_key contains an ECC + * private key or an RSA private key in PKCS#8 binary DER encoded + * format. The OEMCrypto library shall verify that this private key is + * valid. + * 9. Re-encrypt the device private key with an internal key (such as one + * derived by the OEM key or Widevine Keybox key) and the generated IV + * using AES-128-CBC with PKCS#5 padding. The data should also be + * signed. This algorithm is just a suggestion. The implementer may use any + * suitable encrypting and validation algorithm with a key that ties it to + * the device. + * 10. Copy the rewrapped key to the buffer specified by wrapped_private_key + * and the size of the wrapped key to wrapped_private_key_length. + * + * @param[in] session: crypto session identifier. + * @param[in] message: pointer to memory containing data. + * @param[in] message_length: length of the message, in bytes. + * @param[in] core_message_length: length of the core submessage, in bytes. + * @param[in] signature: pointer to memory containing the signature. + * @param[in] signature_length: length of the signature, in bytes. + * @param[out] wrapped_private_key: pointer to buffer in which encrypted RSA or + * ECC private key should be stored. May be null on the first call in order + * to find required buffer size. + * @param[in,out] wrapped_private_key_length: (in) length of the encrypted + * private key, in bytes. (out) actual length of the encrypted private key + * + * @retval OEMCrypto_SUCCESS success + * @retval OEMCrypto_ERROR_NO_DEVICE_KEY + * @retval OEMCrypto_ERROR_INVALID_SESSION + * @retval OEMCrypto_ERROR_INVALID_KEY + * @retval OEMCrypto_ERROR_SIGNATURE_FAILURE + * @retval OEMCrypto_ERROR_INVALID_NONCE + * @retval OEMCrypto_ERROR_SHORT_BUFFER + * @retval OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE + * @retval OEMCrypto_ERROR_BUFFER_TOO_LARGE + * @retval OEMCrypto_ERROR_SESSION_LOST_STATE + * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED + * + * @buffer_size + * OEMCrypto shall support message sizes as described in the section + * OEMCrypto_ResourceRatingTier(). + * OEMCrypto shall return OEMCrypto_ERROR_BUFFER_TOO_LARGE if the buffer is + * larger than the supported size. + * + * @threading + * This is a "Session Function" and may be called simultaneously with session + * functions for other sessions but not simultaneously with other functions + * for this session. It will not be called simultaneously with initialization + * or usage table functions. It is as if the CDM holds a write lock for this + * session, and a read lock on the OEMCrypto system. + * + * @version + * This method changed in API version 16. + */ +OEMCryptoResult OEMCrypto_LoadProvisioning( + OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, + size_t core_message_length, const uint8_t* signature, + size_t signature_length, uint8_t* wrapped_private_key, + size_t* wrapped_private_key_length); + +/** + * Loads a wrapped RSA or ECC private key to secure memory for use by this + * session in future calls to OEMCrypto_PrepAndSignLicenseRequest() or + * OEMCrypto_DeriveKeysFromSessionKey(). The wrapped private key will be the + * one verified and wrapped by OEMCrypto_LoadProvisioning(). The private key + * should be stored in secure memory. + * + * If the bit field "allowed_schemes" was wrapped with this RSA key, its + * value will be loaded and stored with the RSA key, and the key may be used + * with calls to OEMCrypto_GenerateRSASignature(). If there was not a bit field + * wrapped with the RSA key, the key will be used for + * OEMCrypto_PrepAndSignLicenseRequest() or OEMCrypto_DeriveKeysFromSessionKey() + * + * @verification + * The following checks should be performed. If any check fails, an error is + * returned, and the RSA key is not loaded. + * 1. The wrapped key has a valid signature, as described in + * OEMCrypto_LoadProvisioning(). + * 2. The decrypted key is a valid private RSA key. + * 3. If a value for allowed_schemes is included with the key, it is a + * valid value. + * + * @param[in] session: crypto session identifier. + * @param[in] key_type: indicates either an RSA or ECC key for devices that + * support both. + * @param[in] wrapped_private_key: wrapped device private key (RSA or ECC). + * This is the wrapped key generated by OEMCrypto_LoadProvisioning(). + * @param[in] wrapped_private_key_length: length of the wrapped key buffer, in + * bytes. + * + * @retval OEMCrypto_SUCCESS success + * @retval OEMCrypto_ERROR_NO_DEVICE_KEY + * @retval OEMCrypto_ERROR_INVALID_SESSION + * @retval OEMCrypto_ERROR_INVALID_KEY + * @retval OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE + * @retval OEMCrypto_ERROR_SESSION_LOST_STATE + * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED + * + * @threading + * This is a "Session Function" and may be called simultaneously with session + * functions for other sessions but not simultaneously with other functions + * for this session. It will not be called simultaneously with initialization + * or usage table functions. It is as if the CDM holds a write lock for this + * session, and a read lock on the OEMCrypto system. + * + * @version + * This method changed in API version 16. + */ +OEMCryptoResult OEMCrypto_LoadDRMPrivateKey(OEMCrypto_SESSION session, + OEMCrypto_PrivateKeyType key_type, + const uint8_t* wrapped_private_key, + size_t wrapped_private_key_length); + +/** + * Some platforms do not support keyboxes or OEM Certificates. On those + * platforms, there is a DRM certificate baked into the OEMCrypto library. + * This is unusual, and is only available for L3 devices. In order to debug + * and test those devices, they should be able to switch to the test DRM + * certificate. + * + * Temporarily use the standard test RSA key until the next call to + * OEMCrypto_Terminate(). This allows a standard suite of unit tests to be run + * on a production device without permanently changing the key. Using the + * test key is not persistent. + * + * The test key can be found in the OEMCrypto unit test, in PKCS8 form as the + * constant kTestRSAPKCS8PrivateKeyInfo2_2048. + * + * @retval OEMCrypto_SUCCESS success + * @retval OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED devices that use a keybox should + * not implement this function + * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED + * + * @threading + * This is an "Initialization and Termination Function" and will not be + * called simultaneously with any other function, as if the CDM holds a write + * lock on the OEMCrypto system. + * + * @version + * This method is new in API version 10. + */ +OEMCryptoResult OEMCrypto_LoadTestRSAKey(void); + +/** + * The OEMCrypto_GenerateRSASignature() method is only used for devices that are + * CAST receivers. This function is called after OEMCrypto_LoadDRMPrivateKey() + * for the same session. + * + * The parameter padding_scheme has two possible legacy values: + * + * 0x1 - RSASSA-PSS with SHA1. + * + * 0x2 - PKCS1 with block type 1 padding (only). + * + * The only supported padding scheme is 0x2 since version 16 of this API. In + * this second case, the "message" is already a digest, so no further hashing + * is applied, and the message_length can be no longer than 83 bytes. If the + * message_length is greater than 83 bytes OEMCrypto_ERROR_SIGNATURE_FAILURE + * shall be returned. + * + * The second padding scheme is for devices that use X509 certificates for + * authentication. The main example is devices that work as a Cast receiver, + * like a ChromeCast, not for devices that wish to send to the Cast device, + * such as almost all Android devices. OEMs that do not support X509 + * certificate authentication need not implement this function and can return + * OEMCrypto_ERROR_NOT_IMPLEMENTED. + * + * @verification + * Both the padding_scheme and the RSA key's allowed_schemes must be 0x2. If + * not, then the signature is not computed and the error + * OEMCrypto_ERROR_INVALID_KEY is returned. + * + * @param[in] session: crypto session identifier. + * @param[in] message: pointer to memory containing message to be signed. + * @param[in] message_length: length of the message, in bytes. + * @param[out] signature: buffer to hold the message signature. On return, it + * will contain the message signature generated with the device private RSA + * key using RSASSA-PSS. Will be null on the first call in order to find + * required buffer size. + * @param[in,out] signature_length: (in) length of the signature buffer, in + * bytes. (out) actual length of the signature + * @param[in] padding_scheme: specify which scheme to use for the signature. + * + * @retval OEMCrypto_SUCCESS success + * @retval OEMCrypto_ERROR_SHORT_BUFFER if the signature buffer is too small. + * @retval OEMCrypto_ERROR_INVALID_SESSION + * @retval OEMCrypto_ERROR_INVALID_CONTEXT + * @retval OEMCrypto_ERROR_INVALID_KEY + * @retval OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED if algorithm > 0, and the device + * does not support that algorithm. + * @retval OEMCrypto_ERROR_BUFFER_TOO_LARGE + * @retval OEMCrypto_ERROR_SESSION_LOST_STATE + * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED + * + * @buffer_size + * OEMCrypto shall support message sizes as described in the section + * OEMCrypto_ResourceRatingTier(). + * OEMCrypto shall return OEMCrypto_ERROR_BUFFER_TOO_LARGE if the buffer is + * larger than the supported size. + * + * @threading + * This is a "Session Function" and may be called simultaneously with session + * functions for other sessions but not simultaneously with other functions + * for this session. It will not be called simultaneously with initialization + * or usage table functions. It is as if the CDM holds a write lock for this + * session, and a read lock on the OEMCrypto system. + * + * @version + * This method changed in API version 16. + */ +OEMCryptoResult OEMCrypto_GenerateRSASignature( + OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, + uint8_t* signature, size_t* signature_length, + RSA_Padding_Scheme padding_scheme); + +/** + * OEMCrypto will use ODK_PrepareCoreProvisioningRequest() or + * ODK_PrepareCoreProvisioning40Request(), as described in the document + * "Widevine Core Message Serialization", to prepare the core message. + * ODK_PrepareCoreProvisioningRequest() for Provisioning 2 or 3, and + * ODK_PrepareCoreProvisioning40Request() for Provisioning 4. If the ODK + * function returns an error, the error should be returned by OEMCrypto to the + * CDM layer. If it returns OEMCrypto_SUCCESS, then OEMCrypto shall compute the + * signature of the entire message. The entire message is the buffer starting at + * message with length message_length. + * + * For a device that has a keybox, i.e. Provisioning 2.0, OEMCrypto will sign + * the request with the session's derived client mac key from the previous + * call to OEMCrypto_GenerateDerivedKeys(). + * + * For Provisioning 3.0, i.e. a device that has a baked in OEM Certificate, + * OEMCrypto will sign the request with the private key associated with the OEM + * Certificate. The key shall have been loaded by a previous call to + * OEMCrypto_LoadDRMPrivateKey(). + * + * For Provisioning 4.0, i.e. a device that uses a Boot Chain Certificate to + * request and OEM cert, a request for an OEM cert is signed by the OEM private + * key. A request for a DRM cert is signed by the DRM private key. The DRM cert + * that was generated on the device in OEMCrypto_GenerateCertificateKeyPair() is + * signed by the OEM cert private key. + * + * Refer to the Signing Messages Sent to a Server section above for more + * details. + * + * NOTE: if signature pointer is null and/or input signature_length is zero, + * this function returns OEMCrypto_ERROR_SHORT_BUFFER and sets output + * signature_length to the size needed to receive the output signature. + * + * @param[in] session: handle for the session to be used. + * @param[in,out] message: Pointer to memory for the entire message. Modified by + * OEMCrypto via the ODK library. + * @param[in] message_length: length of the entire message buffer. + * @param[in,out] core_message_size: length of the core message at the beginning + * of the message. (in) size of buffer reserved for the core message, in + * bytes. (out) actual length of the core message, in bytes. + * @param[out] signature: pointer to memory to receive the computed signature. + * @param[in,out] signature_length: (in) length of the signature buffer, in + * bytes. (out) actual length of the signature, in bytes. + * + * @retval OEMCrypto_SUCCESS success + * @retval OEMCrypto_ERROR_INVALID_SESSION + * @retval OEMCrypto_ERROR_SHORT_BUFFER if signature buffer is not large enough + * to hold the signature. + * @retval OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE + * @retval OEMCrypto_ERROR_BUFFER_TOO_LARGE + * @retval OEMCrypto_ERROR_SESSION_LOST_STATE + * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED + * + * @buffer_size + * OEMCrypto shall support message sizes as described in the section + * OEMCrypto_ResourceRatingTier(). + * OEMCrypto shall return OEMCrypto_ERROR_BUFFER_TOO_LARGE if the buffer is + * larger than the supported size. + * + * @threading + * This is a "Session Function" and may be called simultaneously with session + * functions for other sessions but not simultaneously with other functions + * for this session. It will not be called simultaneously with initialization + * or usage table functions. It is as if the CDM holds a write lock for this + * session, and a read lock on the OEMCrypto system. + * + * @version + * This method changed in API version 16. + */ +OEMCryptoResult OEMCrypto_PrepAndSignProvisioningRequest( + OEMCrypto_SESSION session, uint8_t* message, size_t message_length, + size_t* core_message_size, uint8_t* signature, size_t* signature_length); + +/// @} + +/// @addtogroup usage_table +/// @{ + +/** + * This creates a new Usage Table Header with no entries. If there is already + * a generation number stored in secure storage, it will be incremented by 1 + * and used as the new Master Generation Number. This will only be called if + * the CDM layer finds no existing usage table on the file system. OEMCrypto + * will encrypt and sign the new, empty, header and return it in the provided + * buffer. + * + * The new entry should be created with a status of kUnused and all times + * times should be set to 0. + * + * Devices that do not implement a Session Usage Table may return + * OEMCrypto_ERROR_NOT_IMPLEMENTED. + * + * @param[out] header_buffer: pointer to memory where encrypted usage table + * header is written. + * @param[in,out] header_buffer_length: (in) length of the header_buffer, in + * bytes. (out) actual length of the header_buffer + * + * @retval OEMCrypto_SUCCESS success + * @retval OEMCrypto_ERROR_SHORT_BUFFER if header_buffer_length is too small + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED + * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE if any active entries are currently + * loaded + * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED + * + * @threading + * This is a "Usage Table Function" and will not be called simultaneously + * with any other function, as if the CDM holds a write lock on the OEMCrypto + * system. + * + * @version + * This method changed in API version 13. + */ +OEMCryptoResult OEMCrypto_CreateUsageTableHeader(uint8_t* header_buffer, + size_t* header_buffer_length); + +/** + * This loads the Usage Table Header. The buffer's signature is verified and + * the buffer is decrypted. OEMCrypto will verify the verification string. If + * the Master Generation Number is more than 1 off, the table is considered + * bad, the headers are NOT loaded, and the error + * OEMCrypto_ERROR_GENERATION_SKEW is returned. If the generation number is + * off by 1, the warning OEMCrypto_WARNING_GENERATION_SKEW is returned but + * the header is still loaded. This warning may be logged by the CDM layer. + * + * @param[in] buffer: pointer to memory containing encrypted usage table header. + * @param[in] buffer_length: length of the buffer, in bytes. + * + * @retval OEMCrypto_SUCCESS success + * @retval OEMCrypto_ERROR_SHORT_BUFFER + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED some devices do not implement usage + * tables. + * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE + * @retval OEMCrypto_WARNING_GENERATION_SKEW if the generation number is off + * by exactly 1. + * @retval OEMCrypto_ERROR_GENERATION_SKEW if the generation number is off by + * more than 1. + * @retval OEMCrypto_ERROR_SIGNATURE_FAILURE if the signature failed. + * @retval OEMCrypto_ERROR_BAD_MAGIC verification string does not match. + * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED + * + * @threading + * This is a "Usage Table Function" and will not be called simultaneously + * with any other function, as if the CDM holds a write lock on the OEMCrypto + * system. + * + * @version + * This method changed in API version 16. + */ +OEMCryptoResult OEMCrypto_LoadUsageTableHeader(const uint8_t* buffer, + size_t buffer_length); + +/** + * This creates a new usage entry. The size of the header will be increased + * by 8 bytes, and secure volatile memory will be allocated for it. The new + * entry will be associated with the given session. The status of the new + * entry will be set to "unused". OEMCrypto will set *usage_entry_number to + * be the index of the new entry. The first entry created will have index 0. + * The new entry will be initialized with a generation number equal to the + * master generation number, which will also be stored in the header's new + * slot. Then the master generation number will be incremented. Since each + * entry's generation number is less than the master generation number, the + * new entry will have a generation number that is larger than all other + * entries and larger than all previously deleted entries. This helps prevent + * a rogue application from deleting an entry and then loading an old version + * of it. + * + * If the session already has a usage entry associated with it, the error + * OEMCrypto_ERROR_MULTIPLE_USAGE_ENTRIES is returned. It is an error to attempt + * to create or load a second usage entry into a session that already has a + * usage entry. + * + * @param[in] session: handle for the session to be used. + * @param[out] usage_entry_number: index of new usage entry. + * + * @retval OEMCrypto_SUCCESS success + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED some devices do not implement usage + * tables. + * @retval OEMCrypto_ERROR_INSUFFICIENT_RESOURCES if there is no room in + * memory to increase the size of the usage table header. The CDM layer + * can delete some entries and then try again, or it can pass the error + * up to the application. + * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE + * @retval OEMCrypto_ERROR_SESSION_LOST_STATE + * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED + * @retval OEMCrypto_ERROR_MULTIPLE_USAGE_ENTRIES if there already is a usage + * entry loaded into this session + * + * @threading + * This is a "Usage Table Function" and will not be called simultaneously + * with any other function, as if the CDM holds a write lock on the OEMCrypto + * system. + * + * @version + * This method changed in API version 13. + */ +OEMCryptoResult OEMCrypto_CreateNewUsageEntry(OEMCrypto_SESSION session, + uint32_t* usage_entry_number); + +/** + * This allows a session to take an existing usage entry. The effect of this + * call is identical to that of creating an entry via + * OEMCrypto_CreateUsageEntry(), except that the usage table header does not + * change size. All information related to the previous entry should be cleared + * from the header. The new entry will be initialized with a generation number + * equal to the master generation number, which will also be stored in the + * header’s existing slot. Then the master generation number will be + * incremented. + * + * If the session already has a usage entry associated with it, the error + * OEMCrypto_ERROR_MULTIPLE_USAGE_ENTRIES is returned. + * + * @param[in] session: handle for the session to be used. + * @param[in] usage_entry_number: index of new usage entry. + * + * @retval OEMCrypto_SUCCESS success + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED some devices do not implement usage + * tables. + * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE + * @retval OEMCrypto_ERROR_SESSION_LOST_STATE + * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED + * @retval OEMCrypto_ERROR_MULTIPLE_USAGE_ENTRIES if there already is a usage + * entry loaded into this session + * @retval OEMCrypto_ERROR_INVALID_SESSION when entry number is in use by + * another session + * + * @threading + * This is a "Usage Table Function" and will not be called simultaneously + * with any other function, as if the CDM holds a write lock on the OEMCrypto + * system. + * + * @version + * This method changed in API version 17. + */ +OEMCryptoResult OEMCrypto_ReuseUsageEntry(OEMCrypto_SESSION session, + uint32_t usage_entry_number); + +/** + * This loads a usage entry saved previously by UpdateUsageEntry. The + * signature at the beginning of the buffer is verified and the buffer will + * be decrypted. Then the verification field in the entry will be verified. + * The index in the entry must match the index passed in. The generation + * number in the entry will be compared against the entry's corresponding + * generation number in the header. If it is off by 1, a warning is returned, + * but the entry is still loaded. This warning may be logged by the CDM + * layer. If the generation number is off by more than 1, an error is + * returned and the entry is not loaded. + * + * OEMCrypto shall call ODK_ReloadClockValues, as described in "License + * Duration and Renewal" to set the session's clock values. + * + * If the entry is already loaded into another open session, then this fails and + * returns OEMCrypto_ERROR_INVALID_SESSION. If the session already has a usage + * entry associated with it, the error OEMCrypto_ERROR_MULTIPLE_USAGE_ENTRIES is + * returned. It is also an error to try to reload the same usage entry into the + * same open session twice. + * + * Before version API 16, the usage entry stored the time that the license + * was loaded. This value is now interpreted as the time that the licence + * request was signed. This can be achieved by simply renaming the field and + * using the same value when reloading an older entry. + * + * @param[in] session: handle for the session to be used. + * @param[in] usage_entry_number: index of existing usage entry. + * @param[in] buffer: pointer to memory containing encrypted usage table entry. + * @param[in] buffer_length: length of the buffer, in bytes. + * + * @retval OEMCrypto_SUCCESS success + * @retval OEMCrypto_ERROR_SHORT_BUFFER + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED some devices do not implement usage + * tables. + * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE index beyond end of table. + * @retval OEMCrypto_ERROR_INVALID_SESSION entry associated with another + * session or the index is wrong. + * @retval OEMCrypto_WARNING_GENERATION_SKEW if the generation number is off + * by exactly 1. + * @retval OEMCrypto_ERROR_GENERATION_SKEW if the generation number is off by + * more than 1. + * @retval OEMCrypto_ERROR_SIGNATURE_FAILURE if the signature failed. + * @retval OEMCrypto_ERROR_BAD_MAGIC verification string does not match. + * @retval OEMCrypto_ERROR_SESSION_LOST_STATE + * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED + * @retval OEMCrypto_ERROR_MULTIPLE_USAGE_ENTRIES if there already is a usage + * entry loaded into this session + * + * @threading + * This is a "Usage Table Function" and will not be called simultaneously + * with any other function, as if the CDM holds a write lock on the OEMCrypto + * system. + * + * @version + * This method changed in API version 13. + */ +OEMCryptoResult OEMCrypto_LoadUsageEntry(OEMCrypto_SESSION session, + uint32_t usage_entry_number, + const uint8_t* buffer, + size_t buffer_length); + +/** + * Updates the session's usage entry and fills buffers with the encrypted and + * signed entry and usage table header. + * + * OEMCrypto shall call ODK_UpdateLastPlaybackTime to update the session's + * clock values, as discussed in the document "License Duration and Renewal". + * The values in the session's clock values structure are copied to the usage + * entry. + * + * OEMCrypto shall update all time and status values in the entry, and then + * increment the entry's generation number. The corresponding generation + * number in the usage table header is also incremented so that it matches + * the one in the entry. The master generation number in the usage table + * header is incremented and the master generation number is copied to secure + * persistent storage. OEMCrypto will encrypt and sign the entry into the + * entry_buffer, and it will encrypt and sign the usage table header into the + * header_buffer. Some actions, such as the first decrypt and deactivating an + * entry, will also increment the entry's generation number as well as + * changing the entry's status and time fields. The first decryption will + * change the status from Inactive to Active, and it will set the time stamp + * "first decrypt". + * + * If the usage entry has the flag ForbidReport set, then the flag is + * cleared. It is the responsibility of the CDM layer to call this function and + * save the usage table before the next call to OEMCrypto_ReportUsage() and + * before the CDM is terminated. Failure to do so will result in generation + * number skew, which will invalidate all of the usage table. + * + * If either entry_buffer_length or header_buffer_length is not large enough, + * they are set to the needed size, and return OEMCrypto_ERROR_SHORT_BUFFER. + * In this case, the entry is not updated, ForbidReport is not cleared, + * generation numbers are not incremented, and no other work is done. + * + * @param[in] session: handle for the session to be used. + * @param[out] header_buffer: pointer to memory where encrypted usage table + * header is written. + * @param[in,out] header_buffer_length: (in) length of the header_buffer, in + * bytes. (out) actual length of the header_buffer + * @param[out] entry_buffer: pointer to memory where encrypted usage table entry + * is written. + * @param[in,out] entry_buffer_length: (in) length of the entry_buffer, in + * bytes. (out) actual length of the entry_buffer + * + * @retval OEMCrypto_SUCCESS success + * @retval OEMCrypto_ERROR_SHORT_BUFFER + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED some devices do not implement usage + * tables. + * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE + * @retval OEMCrypto_ERROR_SESSION_LOST_STATE + * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED + * + * @threading + * This is a "Usage Table Function" and will not be called simultaneously + * with any other function, as if the CDM holds a write lock on the OEMCrypto + * system. + * + * @version + * This method changed in API version 16. + */ +OEMCryptoResult OEMCrypto_UpdateUsageEntry( + OEMCrypto_SESSION session, OEMCrypto_SharedMemory* header_buffer, + size_t* header_buffer_length, OEMCrypto_SharedMemory* entry_buffer, + size_t* entry_buffer_length); + +/** + * This deactivates the usage entry associated with the current session. This + * means that the status of the usage entry is changed to InactiveUsed if it + * was Active, or InactiveUnused if it was Unused. This also increments the + * entry's generation number, and the header's master generation number. The + * corresponding generation number in the usage table header is also + * incremented so that it matches the one in the entry. The entry's flag + * ForbidReport will be set. This flag prevents an application from + * generating a report of a deactivated license without first saving the + * entry. + * + * OEMCrypto shall call ODK_DeactivateUsageEntry to update the session's + * clock values, as discussed in the document "License Duration and Renewal". + * + * It is allowed to call this function multiple times. If the state is + * already InactiveUsed or InactiveUnused, then this function does not change + * the entry or its state. + * + * @param[in] session: handle for the session to be used. + * @param[in] pst: pointer to memory containing Provider Session Token. + * @param[in] pst_length: length of the pst, in bytes. + * + * @retval OEMCrypto_SUCCESS success + * @retval OEMCrypto_ERROR_INVALID_CONTEXT an entry was not created or loaded, + * or the pst does not match. + * @retval OEMCrypto_ERROR_INVALID_SESSION + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED + * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE + * @retval OEMCrypto_ERROR_BUFFER_TOO_LARGE + * @retval OEMCrypto_ERROR_SESSION_LOST_STATE + * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED + * + * @buffer_size + * OEMCrypto shall support pst sizes of at least 255 bytes. + * OEMCrypto shall return OEMCrypto_ERROR_BUFFER_TOO_LARGE if the buffer is + * larger than the supported size. + * + * @threading + * This is a "Usage Table Function" and will not be called simultaneously + * with any other function, as if the CDM holds a write lock on the OEMCrypto + * system. + * + * @version + * This method changed in API version 16. + */ +OEMCryptoResult OEMCrypto_DeactivateUsageEntry(OEMCrypto_SESSION session, + const uint8_t* pst, + size_t pst_length); + +/** + * All fields of OEMCrypto_PST_Report are in network byte order. + * + * If the buffer_length is not sufficient to hold a report structure, set + * buffer_length and return OEMCrypto_ERROR_SHORT_BUFFER. + * + * If an entry was not loaded or created with OEMCrypto_CreateNewUsageEntry() or + * OEMCrypto_LoadUsageEntry() return the error + * OEMCrypto_ERROR_INVALID_CONTEXT. If the pst does not match that in the entry, + * return OEMCrypto_ERROR_WRONG_PST. + * + * If the usage entry's flag ForbidReport is set, indicating the entry has + * not been saved since the entry was deactivated, then the error + * OEMCrypto_ERROR_ENTRY_NEEDS_UPDATE is returned and a report is not + * generated. Similarly, if any key in the session has been used since the + * last call to OEMCrypto_UpdateUsageEntry(), then the report is not generated, + * and OEMCrypto returns the error OEMCrypto_ERROR_ENTRY_NEEDS_UPDATE. + * + * The pst_report is filled out by subtracting the times in the Usage Entry + * from the current time on the secure clock. This design was chosen to avoid + * a requirement to sync the device's secure clock with any external clock. + * + * ![Usage Report Timeline](fig11.svg) + * + * Valid values for status are: + * + * - 0 = kUnused -- the keys have not been used to decrypt. + * - 1 = kActive -- the keys have been used, and have not been deactivated. + * - 2 = kInactive - deprecated. Use kInactiveUsed or kInactiveUnused. + * - 3 = kInactiveUsed -- the keys have been marked inactive after being + * active. + * - 4 = kInactiveUnused -- they keys have been marked inactive, but were + * never active. + * The clock_security_level is reported as follows: + * + * - 0 = Insecure Clock - clock just uses system time. + * - 1 = Secure Timer - clock runs from a secure timer which is initialized + * from system time when OEMCrypto becomes active and cannot be modified + * by user software or the user while OEMCrypto is active. A secure + * timer cannot run backwards, even while OEMCrypto is not active. + * - 2 = Secure Clock - Real-time clock set from a secure source that + * cannot be modified by user software regardless of whether OEMCrypto + * is active or inactive. The clock time can only be modified by + * tampering with the security software or hardware. + * - 3 = Hardware Secure Clock - Real-time clock set from a secure source + * that cannot be modified by user software and there are security + * features that prevent the user from modifying the clock in hardware, + * such as a tamper proof battery. + * + * ![Secure Clock versus Secure Timer versus Insecure Clock](fig12.svg) + * + * After pst_report has been filled in, the HMAC SHA1 signature is computed + * for the buffer from bytes 20 to the end of the pst field. The signature is + * computed using the mac_key[client] which is stored in the usage table. The + * HMAC SHA1 signature is used to prevent a rogue application from using + * OMECrypto_GenerateSignature to forge a Usage Report. + * + * Before version 16 of this API, seconds_since_license_received was reported + * instead of seconds_since_license_signed. For any practical bookkeeping + * purposes, these events are essentially at the same time. + * + * Devices that do not implement a Session Usage Table may return + * OEMCrypto_ERROR_NOT_IMPLEMENTED. + * + * @param[in] session: handle for the session to be used. + * @param[in] pst: pointer to memory containing Provider Session Token. + * @param[in] pst_length: length of the pst, in bytes. + * @param[out] buffer: pointer to buffer in which usage report should be + * stored. May be null on the first call in order to find required buffer + * size. + * @param[in,out] buffer_length: (in) length of the report buffer, in bytes. + * (out) actual length of the report + * + * @retval OEMCrypto_SUCCESS success + * @retval OEMCrypto_ERROR_SHORT_BUFFER if report buffer is not large enough + * to hold the output report. + * @retval OEMCrypto_ERROR_INVALID_SESSION no open session with that id. + * @retval OEMCrypto_ERROR_INVALID_CONTEXT + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED + * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE + * @retval OEMCrypto_ERROR_BUFFER_TOO_LARGE + * @retval OEMCrypto_ERROR_ENTRY_NEEDS_UPDATE if no call to UpdateUsageEntry + * since last call to Deactivate or since key use. + * @retval OEMCrypto_ERROR_WRONG_PST report asked for wrong pst. + * @retval OEMCrypto_ERROR_SESSION_LOST_STATE + * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED + * + * @buffer_size + * OEMCrypto shall support pst sizes of at least 255 bytes. + * OEMCrypto shall return OEMCrypto_ERROR_BUFFER_TOO_LARGE if the buffer is + * larger than the supported size. + * + * @threading + * This is a "Usage Table Function" and will not be called simultaneously + * with any other function, as if the CDM holds a write lock on the OEMCrypto + * system. + * + * @version + * This method changed in API version 13. + */ +OEMCryptoResult OEMCrypto_ReportUsage(OEMCrypto_SESSION session, + const uint8_t* pst, size_t pst_length, + uint8_t* buffer, size_t* buffer_length); + +/** + * Moves the entry associated with the current session from one location in + * the usage table header to another. This function is used by the CDM layer + * to defragment the usage table. This does not modify any data in the entry, + * except the index and the generation number. The index in the session's + * usage entry will be changed to new_index. The generation number in + * session's usage entry and in the header for new_index will be increased to + * the master generation number, and then the master generation number is + * incremented. If there was an existing entry at the new location, it will + * be overwritten. It is an error to call this when the entry that was at + * new_index is associated with a currently open session. In this case, the + * error code OEMCrypto_ERROR_ENTRY_IN_USE is returned. It is the CDM layer's + * responsibility to call UpdateUsageEntry after moving an entry. It is an + * error for new_index to be beyond the end of the existing usage table + * header. + * + * Devices that do not implement a Session Usage Table may return + * OEMCrypto_ERROR_NOT_IMPLEMENTED. + * + * @param[in] session: handle for the session to be used. + * @param[in] new_index: new index to be used for the session's usage entry + * + * @retval OEMCrypto_SUCCESS success + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED + * @retval OEMCrypto_ERROR_INVALID_SESSION + * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE + * @retval OEMCrypto_ERROR_BUFFER_TOO_LARGE + * @retval OEMCrypto_ERROR_ENTRY_IN_USE + * @retval OEMCrypto_ERROR_SESSION_LOST_STATE + * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED + * + * @threading + * This is a "Usage Table Function" and will not be called simultaneously + * with any other function, as if the CDM holds a write lock on the OEMCrypto + * system. + * + * @version + * This method is new in API version 13. + */ +OEMCryptoResult OEMCrypto_MoveEntry(OEMCrypto_SESSION session, + uint32_t new_index); + +/** + * This shrinks the usage table and the header. This function is used by the CDM + * layer after it has defragmented the usage table and can delete unused + * entries. It is an error if any open session is associated with an entry that + * will be erased - the error OEMCrypto_ERROR_ENTRY_IN_USE shall be returned in + * this case, and the header shall not be modified. If new_entry_count is larger + * than the current size, then the header is not changed and the error + * OEMCrypto_ERROR_UNKNOWN_FAILURE is returned. If the header has not been + * previously loaded, then OEMCrypto_ERROR_UNKNOWN_FAILURE is returned. + * OEMCrypto will increment the master generation number in the header and store + * the new value in secure persistent storage. Then, OEMCrypto will encrypt and + * sign the header into the provided buffer. The generation numbers of all + * remaining entries will remain unchanged. The next time + * OEMCrypto_CreateNewUsageEntry() is called, the new entry will have an index + * of new_entry_count. + * + * Devices that do not implement a Session Usage Table may return + * OEMCrypto_ERROR_NOT_IMPLEMENTED. + * + * If header_buffer_length is not large enough to hold the new table, it is + * set to the needed value, the generation number is not incremented, and + * OEMCrypto_ERROR_SHORT_BUFFER is returned. + * + * If the header has not been loaded or created, return the error + * OEMCrypto_ERROR_UNKNOWN_FAILURE. + * + * @param[in] new_entry_count: number of entries to be in the new header. + * @param[out] header_buffer: pointer to memory where encrypted usage table + * header is written. + * @param[in,out] header_buffer_length: (in) length of the header_buffer, in + * bytes. (out) actual length of the header_buffer + * + * @retval OEMCrypto_SUCCESS success + * @retval OEMCrypto_ERROR_SHORT_BUFFER + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED + * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE + * @retval OEMCrypto_ERROR_ENTRY_IN_USE + * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED + * + * @threading + * This is a "Usage Table Function" and will not be called simultaneously + * with any other function, as if the CDM holds a write lock on the OEMCrypto + * system. + * + * @version + * This method is new in API version 13. + */ +OEMCryptoResult OEMCrypto_ShrinkUsageTableHeader(uint32_t new_entry_count, + uint8_t* header_buffer, + size_t* header_buffer_length); + +/// @} + +/// @addtogroup prov40 +/// @{ + +/** + * Get the serialized boot certificate chain in CBOR format used in + * provisioning 4. + * + * @param[out] bcc: pointer to the buffer that receives the serialized boot + * certificate chain in CBOR format. + * @param[in,out] bcc_length - on input, size of the caller's bcc buffer. On + * output, the number of bytes written into the buffer. + * @param[out] additional_signature: pointer to the buffer that receives + * additional device key signature (certificate chain). This field is only + * used by the signing model where a vendor certificate is available on the + * device. + * @param[in,out] additional_signature_length - on input, size of the caller's + * additional_signature buffer. On output, the number of bytes written into + * the buffer. + * + * @retval OEMCrypto_SUCCESS + * @retval OEMCrypto_ERROR_SHORT_BUFFER if any of the buffers is too small to + * return the bcc or additional_signature. + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED if provisioning 4 is not supported. + * + * @threading + * This is a "Property Function" and may be called simultaneously with any + * other property function or session function, but not any initialization or + * usage table function, as if the CDM holds a read lock on the OEMCrypto + * system. + * + * @version + * This method is new in API version 17. + */ +OEMCryptoResult OEMCrypto_GetBootCertificateChain( + uint8_t* bcc, size_t* bcc_length, uint8_t* additional_signature, + size_t* additional_signature_length); + +/** + * Generates a key pair used in OEM and DRM certificate provisioning. The public + * key is supposed to be certified by the server. The private key is wrapped + * with the encryption key so it can be stored in the file system. + * + * The |public_key_signature| output is formatted differently depending + * on whether or not an OEM private key has been loaded. + * + * If an OEM private key is unavailable, the request is assumed to be for OEM + * certificate provisioning. In this case, the public key is signed by the + * device private key. The format of |public_key_signature| in this case is a + * COSE_Sign1 CBOR array. The format is described in RFC 8152 Section 4.2 and + * 4.4, as well as Android IRemotelyProvisionedComponent.aidl (under + * "SignedData") + * + * ~~~ + * |public_key_signature|: COSE_Sign1 CBOR array + * [ + * protected: bstr .cbor { 1 : AlgorithmEdDSA / AlgorithmES256 / + * AlgorithmES384 }, + * unprotected: {}, + * payload: bstr .cbor Data / nil, + * signature: bstr ; PureEd25519(priv_key, Sig_structure) / + * ; ECDSA(priv_key, Sig_structure) + * ] + * ~~~ + * + * Notes: + * 1. The payload field in the COSE_Sign1 struct is the public key generated + * by OEMCrypto_GenerateCertificateKeyPair + * 2. The signature field in the COSE_Sign1 struct is the concatenation of the + * (R,S) values from the EC/Ed signature. If either R or S is smaller than + * the key size, it is left-padded with 0 to match the key size as + * described in RFC 8152. This signature is not DER encoded. + * 3. The signature is generated by calling the selected EC signing function + * (PureEd25519 or one of the supported ECDSA algorithms) on + * `Sig_structure`, which is a CBOR array described below. The payload + * field in Sig_structure is the same as the payload in the above + * COSE_Sign1 CBOR array. + * + * ~~~ + * Sig_structure: CBOR array + * [ + * context: "Signature1", + * protected: bstr .cbor { 1 : AlgorithmEdDSA / AlgorithmES256 / + * AlgorithmES384 }, + * external_aad: bstr .size 0, + * payload: bstr .cbor Data / nil, + * ] + * ~~~ + * + * If an OEM private key is available, the request is assumed to be for DRM + * certificate provisioning and the public key is signed by the OEM private key. + * If the OEM private key is an RSA key, then |public_key_signature| is the raw + * output of the RSA sign operation with RSASSA-PSS padding. If the OEM private + * key is an ECC key, then |public_key_signature| is the ASN.1 DER-encoded (R,S) + * signature as specified in RFC 3279 2.2.3. + * + * After this function completes successfully, the session will hold a private + * key and will be ready for a call to + * OEMCrypto_PrepAndSignProvisioningRequest(). In particular, when this + * function is used to generate a DRM Certificate key pair, the session will be + * ready to sign a provisioning request with the DRM Cert private key. When this + * function is used to generate an OEM Certificate key pair, the session will be + * ready to sign a provisioning request with the OEM Cert private key. + * + * The public key shall be an ASN.1 DER-encoded SubjectPublicKeyInfo as + * specified in RFC 5280. Widevine recommends ECC keys for Provisioning 4.0, but + * an RSA key may also be used. If the key is an RSA key, then the encoding + * should use "rsaEncryption" (OID 1.2.840.113549.1.1.1), and not RSASSA-PSS. + * + * @param[in] session: session id. + * @param[out] public_key: pointer to the buffer that receives the public key + * that is to be certified by the server. The key must be an ASN.1 + * DER-encoded SubjectPublicKeyInfo as specified in RFC 5280. + * @param[in,out] public_key_length: on input, size of the caller's public_key + * buffer. On output, the number of bytes written into the buffer. + * @param[out] public_key_signature: pointer to the buffer that receives the + * signature of the public key. The format depends on whether an OEM private + * key has been loaded. + * @param[in,out] public_key_signature_length: on input, size of the caller's + * public_key_signature buffer. On output, the number of bytes written into + * the buffer. + * @param[out] wrapped_private_key: pointer to the buffer that receives the + * encrypted private key. It is encrypted by the device encryption key. + * @param[in,out] wrapped_private_key_length: on input, size of the caller's + * wrapped_private_key buffer. On output, the number of bytes written into + * the buffer. + * @param[out] key_type: the type of the generated key pair (RSA or ECC). + * + * @retval OEMCrypto_SUCCESS + * @retval OEMCrypto_ERROR_INVALID_SESSION + * @retval OEMCrypto_ERROR_SHORT_BUFFER if any of the buffer |public_key|, + * |public_key_signature| or |wrapped_private_key_size| is too small. + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED + * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE + * + * @threading + * This is a "Session Function" and may be called simultaneously with session + * functions for other sessions but not simultaneously with other functions + * for this session. It will not be called simultaneously with initialization + * or usage table functions. It is as if the CDM holds a write lock for this + * session, and a read lock on the OEMCrypto system. + * + * @version + * This method is new in API version 17. + */ +OEMCryptoResult OEMCrypto_GenerateCertificateKeyPair( + OEMCrypto_SESSION session, uint8_t* public_key, size_t* public_key_length, + uint8_t* public_key_signature, size_t* public_key_signature_length, + uint8_t* wrapped_private_key, size_t* wrapped_private_key_length, + OEMCrypto_PrivateKeyType* key_type); + +/** + * Get the serialized device information in CBOR map format. This is for devices + * that use Provisioning 4.0, with the device key uploading option in the + * factory. + * + * The device + * information may contain, for example, device make and model, "fused" status, + * and other properties, which is intended to be 1) uploaded during device + * manufacture in the factory, 2) checked by the server to verify that the + * provisioning request is coming from the expected device in the fields, based + * on the values previously uploaded and registered. + * + * Devices that do not support Provisioning 4.0, or do not support + * Provisioning 4.0 Uploading Option should return + * OEMCrypto_ERROR_NOT_IMPLEMENTED. + * + * @param[out] device_info: pointer to the buffer that receives the serialized + * device information in CBOR map format. + * @param[in,out] device_info_length: on input, size of the caller's + * device_info buffer. On output, the number of bytes written into the buffer. + * + * @retval OEMCrypto_SUCCESS + * @retval OEMCrypto_ERROR_SHORT_BUFFER if device_info_length is too small to + * return the device_info. + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED if provisioning 4 is not supported, + * or device information is not available on the platform. + * + * @threading + * This is a "Property Function" and may be called simultaneously with any + * other property function or session function, but not any initialization or + * usage table function, as if the CDM holds a read lock on the OEMCrypto + * system. + * + * @version + * This method is new in API version 18. + */ +OEMCryptoResult OEMCrypto_GetDeviceInformation(uint8_t* device_info, + size_t* device_info_length); + +/** + * Get the serialized signed Certificate Signing Request (CSR) payload in + * COSE_Sign1 format. This is for devices that use Provisioning 4.0, with the + * device key uploading option in the factory. + * + * With the uploading option, the RKP factory extraction tool provided by Google + * makes a call to this function to collect the signed CSR payload for + * generating the CSR to be uploaded to the device database. The CSR payload is + * signed by the leaf cert of the Boot Certificate Chain. + * + * The format of a CSR payload before COSE_Sign1 is a CBOR array described in + * Android IRemotelyProvisionedComponent.aidl (under "CsrPayload"): + * + * ~~~ + * CsrPayload = [ ; CBOR Array defining the payload for CSR. + * version: 3, ; The CsrPayload CDDL Schema version. + * CertificateType: "widevine" ; The type of certificate being requested. + * DeviceInfo, ; Defined in Android DeviceInfo.aidl + * KeysToSign: [] ; Empty list + * ] + * ~~~ + * + * The type of CertificateType is tstr and the value should always be + * "widevine". The type of KeysToSign is CBOR array and the value is not used, + * which should be left as an empty list. Note that the DeviceInfo above is a + * CBOR map structure defined in DeviceInfo.aidl, which can be constructed from + * the input |encoded_device_info|. DeviceInfo must be canonicalized according + * to the specification in RFC 7049. The required fields from DeviceInfo.aidl + * are: brand, manufacturer, product, model, device, vb_state, bootloader_state, + * vbmeta_digest, security_level. + * + * Once CsrPayload is prepared, together with |challenge| it is signed by the + * leaf cert of BCC, in the format of: + * + * ~~~ + * |signed_csr_payload| = SignedData<[ + * challenge: bstr .size (0..64), + * bstr .cbor CsrPayload, + * ]> + * ~~~ + * + * This function should output |signed_csr_payload| in the format of + * SignedData, which is a COSE_Sign1 CBOR and is defined in Android + * IRemotelyProvisionedComponent.aidl (under "SignedData"): + * + * ~~~ + * SignedData = [ + * protected: bstr .cbor { 1 : AlgorithmEdDSA / AlgorithmES256 / + * AlgorithmES384 }, + * unprotected: {}, + * payload: bstr .cbor Data / nil, + * signature: bstr ; PureEd25519(priv_key, Sig_structure) / + * ; ECDSA(priv_key, Sig_structure) + * ] + * ~~~ + * + * Also see OEMCrypto_GenerateCertificateKeyPair() for more details of + * SignedData and Sig_structure. + * + * Data in the payload field of SignedData is a CBOR array: + * + * ~~~ + * Data = [ + * challenge: bstr .size (0..64), + * bstr .cbor CsrPayload, + * ] + * ~~~ + * + * Devices that do not support Provisioning 4.0, or do not support + * Provisioning 4.0 Uploading Option should return + * OEMCrypto_ERROR_NOT_IMPLEMENTED. + * + * @param[in] challenge: pointer to the buffer containing a byte string to be + * signed. It is generated by the RKP factory extraction tool. + * @param[in] challenge_length: size of the challenge buffer. + * @param[in] encoded_device_info: pointer to the buffer containing the + * serialized device information in CBOR map format. It should be returned as + * `device_info` in a call to the function `OEMCrypto_GetDeviceInformation()`. + * @param[in] encoded_device_info_length: size of the encoded_device_info + * buffer. + * @param[out] signed_csr_payload: pointer to the buffer that receives the + * serialized signed CSR payload in COSE_Sign1 format. + * @param[in,out] signed_csr_payload_length: on input, size of the caller's + * signed_csr_payload buffer. On output, the number of bytes written into the + * buffer. + * + * @retval OEMCrypto_SUCCESS + * @retval OEMCrypto_ERROR_INVALID_CONTEXT if challenge_length or + * encoded_device_info_length is 0, or any pointer is NULL + * @retval OEMCrypto_ERROR_SHORT_BUFFER if signed_csr_payload_length is too + * small to return the signed_csr_payload. + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED if provisioning 4 is not supported, + * or device information is not available on the platform. + * + * @threading + * This is a "Property Function" and may be called simultaneously with any + * other property function or session function, but not any initialization or + * usage table function, as if the CDM holds a read lock on the OEMCrypto + * system. + * + * @version + * This method is new in API version 18. + */ +OEMCryptoResult OEMCrypto_GetDeviceSignedCsrPayload( + const uint8_t* challenge, size_t challenge_length, + const uint8_t* encoded_device_info, size_t encoded_device_info_length, + uint8_t* signed_csr_payload, size_t* signed_csr_payload_length); + +/** + * Loads an OEM private key to a session. The key will be used in signing DRM + * certificate request, or the public key generated by calling + * OEMCrypto_GenerateCertificateKeyPair. + * + * @param[in] session: session id. + * @param[in] key_type: type of the leaf key (RSA or ECC). + * @param[in] wrapped_private_key: the encrypted private key. This is the + * wrapped key generated by OEMCrypto_GenerateCertificateKeyPair. + * @param[in] wrapped_private_key_length: length of |wrapped_private_key| in + * bytes. + * + * @retval OEMCrypto_SUCCESS + * @retval OEMCrypto_ERROR_INVALID_CONTEXT + * @retval OEMCrypto_ERROR_NO_DEVICE_KEY + * @retval OEMCrypto_ERROR_INVALID_SESSION + * @retval OEMCrypto_ERROR_INVALID_KEY + * @retval OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE + * @retval OEMCrypto_ERROR_SESSION_LOST_STATE + * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED + * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE + * + * @threading + * This is a "Session Function" and may be called simultaneously with session + * functions for other sessions but not simultaneously with other functions + * for this session. It will not be called simultaneously with initialization + * or usage table functions. It is as if the CDM holds a write lock for this + * session, and a read lock on the OEMCrypto system. + * + * @version + * This method is new in API version 17. + */ +OEMCryptoResult OEMCrypto_InstallOemPrivateKey( + OEMCrypto_SESSION session, OEMCrypto_PrivateKeyType key_type, + const uint8_t* wrapped_private_key, size_t wrapped_private_key_length); +/// @} + +/// @addtogroup test_verify +/// @{ + +/** + * Enter Test Mode. This enables OEMCrypto test functionality. Without a call to + * this function, none of the test functions](./test-verify) shall be + * enabled. After this function has been called, OEMCrypto will not use the + * production keybox. Once OEMCrypto has entered Test Mode, it will not leave + * Test Mode until the next reboot. + * + * If the device is not in Test Mode, it will be in Production Mode and + * OEMCrypto will use a production root of trust (keybox or OEM Certificate) if + * available. In Production Mode, none of the test functions are enabled. + * + * Widevine recommends shipping a Production Only version of OEMCrypto on + * released devices. A Production Only version of OEMCrypto will have all test + * functions disabled. In this case, OEMCrypto_EnterTestMode() will return + * OEMCRYPTO_ERROR_NOT_IMPLEMENTED. + * + * @retval OEMCrypto_SUCCESS success + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED OEMCrypto is a production build, and + * does not support debug or test-only functions. + * + * @threading + * This is an "Initialization and Termination Function" and will not be + * called simultaneously with any other function, as if the CDM holds a write + * lock on the OEMCrypto system. It is called once after + * OEMCrypto_Initialize(), and before any other test-only functions are + * called. + * + * @version + * This method is new in API version 18. + */ +OEMCryptoResult OEMCrypto_EnterTestMode(void); + +/** + * Returns the type of hash function supported for Full Decrypt Path Testing. + * A hash type of OEMCrypto_Hash_Not_Supported = 0 means this feature is not + * supported. OEMCrypto is not required by Google to support this feature, + * but support will greatly improve automated testing. A hash type of + * OEMCrypto_CRC_Clear_Buffer = 1 means the device will be able to compute + * the CRC 32 checksum of the decrypted content in the secure buffer after a + * call to OEMCrypto_DecryptCENC(). Google intends to provide test applications + * on some platforms, such as Android, that will automate decryption testing + * using the CRC 32 checksum of all frames in some test content. + * + * If an SOC vendor cannot support CRC 32 checksums of decrypted output, but + * can support some other hash or checksum, then the function should return + * OEMCrypto_Partner_Defined_Hash = 2 and those partners should modify the + * test application to compute the appropriate hash. An application that + * computes the CRC 32 hashes of test content and builds a hash file in the + * correct format will be provided by Widevine. The source of this + * application will be provided so that partners may modify it to compute + * their own hash format and generate their own hashes. + * + * @retval OEMCrypto_Hash_Not_Supported = 0; + * @retval OEMCrypto_CRC_Clear_Buffer = 1; + * @retval OEMCrypto_Partner_Defined_Hash = 2; + * + * @threading + * This is a "Property Function" and may be called simultaneously with any + * other property function or session function, but not any initialization or + * usage table function, as if the CDM holds a read lock on the OEMCrypto + * system. + * + * @version + * This method is new in API version 15. + */ +uint32_t OEMCrypto_SupportsDecryptHash(void); + +/** + * Set the hash value for the next frame to be decrypted. This function is + * called before the first subsample is passed to OEMCrypto_DecryptCENC(), when + * the subsample_flag has the bit OEMCrypto_FirstSubsample set. The hash is + * over all of the frame or sample: encrypted and clear subsamples + * concatenated together, up to, and including the subsample with the + * subsample_flag having the bit OEMCrypto_LastSubsample set. If hashing the + * output is not supported, then this will return + * OEMCrypto_ERROR_NOT_IMPLEMENTED. If the hash is ill formed or there are + * other error conditions, this returns OEMCrypto_ERROR_UNKNOWN_FAILURE. The + * length of the hash will be at most 128 bytes, and will be 4 bytes (32 + * bits) for the default CRC32 hash. + * + * This may be called before the first call to OEMCrypto_GetKeyHandle. In that + * case, this function cannot verify that the key control block allows hash + * verification. The function DecryptCENC should verify that the key control bit + * allows hash verification when it is called. If an attempt is made to compute + * a hash when the selected key does not have the bit Allow_Hash_Verification + * set, then a hash should not be computed, and OEMCrypto_GetHashErrorCode() + * should return the error OEMCrypto_ERROR_UNKNOWN_FAILURE. + * + * OEMCrypto should compute the hash of the frame and then compare it with + * the correct value. If the values differ, then OEMCrypto should latch in an + * error and save the frame number of the bad hash. It is allowed for + * OEMCrypto to postpone computation of the hash until the frame is + * displayed. This might happen if the actual decryption operation is carried + * out by a later step in the video pipeline, or if you are using a partner + * specified hash of the decoded frame. For this reason, an error state must + * be saved until the call to OEMCrypto_GetHashErrorCode() is made. + * + * @param[in] session: session id for current decrypt operation + * @param[in] frame_number: frame number for the recent DecryptCENC sample. + * @param[in] hash: hash or CRC of previously decrypted frame. + * @param[in] hash_length: length of hash, in bytes. + * + * @retval OEMCrypto_SUCCESS if the hash was set + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED function not implemented + * @retval OEMCrypto_ERROR_INVALID_SESSION session not open + * @retval OEMCrypto_ERROR_SHORT_BUFFER hash_length too short for supported + * hash type + * @retval OEMCrypto_ERROR_BUFFER_TOO_LARGE hash_length too long for supported + * hash type + * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE other error + * @retval OEMCrypto_ERROR_SESSION_LOST_STATE + * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED + * + * @threading + * This is a "Session Function" and may be called simultaneously with session + * functions for other sessions but not simultaneously with other functions + * for this session. It will not be called simultaneously with initialization + * or usage table functions. It is as if the CDM holds a write lock for this + * session, and a read lock on the OEMCrypto system. + * + * @version + * This method is new in API version 15. + */ +OEMCryptoResult OEMCrypto_SetDecryptHash(OEMCrypto_SESSION session, + uint32_t frame_number, + const uint8_t* hash, + size_t hash_length); + +/** + * If the hash set in OEMCrypto_SetDecryptHash() did not match the computed + * hash, then an error code was saved internally. This function returns that + * error and the frame number of the bad hash. This will be called + * periodically, but might not be in sync with the decrypt loop. OEMCrypto + * shall not reset the error state to "no error" once any frame has failed + * verification. It should be initialized to "no error" when the session is + * first opened. If there is more than one bad frame, it is the implementer's + * choice if it is more useful to return the number of the first bad frame, + * or the most recent bad frame. + * + * If the hash could not be computed -- either because the + * Allow_Hash_Verification was not set in the key control block, or because + * there were other issues -- this function should return + * OEMCrypto_ERROR_UNKNOWN_FAILURE. + * + * @param[in] session: session id for operation. + * @param[out] failed_frame_number: frame number for sample with incorrect hash. + * + * @retval OEMCrypto_SUCCESS if all frames have had a correct hash + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED + * @retval OEMCrypto_ERROR_BAD_HASH if any frame had an incorrect hash + * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE if the hash could not be computed + * @retval OEMCrypto_ERROR_SESSION_LOST_STATE + * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED + * + * @threading + * This is a "Session Function" and may be called simultaneously with session + * functions for other sessions but not simultaneously with other functions + * for this session. It will not be called simultaneously with initialization + * or usage table functions. It is as if the CDM holds a write lock for this + * session, and a read lock on the OEMCrypto system. + * + * @version + * This method is new in API version 15. + */ +OEMCryptoResult OEMCrypto_GetHashErrorCode(OEMCrypto_SESSION session, + uint32_t* failed_frame_number); + +/** + * Allocates a secure buffer and fills out the destination buffer information + * in output_descriptor. The integer secure_fd may also be set to indicate + * the source of the buffer. OEMCrypto may use the secure_fd to help track + * the buffer if it wishes. The unit tests will pass a pointer to the same + * destination buffer description and the same secure_fd to + * OEMCrypto_FreeSecureBuffer when the buffer is to be freed. + * + * This is especially helpful if the hash functions above are supported. This + * will only be used by the OEMCrypto unit tests, so we recommend returning + * OEMCrypto_ERROR_NOT_IMPLEMENTED for production devices if performance is + * an issue. If OEMCrypto_ERROR_NOT_IMPLEMENTED is returned, then secure + * buffer unit tests will be skipped. + * + * @param[in] session: session id for operation. + * @param[in] buffer_size: the requested buffer size. + * @param[out] output_descriptor: the buffer descriptor for the created + * buffer. This will be passed into the OEMCrypto_DecryptCENC() function. + * @param[out] secure_fd: a pointer to platform dependent file or buffer + * descriptor. This will be passed to OEMCrypto_FreeSecureBuffer(). + * + * @retval OEMCrypto_SUCCESS if the buffer was created + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED + * @retval OEMCrypto_ERROR_OUTPUT_TOO_LARGE + * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE + * + * @threading + * This is a "Session Function" and may be called simultaneously with session + * functions for other sessions but not simultaneously with other functions + * for this session. It will not be called simultaneously with initialization + * or usage table functions. It is as if the CDM holds a write lock for this + * session, and a read lock on the OEMCrypto system. + * + * @version + * This method is new in API version 16. + */ +OEMCryptoResult OEMCrypto_AllocateSecureBuffer( + OEMCrypto_SESSION session, size_t buffer_size, + OEMCrypto_DestBufferDesc* output_descriptor, int* secure_fd); + +/** + * Frees a secure buffer that had previously been created with + * OEMCrypto_AllocateSecureBuffer(). Any return value except OEMCrypto_SUCCESS + * will cause the unit test using secure buffers to fail. + * + * @param[in] session: session id for operation. + * @param[in,out] output_descriptor: the buffer descriptor modified by + * OEMCrypto_AllocateSecureBuffer() + * @param[in] secure_fd: The integer returned by + * OEMCrypto_AllocateSecureBuffer() + * + * @retval OEMCrypto_SUCCESS if the buffer was freed + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED + * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE + * + * @threading + * This is a "Session Function" and may be called simultaneously with session + * functions for other sessions but not simultaneously with other functions + * for this session. It will not be called simultaneously with initialization + * or usage table functions. It is as if the CDM holds a write lock for this + * session, and a read lock on the OEMCrypto system. + * + * @version + * This method is new in API version 16. + */ +OEMCryptoResult OEMCrypto_FreeSecureBuffer( + OEMCrypto_SESSION session, OEMCrypto_DestBufferDesc* output_descriptor, + int secure_fd); + +/// @} + +/* + * OEMCrypto_OPK_SerializationVersion + * + * Note: This is an undocumented function. It is only required and used by the + * OPK implementation of OEMCrypto. It is not in a documentation group and does + * not show up on the devsite documentation page. + * + * Check the serialization protocol version used by the OEMCrypto Porting Kit + * (OPK). If the OPK is not used, this function must return + * OEMCrypto_ERROR_NOT_IMPLEMENTED. The serialization version is expressed as + * |major.minor|, where |major| and |minor| are integers. The TEE and REE + * serialization versions must match in order for OEMCrypto to communicate + * with the TEE. If the serialization versions do not match, calls to other + * OEMCrypto functions will return OPK_ERROR_INCOMPATIBLE_VERSION. A match is + * achieved if the |major| fields of the TEE and REE versions are the + * same. Differences in only the |minor| fields indicates that the protocols + * are different but are still compatible. + * + * @param[in,out] ree_major: pointer to memory to recieve the REE's |major| + * version. On input, *ree_major may be zero to request the serialization + * version of the REE. If *ree_major is non-zero, this function will test the + * TEE's compatibility using the specified REE major version. + * @param[in,out] ree_minor: pointer to memory to recieve the REE's |minor| + * version. On input, *ree_minor may be zero to request the serialization + * version of the REE. If *ree_minor is non-zero, this function will test the + * TEE's compatibility using the specified REE minor version. + * @param[out] tee_major: pointer to memory to recieve the TEE's |major| version + * @param[out] tee_minor: pointer to memory to recieve the TEE's |minor| version + * + * @retval OEMCrypto_SUCCESS success + * @retval OPK_ERROR_INCOMPATIBLE_VERSION + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED + */ +OEMCryptoResult OEMCrypto_OPK_SerializationVersion(uint32_t* ree_major, + uint32_t* ree_minor, + uint32_t* tee_major, + uint32_t* tee_minor); + +/****************************************************************************/ +/****************************************************************************/ +/* The following functions are optional. They are only used if the device + * supports OTA keybox provisioning. Widevine does not allow all devices to + * support OTA provisioning. Using an OTA provisioned keybox usually lowers a + * device's security profile in the DCSL. Please work with your Widevine Partner + * Engineer before implementing these functions to make sure you understand the + * security implications of using Keybox OTA Provisioning. + */ + +/** + * Generate an OTA Keybox provisioning request. The format of the + * message is specified in the document Keybox OTA Reprovisioning. If + * use_test_key is true, then the debug model key and id should be + * used. Widevine does not allow all devices to support OTA + * provisioning. Using an OTA provisioned keybox usually lowers a device's + * security profile in the DCSL. + * + * @param[in] session: handle for the session to be used. + * @param[out] buffer: where the provisioning request is stored. + * @param[in,out] buffer_length: length of the request, in bytes. + * @param[in] use_test_key: If non-zero, use the debug model key. This is used + * for testing the workflow. + * + * @retval OEMCrypto_SUCCESS on success + * @retval OEMCrypto_ERROR_SHORT_BUFFER if buffer_length is too small. + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED + * Any other error will be logged. + * + * @threading + * This is an "Initialization and Termination Function" and will not be called + * simultaneously with any other function, as if the CDM holds a write lock on + * the OEMCrypto system. It will be called only after + * OEMCrypto_IsKeyboxOrOEMCertValid() returns + * OEMCrypto_ERROR_NEEDS_KEYBOX_PROVISIONING immediately after initialization, + * and before any session is opened. + * + * @version + * This method is new in API version 16. + */ +OEMCryptoResult OEMCrypto_GenerateOTARequest(OEMCrypto_SESSION session, + uint8_t* buffer, + size_t* buffer_length, + uint32_t use_test_key); + +/** + * The buffer will be parsed as an OTA Keybox provisioning message, as + * described in the document OTA Keybox Reprovisioning. The + * signature will be verified. The keybox will be decrypted and verified. If + * |use_test_key| is false, the keybox will be installed permanently. + * + * If |use_test_key| is true, do not use the real model key, use the debug + * model key specified in OTA Keybox Reprovisioning. + * + * @param[in] session: handle for the session to be used. + * @param[in] buffer: pointer to provisioning response. + * @param[in] buffer_length: length of the buffer, in bytes. + * @param[in] use_test_key: If non-zero, use the debug model key. This is used + * for testing the workflow. + * + * @retval OEMCrypto_SUCCESS on success + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED + * @retval OEMCrypto_ERROR_SIGNATURE_FAILURE signature of message was wrong. + * @retval OEMCrypto_ERROR_KEYBOX_INVALID if the keybox was unpacked, but is + * invalid. + * @retval OEMCrypto_ERROR_WRITE_KEYBOX could not save keybox. + * Any other error will be logged. + * + * @threading + * This is an "Initialization and Termination Function" and will not be called + * simultaneously with any other function, as if the CDM holds a write lock on + * the OEMCrypto system. It will only be called after + * OEMCrypto_GenerateOTARequest(). + * + * @version + * This method is new in API version 16. + */ +OEMCryptoResult OEMCrypto_ProcessOTAKeybox(OEMCrypto_SESSION session, + const uint8_t* buffer, + size_t buffer_length, + uint32_t use_test_key); + +/****************************************************************************/ +/****************************************************************************/ +/* The following functions are deprecated. They are not required for the + * current version of OEMCrypto. They are being declared here to help with + * backwards compatibility. + */ + +/* + * OEMCrypto_GenerateSignature + * @deprecated + * Not required for the current version of OEMCrypto. Declared here to + * help with backward compatibility. + */ +OEMCryptoResult OEMCrypto_GenerateSignature(OEMCrypto_SESSION session, + const uint8_t* message, + size_t message_length, + uint8_t* signature, + size_t* signature_length); + +/* + * OEMCrypto_RewrapDeviceRSAKey30 + * @deprecated + * Not required for the current version of OEMCrypto. Declared here to + * help with backward compatibility. + */ +OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey30( + OEMCrypto_SESSION session, const uint32_t* unaligned_nonce, + const uint8_t* encrypted_message_key, size_t encrypted_message_key_length, + const uint8_t* enc_rsa_key, size_t enc_rsa_key_length, + const uint8_t* enc_rsa_key_iv, uint8_t* wrapped_rsa_key, + size_t* wrapped_rsa_key_length); + +/* + * OEMCrypto_RewrapDeviceRSAKey + * @deprecated + * Not required for the current version of OEMCrypto. Declared here to + * help with backward compatibility. + */ +OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey( + OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, + const uint8_t* signature, size_t signature_length, + const uint32_t* unaligned_nonce, const uint8_t* enc_rsa_key, + size_t enc_rsa_key_length, const uint8_t* enc_rsa_key_iv, + uint8_t* wrapped_rsa_key, size_t* wrapped_rsa_key_length); + +/* + * OEMCrypto_UpdateUsageTable + * @deprecated + * Not required for the current version of OEMCrypto. Declared here to + * help with backward compatibility. + */ +OEMCryptoResult OEMCrypto_UpdateUsageTable(void); + +/* + * OEMCrypto_DeleteUsageEntry + * @deprecated + * Not required for the current version of OEMCrypto. Declared here to + * help with backward compatibility. + */ +OEMCryptoResult OEMCrypto_DeleteUsageEntry( + OEMCrypto_SESSION session, const uint8_t* pst, size_t pst_length, + const uint8_t* message, size_t message_length, const uint8_t* signature, + size_t signature_length); + +/* + * OEMCrypto_ForceDeleteUsageEntry + * @deprecated + * Not required for the current version of OEMCrypto. Declared here to + * help with backward compatibility. + */ +OEMCryptoResult OEMCrypto_ForceDeleteUsageEntry(const uint8_t* pst, + size_t pst_length); + +/* + * OEMCrypto_CopyOldUsageEntry + * @deprecated + * Not required for the current version of OEMCrypto. Declared here to + * help with backward compatibility. + */ +OEMCryptoResult OEMCrypto_CopyOldUsageEntry(OEMCrypto_SESSION session, + const uint8_t* pst, + size_t pst_length); + +/* + * OEMCrypto_DeleteOldUsageTable + * @deprecated + * Not required for the current version of OEMCrypto. Declared here to + * help with backward compatibility. + */ +OEMCryptoResult OEMCrypto_DeleteOldUsageTable(void); + +/* + * OEMCrypto_CreateOldUsageEntry + * @deprecated + * Not required for the current version of OEMCrypto. Declared here to + * help with backward compatibility. + */ +OEMCryptoResult OEMCrypto_CreateOldUsageEntry( + uint64_t time_since_license_received, uint64_t time_since_first_decrypt, + uint64_t time_since_last_decrypt, OEMCrypto_Usage_Entry_Status status, + uint8_t* server_mac_key, uint8_t* client_mac_key, const uint8_t* pst, + size_t pst_length); + +/* + * OEMCrypto_GenerateDerivedKeys_V15 + * @deprecated + * Not required for the current version of OEMCrypto. Declared here to + * help with backward compatibility. + */ +OEMCryptoResult OEMCrypto_GenerateDerivedKeys_V15( + OEMCrypto_SESSION session, const uint8_t* mac_key_context, + uint32_t mac_key_context_length, const uint8_t* enc_key_context, + uint32_t enc_key_context_length); + +typedef struct { + size_t encrypt; // number of 16 byte blocks to decrypt. + size_t skip; // number of 16 byte blocks to leave in clear. + size_t offset; // offset into the pattern in blocks for this call. +} OEMCrypto_CENCEncryptPatternDesc_V15; + +/* + * OEMCrypto_DecryptCENC_V15 + * @deprecated + * Not required for the current version of OEMCrypto. Declared here to + * help with backward compatibility. + */ +OEMCryptoResult OEMCrypto_DecryptCENC_V15( + OEMCrypto_SESSION session, const uint8_t* data_addr, + size_t data_addr_length, bool is_encrypted, const uint8_t* iv, + size_t block_offset, // used for CTR "cenc" mode only. + OEMCrypto_DestBufferDesc* out_buffer, + const OEMCrypto_CENCEncryptPatternDesc_V15* pattern, + uint8_t subsample_flags); + +/* + * OEMCrypto_GetOEMPublicCertificate_V15 + * @deprecated + * Not required for the current version of OEMCrypto. Declared here to + * help with backward compatibility. + */ +OEMCryptoResult OEMCrypto_GetOEMPublicCertificate_V15( + OEMCrypto_SESSION session, uint8_t* public_cert, + size_t* public_cert_length); + +/* + * OEMCrypto_LoadDeviceRSAKey + * @deprecated + * Not required for the current version of OEMCrypto. Declared here to + * help with backward compatibility. + */ +OEMCryptoResult OEMCrypto_LoadDeviceRSAKey(OEMCrypto_SESSION session, + const uint8_t* wrapped_rsa_key, + size_t wrapped_rsa_key_length); + +/* + * OEMCrypto_BuildInformation_V16 + * @deprecated + * Not required for the current version of OEMCrypto. Declared here to + * help with backward compatibility. + */ +const char* OEMCrypto_BuildInformation_V16(void); + +/* + * OEMCrypto_SecurityLevel_V16 + * @deprecated + * Not required for the current version of OEMCrypto. Declared here to + * help with backward compatibility. + */ +const char* OEMCrypto_SecurityLevel_V16(void); + +typedef struct { + OEMCrypto_Substring entitlement_key_id; + OEMCrypto_Substring content_key_id; + OEMCrypto_Substring content_key_data_iv; + OEMCrypto_Substring content_key_data; +} OEMCrypto_EntitledContentKeyObject_V16; + +/* + * OEMCrypto_LoadEntitledContentKeys_V16 + * @deprecated + * Not required for the current version of OEMCrypto. Declared here to + * help with backward compatibility. + */ +OEMCryptoResult OEMCrypto_LoadEntitledContentKeys_V16( + OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, + size_t key_array_length, + const OEMCrypto_EntitledContentKeyObject_V16* key_array); + +/** + * OEmCrypto_IsSRIMUpdateSupported + * @deprecated + * Not required for the current version of OEMCrypto. Declared here to + * help with backward compatibility. + */ +bool OEMCrypto_IsSRMUpdateSupported(void); + +/** + * OEMCrypto_LoadSRM + * @deprecated + * Not required for the current version of OEMCrypto. Declared here to + * help with backward compatibility. + */ +OEMCryptoResult OEMCrypto_LoadSRM(const uint8_t* buffer, size_t buffer_length); + +/** + * OEMCrypto_RemoveSRM + * @deprecated + * Not required for the current version of OEMCrypto. Declared here to + * help with backward compatibility. + */ +OEMCryptoResult OEMCrypto_RemoveSRM(void); + +/** + * OEMCrypto_LoadKeys + * @deprecated + * OEMCrypto_LoadKeys is only used to load a v15 license or renewal. + */ +OEMCryptoResult OEMCrypto_LoadKeys( + OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, + const uint8_t* signature, size_t signature_length, + OEMCrypto_Substring enc_mac_keys_iv, OEMCrypto_Substring enc_mac_keys, + size_t key_array_length, const OEMCrypto_KeyObject* key_array, + OEMCrypto_Substring pst, OEMCrypto_Substring srm_restriction_data, + OEMCrypto_LicenseType license_type); + +typedef struct { + OEMCrypto_Substring key_id; + OEMCrypto_Substring key_control_iv; + OEMCrypto_Substring key_control; +} OEMCrypto_KeyRefreshObject; +/** + * OEMCrypto_RefreshKeys + * @deprecated + * OEMCrypto_RefreshKeys is only used to load a v15 license or renewal. + */ +OEMCryptoResult OEMCrypto_RefreshKeys( + OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, + const uint8_t* signature, size_t signature_length, size_t num_keys, + const OEMCrypto_KeyRefreshObject* key_array); + +/** + * OEMCrypto_GetRandom + * @deprecated + * OEMCrypto_GetRandom is not needed to export random numbers. + */ +OEMCryptoResult OEMCrypto_GetRandom(uint8_t* random_data, + size_t random_data_length); + +/** + * OEMCrypto_SelectKey + * @deprecated + * Not required for the current version of OEMCrypto. Declared here to + * help with backward compatibility. + */ +OEMCryptoResult OEMCrypto_SelectKey(OEMCrypto_SESSION session, + const uint8_t* content_key_id, + size_t content_key_id_length, + OEMCryptoCipherMode cipher_mode); + +/** + * OEMCrypto_DecryptCENC_V17 + * @deprecated + * Not required for the current version of OEMCrypto. Declared here to + * help with backward compatibility. + */ +OEMCryptoResult OEMCrypto_DecryptCENC_V17( + OEMCrypto_SESSION session, const OEMCrypto_SampleDescription* samples, + size_t samples_length, const OEMCrypto_CENCEncryptPatternDesc* pattern); + +/** + * OEMCrypto_Generic_Encrypt_V17 + * @deprecated + * Not required for the current version of OEMCrypto. Declared here to + * help with backward compatibility. + */ +OEMCryptoResult OEMCrypto_Generic_Encrypt_V17( + OEMCrypto_SESSION session, const OEMCrypto_SharedMemory* in_buffer, + size_t in_buffer_length, const uint8_t* iv, OEMCrypto_Algorithm algorithm, + OEMCrypto_SharedMemory* out_buffer); + +/** + * OEMCrypto_Generic_Decrypt_V17 + * @deprecated + * Not required for the current version of OEMCrypto. Declared here to + * help with backward compatibility. + */ +OEMCryptoResult OEMCrypto_Generic_Decrypt_V17( + OEMCrypto_SESSION session, const OEMCrypto_SharedMemory* in_buffer, + size_t in_buffer_length, const uint8_t* iv, OEMCrypto_Algorithm algorithm, + OEMCrypto_SharedMemory* out_buffer); + +/** + * OEMCrypto_Generic_Sign_V17 + * @deprecated + * Not required for the current version of OEMCrypto. Declared here to + * help with backward compatibility. + */ +OEMCryptoResult OEMCrypto_Generic_Sign_V17(OEMCrypto_SESSION session, + const OEMCrypto_SharedMemory* buffer, + size_t buffer_length, + OEMCrypto_Algorithm algorithm, + OEMCrypto_SharedMemory* signature, + size_t* signature_length); + +/** + * OEMCrypto_Generic_Verify_V17 + * @deprecated + * Not required for the current version of OEMCrypto. Declared here to + * help with backward compatibility. + */ +OEMCryptoResult OEMCrypto_Generic_Verify_V17( + OEMCrypto_SESSION session, const OEMCrypto_SharedMemory* buffer, + size_t buffer_length, OEMCrypto_Algorithm algorithm, + const OEMCrypto_SharedMemory* signature, size_t signature_length); + +/****************************************************************************/ +/****************************************************************************/ +/* The following functions are used by internal L3 CDMs and are not required by + * other CDM implementations. + */ + +/** + * Get the embedded Drm Certificate used by internal L3 CDMs. + * + * @param[out] public_cert where the certificate is stored. + * @param[in,out] public_cert_length the length, in bytes, of the certificate. + * + * @retval OEMCrypto_SUCCESS on success + * @retval OEMCrypto_ERROR_SHORT_BUFFER if public_cert_length is too small. + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED + */ +OEMCryptoResult OEMCrypto_GetEmbeddedDrmCertificate(uint8_t* public_cert, + size_t* public_cert_length); + +#ifdef __cplusplus +} +#endif + +#endif // OEMCRYPTO_CENC_H_ diff --git a/oemcrypto/include/OEMCryptoCENCCommon.h b/oemcrypto/include/OEMCryptoCENCCommon.h new file mode 100644 index 0000000..7da8b45 --- /dev/null +++ b/oemcrypto/include/OEMCryptoCENCCommon.h @@ -0,0 +1,262 @@ +// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// source code may only be used and distributed under the Widevine +// License Agreement. + +/********************************************************************* + * OEMCryptoCENCCommon.h + * + * Common structures and error codes between WV servers and OEMCrypto. + * + *********************************************************************/ + +#ifndef WIDEVINE_ODK_INCLUDE_OEMCRYPTOCENCCOMMON_H_ +#define WIDEVINE_ODK_INCLUDE_OEMCRYPTOCENCCOMMON_H_ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/// @addtogroup common_types +/// @{ + +/* clang-format off */ +/** Error and result codes returned by OEMCrypto functions. */ +typedef enum OEMCryptoResult { + OEMCrypto_SUCCESS = 0, + OEMCrypto_ERROR_INIT_FAILED = 1, + OEMCrypto_ERROR_TERMINATE_FAILED = 2, + OEMCrypto_ERROR_OPEN_FAILURE = 3, + OEMCrypto_ERROR_CLOSE_FAILURE = 4, + OEMCrypto_ERROR_ENTER_SECURE_PLAYBACK_FAILED = 5, /* deprecated */ + OEMCrypto_ERROR_EXIT_SECURE_PLAYBACK_FAILED = 6, /* deprecated */ + OEMCrypto_ERROR_SHORT_BUFFER = 7, + OEMCrypto_ERROR_NO_DEVICE_KEY = 8, /* no keybox device key. */ + OEMCrypto_ERROR_NO_ASSET_KEY = 9, + OEMCrypto_ERROR_KEYBOX_INVALID = 10, + OEMCrypto_ERROR_NO_KEYDATA = 11, + OEMCrypto_ERROR_NO_CW = 12, + OEMCrypto_ERROR_DECRYPT_FAILED = 13, + OEMCrypto_ERROR_WRITE_KEYBOX = 14, + OEMCrypto_ERROR_WRAP_KEYBOX = 15, + OEMCrypto_ERROR_BAD_MAGIC = 16, + OEMCrypto_ERROR_BAD_CRC = 17, + OEMCrypto_ERROR_NO_DEVICEID = 18, + OEMCrypto_ERROR_RNG_FAILED = 19, + OEMCrypto_ERROR_RNG_NOT_SUPPORTED = 20, + OEMCrypto_ERROR_SETUP = 21, + OEMCrypto_ERROR_OPEN_SESSION_FAILED = 22, + OEMCrypto_ERROR_CLOSE_SESSION_FAILED = 23, + OEMCrypto_ERROR_INVALID_SESSION = 24, + OEMCrypto_ERROR_NOT_IMPLEMENTED = 25, + OEMCrypto_ERROR_NO_CONTENT_KEY = 26, + OEMCrypto_ERROR_CONTROL_INVALID = 27, + OEMCrypto_ERROR_UNKNOWN_FAILURE = 28, + OEMCrypto_ERROR_INVALID_CONTEXT = 29, + OEMCrypto_ERROR_SIGNATURE_FAILURE = 30, + OEMCrypto_ERROR_TOO_MANY_SESSIONS = 31, + OEMCrypto_ERROR_INVALID_NONCE = 32, + OEMCrypto_ERROR_TOO_MANY_KEYS = 33, + OEMCrypto_ERROR_DEVICE_NOT_RSA_PROVISIONED = 34, + OEMCrypto_ERROR_INVALID_RSA_KEY = 35, /* deprecated */ + OEMCrypto_ERROR_KEY_EXPIRED = 36, + OEMCrypto_ERROR_INSUFFICIENT_RESOURCES = 37, + OEMCrypto_ERROR_INSUFFICIENT_HDCP = 38, + OEMCrypto_ERROR_BUFFER_TOO_LARGE = 39, + OEMCrypto_WARNING_GENERATION_SKEW = 40, /* Warning, not error. */ + OEMCrypto_ERROR_GENERATION_SKEW = 41, + OEMCrypto_LOCAL_DISPLAY_ONLY = 42, /* Info, not an error. */ + OEMCrypto_ERROR_ANALOG_OUTPUT = 43, + OEMCrypto_ERROR_WRONG_PST = 44, + OEMCrypto_ERROR_WRONG_KEYS = 45, + OEMCrypto_ERROR_MISSING_MASTER = 46, + OEMCrypto_ERROR_LICENSE_INACTIVE = 47, + OEMCrypto_ERROR_ENTRY_NEEDS_UPDATE = 48, + OEMCrypto_ERROR_ENTRY_IN_USE = 49, + OEMCrypto_ERROR_USAGE_TABLE_UNRECOVERABLE = 50, /* Obsolete. Don't use. */ + /* Use OEMCrypto_ERROR_NO_CONTENT_KEY instead of KEY_NOT_LOADED. */ + OEMCrypto_KEY_NOT_LOADED = 51, /* Obsolete. */ + OEMCrypto_KEY_NOT_ENTITLED = 52, + OEMCrypto_ERROR_BAD_HASH = 53, + OEMCrypto_ERROR_OUTPUT_TOO_LARGE = 54, + OEMCrypto_ERROR_SESSION_LOST_STATE = 55, + OEMCrypto_ERROR_SYSTEM_INVALIDATED = 56, + OEMCrypto_ERROR_LICENSE_RELOAD = 57, + OEMCrypto_ERROR_MULTIPLE_USAGE_ENTRIES = 58, + OEMCrypto_WARNING_MIXED_OUTPUT_PROTECTION = 59, + OEMCrypto_ERROR_INVALID_ENTITLED_KEY_SESSION = 60, + OEMCrypto_ERROR_NEEDS_KEYBOX_PROVISIONING = 61, + OEMCrypto_ERROR_UNSUPPORTED_CIPHER = 62, + OEMCrypto_ERROR_DVR_FORBIDDEN = 63, + OEMCrypto_ERROR_INSUFFICIENT_PRIVILEGE = 64, + OEMCrypto_ERROR_INVALID_KEY = 65, + /* ODK return values */ + ODK_ERROR_BASE = 1000, + ODK_ERROR_CORE_MESSAGE = ODK_ERROR_BASE, + ODK_SET_TIMER = ODK_ERROR_BASE + 1, + ODK_DISABLE_TIMER = ODK_ERROR_BASE + 2, + ODK_TIMER_EXPIRED = ODK_ERROR_BASE + 3, + ODK_UNSUPPORTED_API = ODK_ERROR_BASE + 4, + ODK_STALE_RENEWAL = ODK_ERROR_BASE + 5, + /* OPK return values */ + OPK_ERROR_BASE = 2000, + OPK_ERROR_REMOTE_CALL = OPK_ERROR_BASE, + OPK_ERROR_INCOMPATIBLE_VERSION = OPK_ERROR_BASE + 1, + OPK_ERROR_NO_PERSISTENT_DATA = OPK_ERROR_BASE + 2, + OPK_ERROR_PREHOOK_FAILURE = OPK_ERROR_BASE + 3, + OPK_ERROR_POSTHOOK_FAILURE = OPK_ERROR_BASE + 4, +} OEMCryptoResult; +/* clang-format on */ + +/** + * Valid values for status in the usage table. + */ +typedef enum OEMCrypto_Usage_Entry_Status { + kUnused = 0, + kActive = 1, + kInactive = 2, /* Deprecated. Use kInactiveUsed or kInactiveUnused. */ + kInactiveUsed = 3, + kInactiveUnused = 4, +} OEMCrypto_Usage_Entry_Status; + +/* Not used publicly. Not documented with Doxygen. */ +typedef enum OEMCrypto_ProvisioningRenewalType { + OEMCrypto_NoRenewal = 0, + OEMCrypto_RenewalACert = 1, +} OEMCrypto_ProvisioningRenewalType; + +/** + * OEMCrypto_LicenseType is used in the license message to indicate if the key + * objects are for content keys, or for entitlement keys. + */ +typedef enum OEMCrypto_LicenseType { + OEMCrypto_ContentLicense = 0, + OEMCrypto_EntitlementLicense = 1, + OEMCrypto_LicenseType_MaxValue = OEMCrypto_EntitlementLicense, +} OEMCrypto_LicenseType; + +/** + * Private key type used in the provisioning response. + */ +typedef enum OEMCrypto_PrivateKeyType { + OEMCrypto_RSA_Private_Key = 0, + OEMCrypto_ECC_Private_Key = 1, + OEMCrypto_PrivateKeyType_MaxValue = OEMCrypto_ECC_Private_Key, +} OEMCrypto_PrivateKeyType; + +/** + * The base for (delayed) timers, i.e. from what time the (delayed) timer + * starts. + */ +typedef enum OEMCrypto_TimerDelayBase { + OEMCrypto_License_Start = 0, + OEMCrypto_License_Load = 1, + OEMCrypto_First_Decrypt = 2, + OEMCrypto_TimerDelayBase_MaxValue = OEMCrypto_First_Decrypt, +} OEMCrypto_TimerDelayBase; + +/** + * Used to indicate a substring of a signed message in ODK_ParseLicense + * and other functions which must verify that a parameter is contained within a + * signed message. + */ +typedef struct { + size_t offset; + size_t length; +} OEMCrypto_Substring; + +/** + * Used to specify information about CMI Descriptor 0. + * @param id: ID value of CMI Descriptor assigned by DTLA. + * @param length: byte length of the usage rules field. + * @param data: usage rules data. + */ +typedef struct { + uint8_t id; // 0x00 + uint8_t extension; // 0x00 + uint16_t length; // 0x01 + uint8_t data; +} OEMCrypto_DTCP2_CMI_Descriptor_0; + +/** + * Used to specify information about CMI Descriptor 1. + * @param id: ID value of CMI Descriptor assigned by DTLA. + * @param extension: specified by the CMI descriptor + * @param length: byte length of the usage rules field. + * @param data: usage rules data. + */ +typedef struct { + uint8_t id; // 0x01 + uint8_t extension; // 0x00 + uint16_t length; // 0x03 + uint8_t data[3]; +} OEMCrypto_DTCP2_CMI_Descriptor_1; + +/** + * Used to specify information about CMI Descriptor 2. + * @param id: ID value of CMI Descriptor assigned by DTLA. + * @param extension: specified by the CMI descriptor + * @param length: byte length of the usage rules field. + * @param data: usage rules data. + */ +typedef struct { + uint8_t id; // 0x02 + uint8_t extension; // 0x00 + uint16_t length; // 0x03 + uint8_t data[3]; +} OEMCrypto_DTCP2_CMI_Descriptor_2; + +/** + * Used to specify the required DTCP2 level. If dtcp2_required is 0, there are + * no requirements on any of the keys. If dtcp2_required is 1, any key with the + * kControlHDCPRequired bit set requires DTCP2 in its output. + * @param dtcp2_required: specifies whether dtcp2 is required. 0 = not required, + * 1 = DTCP2 required. + * @param cmi_descriptor_1: three bytes of CMI descriptor 1 + */ +typedef struct { + uint8_t dtcp2_required; // 0 = not required. 1 = DTCP2 v1 required. + OEMCrypto_DTCP2_CMI_Descriptor_0 cmi_descriptor_0; + OEMCrypto_DTCP2_CMI_Descriptor_1 cmi_descriptor_1; + OEMCrypto_DTCP2_CMI_Descriptor_2 cmi_descriptor_2; +} OEMCrypto_DTCP2_CMI_Packet; + +/** + * Points to the relevant fields for a content key. The fields are extracted + * from the License Response message offered to ODK_ParseLicense(). Each + * field points to one of the components of the key. Key data, key control, + * and both IV fields are 128 bits (16 bytes): + * @param key_id: the unique id of this key. + * @param key_id_length: the size of key_id. OEMCrypto may assume this is at + * most 16. However, OEMCrypto shall correctly handle key id lengths + * from 1 to 16 bytes. + * @param key_data_iv: the IV for performing AES-128-CBC decryption of the + * key_data field. + * @param key_data - the key data. It is encrypted (AES-128-CBC) with the + * session's derived encrypt key and the key_data_iv. + * @param key_control_iv: the IV for performing AES-128-CBC decryption of the + * key_control field. + * @param key_control: the key control block. It is encrypted (AES-128-CBC) with + * the content key from the key_data field. + * + * The memory for the OEMCrypto_KeyObject fields is allocated and freed + * by the caller of ODK_ParseLicense(). + */ +typedef struct { + OEMCrypto_Substring key_id; + OEMCrypto_Substring key_data_iv; + OEMCrypto_Substring key_data; + OEMCrypto_Substring key_control_iv; + OEMCrypto_Substring key_control; +} OEMCrypto_KeyObject; + +/// @} + +#ifdef __cplusplus +} +#endif + +#endif // WIDEVINE_ODK_INCLUDE_OEMCRYPTOCENCCOMMON_H_ diff --git a/oemcrypto/include/level3.h b/oemcrypto/include/level3.h new file mode 100644 index 0000000..826c657 --- /dev/null +++ b/oemcrypto/include/level3.h @@ -0,0 +1,612 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine +// License Agreement. + +/********************************************************************* + * level3.h + * + * Reference APIs needed to support Widevine's crypto algorithms. + *********************************************************************/ + +#ifndef LEVEL3_OEMCRYPTO_H_ +#define LEVEL3_OEMCRYPTO_H_ + +#include +#include + +#include "OEMCryptoCENC.h" +#include "level3_file_system.h" + +// clang-format off +#ifdef DYNAMIC_ADAPTER +#define Level3_IsInApp _lcc00 +#define Level3_Initialize _lcc01 +#define Level3_Terminate _lcc02 +#define Level3_InstallKeyboxOrOEMCert _lcc03 +#define Level3_GetKeyData _lcc04 +#define Level3_IsKeyboxOrOEMCertValid _lcc05 +#define Level3_GetDeviceID _lcc07 +#define Level3_WrapKeyboxOrOEMCert _lcc08 +#define Level3_OpenSession _lcc09 +#define Level3_CloseSession _lcc10 +#define Level3_GenerateSignature _lcc13 +#define Level3_GenerateNonce _lcc14 +#define Level3_RewrapDeviceRSAKey _lcc18 +#define Level3_LoadDeviceRSAKey _lcc19 +#define Level3_DeriveKeysFromSessionKey _lcc21 +#define Level3_APIVersion _lcc22 +#define Level3_Generic_Encrypt_V17 _lcc24 +#define Level3_Generic_Decrypt_V17 _lcc25 +#define Level3_Generic_Sign_V17 _lcc26 +#define Level3_Generic_Verify_V17 _lcc27 +#define Level3_SupportsUsageTable _lcc29 +#define Level3_ReportUsage _lcc32 +#define Level3_GetMaxNumberOfSessions _lcc37 +#define Level3_GetNumberOfOpenSessions _lcc38 +#define Level3_IsAntiRollbackHwPresent _lcc39 +#define Level3_QueryKeyControl _lcc41 +#define Level3_GetHDCPCapability _lcc44 +#define Level3_LoadTestRSAKey _lcc45 +#define Level3_SecurityPatchLevel _lcc46 +#define Level3_GetProvisioningMethod _lcc49 +#define Level3_RewrapDeviceRSAKey30 _lcc51 +#define Level3_SupportedCertificates _lcc52 +#define Level3_IsSRMUpdateSupported _lcc53 +#define Level3_GetCurrentSRMVersion _lcc54 +#define Level3_LoadSRM _lcc55 +#define Level3_RemoveSRM _lcc57 +#define Level3_CreateUsageTableHeader _lcc61 +#define Level3_LoadUsageTableHeader _lcc62 +#define Level3_CreateNewUsageEntry _lcc63 +#define Level3_LoadUsageEntry _lcc64 +#define Level3_UpdateUsageEntry _lcc65 +#define Level3_ShrinkUsageTableHeader _lcc67 +#define Level3_MoveEntry _lcc68 +#define Level3_GetAnalogOutputFlags _lcc71 +#define Level3_LoadTestKeybox _lcc78 +#define Level3_SelectKey _lcc81 +#define Level3_LoadKeys _lcc83 +#define Level3_SetSandbox _lcc84 +#define Level3_ResourceRatingTier _lcc85 +#define Level3_SupportsDecryptHash _lcc86 +#define Level3_SetDecryptHash _lcc88 +#define Level3_GetHashErrorCode _lcc89 +#define Level3_RefreshKeys _lcc91 +#define Level3_LoadEntitledContentKeys_V16 _lcc92 +#define Level3_CopyBuffer _lcc93 +#define Level3_MaximumUsageTableHeaderSize _lcc94 +#define Level3_GenerateDerivedKeys _lcc95 +#define Level3_PrepAndSignLicenseRequest _lcc96 +#define Level3_PrepAndSignRenewalRequest _lcc97 +#define Level3_PrepAndSignProvisioningRequest _lcc98 +#define Level3_LoadLicense _lcc99 +#define Level3_LoadRenewal _lcc101 +#define Level3_LoadProvisioning _lcc102 +#define Level3_LoadOEMPrivateKey _lcc103 +#define Level3_GetOEMPublicCertificate _lcc104 +#define Level3_DecryptCENC_V17 _lcc105 +#define Level3_LoadDRMPrivateKey _lcc107 +#define Level3_MinorAPIVersion _lcc108 +#define Level3_AllocateSecureBuffer _lcc109 +#define Level3_FreeSecureBuffer _lcc110 +#define Level3_CreateEntitledKeySession _lcc111 +#define Level3_RemoveEntitledKeySession _lcc112 +#define Level3_GetBootCertificateChain _lcc116 +#define Level3_GenerateCertificateKeyPair _lcc117 +#define Level3_InstallOemPrivateKey _lcc118 +#define Level3_ReassociateEntitledKeySession _lcc119 +#define Level3_LoadCasECMKeys _lcc120 +#define Level3_LoadEntitledContentKeys _lcc121 // place holder for v17. +#define Level3_ProductionReady _lcc122 +#define Level3_Idle _lcc123 +#define Level3_Wake _lcc124 +#define Level3_BuildInformation _lcc125 +#define Level3_SecurityLevel _lcc126 +#define Level3_ReuseUsageEntry _lcc127 +#define Level3_GetDTCP2Capability _lcc128 +#define Level3_GetWatermarkingSupport _lcc129 +#define Level3_GetOEMKeyToken _lcc130 +#define Level3_GetDeviceInformation _lcc131 +#define Level3_SetMaxAPIVersion _lcc132 +#define Level3_GetKeyHandle _lcc133 +#define Level3_DecryptCENC _lcc134 +#define Level3_Generic_Encrypt _lcc135 +#define Level3_Generic_Decrypt _lcc136 +#define Level3_Generic_Sign _lcc137 +#define Level3_Generic_Verify _lcc138 +#define Level3_GetSignatureHashAlgorithm _lcc139 +#define Level3_EnterTestMode _lcc140 +#define Level3_GetDeviceSignedCsrPayload _lcc141 +#define Level3_GetEmbeddedDrmCertificate _lcc143 +#else +#define Level3_Initialize _oecc01 +#define Level3_Terminate _oecc02 +#define Level3_InstallKeyboxOrOEMCert _oecc03 +#define Level3_GetKeyData _oecc04 +#define Level3_IsKeyboxOrOEMCertValid _oecc05 +#define Level3_GetDeviceID _oecc07 +#define Level3_WrapKeyboxOrOEMCert _oecc08 +#define Level3_OpenSession _oecc09 +#define Level3_CloseSession _oecc10 +#define Level3_GenerateSignature _oecc13 +#define Level3_GenerateNonce _oecc14 +#define Level3_RewrapDeviceRSAKey _oecc18 +#define Level3_LoadDeviceRSAKey _oecc19 +#define Level3_DeriveKeysFromSessionKey _oecc21 +#define Level3_APIVersion _oecc22 +#define Level3_Generic_Encrypt_V17 _oecc24 +#define Level3_Generic_Decrypt_V17 _oecc25 +#define Level3_Generic_Sign_V17 _oecc26 +#define Level3_Generic_Verify_V17 _oecc27 +#define Level3_SupportsUsageTable _oecc29 +#define Level3_ReportUsage _oecc32 +#define Level3_GenerateRSASignature _oecc36 +#define Level3_GetMaxNumberOfSessions _oecc37 +#define Level3_GetNumberOfOpenSessions _oecc38 +#define Level3_IsAntiRollbackHwPresent _oecc39 +#define Level3_QueryKeyControl _oecc41 +#define Level3_GetHDCPCapability _oecc44 +#define Level3_LoadTestRSAKey _oecc45 +#define Level3_SecurityPatchLevel _oecc46 +#define Level3_GetProvisioningMethod _oecc49 +#define Level3_RewrapDeviceRSAKey30 _oecc51 +#define Level3_SupportedCertificates _oecc52 +#define Level3_IsSRMUpdateSupported _oecc53 +#define Level3_GetCurrentSRMVersion _oecc54 +#define Level3_LoadSRM _oecc55 +#define Level3_RemoveSRM _oecc57 +#define Level3_CreateUsageTableHeader _oecc61 +#define Level3_LoadUsageTableHeader _oecc62 +#define Level3_CreateNewUsageEntry _oecc63 +#define Level3_LoadUsageEntry _oecc64 +#define Level3_UpdateUsageEntry _oecc65 +#define Level3_DeactivateUsageEntry _oecc66 +#define Level3_ShrinkUsageTableHeader _oecc67 +#define Level3_MoveEntry _oecc68 +#define Level3_GetAnalogOutputFlags _oecc71 +#define Level3_LoadTestKeybox _oecc78 +#define Level3_SelectKey _oecc81 +#define Level3_LoadKeys _oecc83 +#define Level3_SetSandbox _oecc84 +#define Level3_ResourceRatingTier _oecc85 +#define Level3_SupportsDecryptHash _oecc86 +#define Level3_SetDecryptHash _oecc88 +#define Level3_GetHashErrorCode _oecc89 +#define Level3_RefreshKeys _oecc91 +#define Level3_LoadEntitledContentKeys_V16 _oecc92 +#define Level3_CopyBuffer _oecc93 +#define Level3_MaximumUsageTableHeaderSize _oecc94 +#define Level3_GenerateDerivedKeys _oecc95 +#define Level3_PrepAndSignLicenseRequest _oecc96 +#define Level3_PrepAndSignRenewalRequest _oecc97 +#define Level3_PrepAndSignProvisioningRequest _oecc98 +#define Level3_LoadLicense _oecc99 +#define Level3_LoadRenewal _oecc101 +#define Level3_LoadProvisioning _oecc102 +#define Level3_LoadOEMPrivateKey _oecc103 +#define Level3_GetOEMPublicCertificate _oecc104 +#define Level3_DecryptCENC_V17 _oecc105 +#define Level3_LoadDRMPrivateKey _oecc107 +#define Level3_MinorAPIVersion _oecc108 +#define Level3_AllocateSecureBuffer _oecc109 +#define Level3_FreeSecureBuffer _oecc110 +#define Level3_CreateEntitledKeySession _oecc111 +#define Level3_RemoveEntitledKeySession _oecc112 +#define Level3_GetBootCertificateChain _oecc116 +#define Level3_GenerateCertificateKeyPair _oecc117 +#define Level3_InstallOemPrivateKey _oecc118 +#define Level3_ReassociateEntitledKeySession _oecc119 +#define Level3_LoadCasECMKeys _oecc120 +#define Level3_LoadEntitledContentKeys _oecc121 // place holder for v17. +#define Level3_ProductionReady _oecc122 +#define Level3_Idle _oecc123 +#define Level3_Wake _oecc124 +#define Level3_BuildInformation _oecc125 +#define Level3_SecurityLevel _oecc126 +#define Level3_ReuseUsageEntry _oecc127 +#define Level3_GetDTCP2Capability _oecc128 +#define Level3_GetWatermarkingSupport _oecc129 +#define Level3_GetOEMKeyToken _oecc130 +#define Level3_GetDeviceInformation _oecc131 +#define Level3_SetMaxAPIVersion _oecc132 +#define Level3_GetKeyHandle _oecc133 +#define Level3_DecryptCENC _oecc134 +#define Level3_Generic_Encrypt _oecc135 +#define Level3_Generic_Decrypt _oecc136 +#define Level3_Generic_Sign _oecc137 +#define Level3_Generic_Verify _oecc138 +#define Level3_GetSignatureHashAlgorithm _oecc139 +#define Level3_EnterTestMode _oecc140 +#define Level3_GetDeviceSignedCsrPayload _oecc141 +// Internal-only. +#define Level3_GetEmbeddedDrmCertificate _oecc143 +#endif + +#define Level3_GetInitializationState _oecl3o01 +// clang-format on + +extern "C" { + +bool Level3_IsInApp(); +OEMCryptoResult Level3_Initialize(void); +OEMCryptoResult Level3_Terminate(void); +OEMCryptoResult Level3_OpenSession(OEMCrypto_SESSION* session); +OEMCryptoResult Level3_CloseSession(OEMCrypto_SESSION session); +OEMCryptoResult Level3_GenerateDerivedKeys(OEMCrypto_SESSION session, + const uint8_t* mac_key_context, + size_t mac_key_context_length, + const uint8_t* enc_key_context, + size_t enc_key_context_length); +OEMCryptoResult Level3_GenerateNonce(OEMCrypto_SESSION session, + uint32_t* nonce); +OEMCryptoResult Level3_QueryKeyControl(OEMCrypto_SESSION session, + const uint8_t* key_id, + size_t key_id_length, + uint8_t* key_control_block, + size_t* key_control_block_length); +OEMCryptoResult Level3_DecryptCENC_V17( + OEMCrypto_SESSION session, const OEMCrypto_SampleDescription* samples, + size_t samples_length, const OEMCrypto_CENCEncryptPatternDesc* pattern); +OEMCryptoResult Level3_InstallKeyboxOrOEMCert(const uint8_t* rot, + size_t rotLength); +OEMCryptoResult Level3_IsKeyboxOrOEMCertValid(void); +OEMCryptoResult Level3_WrapKeyboxOrOEMCert(const uint8_t* rot, size_t rotLength, + uint8_t* wrappedRot, + size_t* wrappedRotLength, + const uint8_t* transportKey, + size_t transportKeyLength); +OEMCrypto_ProvisioningMethod Level3_GetProvisioningMethod(); +OEMCryptoResult Level3_GetOEMPublicCertificate(uint8_t* public_cert, + size_t* public_cert_length); +OEMCryptoResult Level3_GetDeviceID(uint8_t* deviceID, size_t* idLength); +OEMCryptoResult Level3_GetKeyData(uint8_t* keyData, size_t* keyDataLength); +OEMCryptoResult Level3_LoadOEMPrivateKey(OEMCrypto_SESSION session); +OEMCryptoResult Level3_LoadDRMPrivateKey(OEMCrypto_SESSION session, + OEMCrypto_PrivateKeyType key_type, + const uint8_t* wrapped_rsa_key, + size_t wrapped_rsa_key_length); +OEMCryptoResult Level3_LoadProvisioning( + OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, + size_t core_message_length, const uint8_t* signature, + size_t signature_length, uint8_t* wrapped_private_key, + size_t* wrapped_private_key_length); +OEMCryptoResult Level3_RewrapDeviceRSAKey( + OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, + const uint8_t* signature, size_t signature_length, const uint32_t* nonce, + const uint8_t* enc_rsa_key, size_t enc_rsa_key_length, + const uint8_t* enc_rsa_key_iv, uint8_t* wrapped_rsa_key, + size_t* wrapped_rsa_key_length); +OEMCryptoResult Level3_LoadTestRSAKey(); +OEMCryptoResult Level3_GenerateRSASignature(OEMCrypto_SESSION session, + const uint8_t* message, + size_t message_length, + uint8_t* signature, + size_t* signature_length, + RSA_Padding_Scheme padding_scheme); +OEMCryptoResult Level3_DeriveKeysFromSessionKey(OEMCrypto_SESSION session, + const uint8_t* enc_session_key, + size_t enc_session_key_length, + const uint8_t* mac_key_context, + size_t mac_key_context_length, + const uint8_t* enc_key_context, + size_t enc_key_context_length); +uint32_t Level3_APIVersion(); +uint32_t Level3_MinorAPIVersion(); +uint8_t Level3_SecurityPatchLevel(); +OEMCrypto_Security_Level Level3_SecurityLevel(); +OEMCryptoResult Level3_GetHDCPCapability(OEMCrypto_HDCP_Capability* current, + OEMCrypto_HDCP_Capability* maximum); +bool Level3_SupportsUsageTable(); +bool Level3_IsAntiRollbackHwPresent(); +OEMCryptoResult Level3_GetNumberOfOpenSessions(size_t* count); +OEMCryptoResult Level3_GetMaxNumberOfSessions(size_t* maximum); +uint32_t Level3_SupportedCertificates(); +OEMCryptoResult Level3_Generic_Encrypt_V17( + OEMCrypto_SESSION session, const uint8_t* in_buffer, size_t buffer_length, + const uint8_t* iv, OEMCrypto_Algorithm algorithm, uint8_t* out_buffer); +OEMCryptoResult Level3_Generic_Decrypt_V17( + OEMCrypto_SESSION session, const uint8_t* in_buffer, size_t buffer_length, + const uint8_t* iv, OEMCrypto_Algorithm algorithm, uint8_t* out_buffer); +OEMCryptoResult Level3_Generic_Sign_V17(OEMCrypto_SESSION session, + const uint8_t* in_buffer, + size_t buffer_length, + OEMCrypto_Algorithm algorithm, + uint8_t* signature, + size_t* signature_length); +OEMCryptoResult Level3_Generic_Verify_V17(OEMCrypto_SESSION session, + const uint8_t* in_buffer, + size_t buffer_length, + OEMCrypto_Algorithm algorithm, + const uint8_t* signature, + size_t signature_length); +OEMCryptoResult Level3_DeactivateUsageEntry(OEMCrypto_SESSION session, + const uint8_t* pst, + size_t pst_length); +OEMCryptoResult Level3_ReportUsage(OEMCrypto_SESSION session, + const uint8_t* pst, size_t pst_length, + uint8_t* buffer, size_t* buffer_length); +bool Level3_IsSRMUpdateSupported(); +OEMCryptoResult Level3_GetCurrentSRMVersion(uint16_t* version); +OEMCryptoResult Level3_LoadSRM(const uint8_t* buffer, size_t buffer_length); +OEMCryptoResult Level3_RemoveSRM(); +OEMCryptoResult Level3_CreateUsageTableHeader(uint8_t* header_buffer, + size_t* header_buffer_length); +OEMCryptoResult Level3_LoadUsageTableHeader(const uint8_t* buffer, + size_t buffer_length); +OEMCryptoResult Level3_CreateNewUsageEntry(OEMCrypto_SESSION session, + uint32_t* usage_entry_number); +OEMCryptoResult Level3_LoadUsageEntry(OEMCrypto_SESSION session, uint32_t index, + const uint8_t* buffer, + size_t buffer_size); +OEMCryptoResult Level3_UpdateUsageEntry(OEMCrypto_SESSION session, + uint8_t* header_buffer, + size_t* header_buffer_length, + uint8_t* entry_buffer, + size_t* entry_buffer_length); +OEMCryptoResult Level3_ShrinkUsageTableHeader(uint32_t new_table_size, + uint8_t* header_buffer, + size_t* header_buffer_length); +OEMCryptoResult Level3_MoveEntry(OEMCrypto_SESSION session, uint32_t new_index); +uint32_t Level3_GetAnalogOutputFlags(); +OEMCryptoResult Level3_LoadTestKeybox(const uint8_t* buffer, size_t length); +OEMCryptoResult Level3_SelectKey(const OEMCrypto_SESSION session, + const uint8_t* key_id, size_t key_id_length, + OEMCryptoCipherMode cipher_mode); +OEMCryptoResult Level3_LoadLicense(OEMCrypto_SESSION session, + const uint8_t* message, + size_t message_length, + size_t core_message_length, + const uint8_t* signature, + size_t signature_length); +OEMCryptoResult Level3_LoadKeys( + OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, + const uint8_t* signature, size_t signature_length, + OEMCrypto_Substring enc_mac_keys_iv, OEMCrypto_Substring enc_mac_keys, + size_t num_keys, const OEMCrypto_KeyObject* key_array, + OEMCrypto_Substring pst, OEMCrypto_Substring srm_restriction_data, + OEMCrypto_LicenseType license_type); +OEMCryptoResult Level3_SetSandbox(const uint8_t* sandbox_id, + size_t sandbox_id_length); +uint32_t Level3_ResourceRatingTier(); +uint32_t Level3_SupportsDecryptHash(); + +OEMCryptoResult Level3_SetDecryptHash(OEMCrypto_SESSION session, + uint32_t frame_number, + const uint8_t* hash, size_t hash_length); +OEMCryptoResult Level3_GetHashErrorCode(OEMCrypto_SESSION session, + uint32_t* failed_frame_number); +OEMCryptoResult Level3_BuildInformation(char* buffer, size_t* buffer_length); +OEMCryptoResult Level3_LoadRenewal(OEMCrypto_SESSION session, + const uint8_t* message, + size_t message_length, + size_t core_message_length, + const uint8_t* signature, + size_t signature_length); +OEMCryptoResult Level3_RefreshKeys(OEMCrypto_SESSION session, + const uint8_t* message, + size_t message_length, + const uint8_t* signature, + size_t signature_length, size_t num_keys, + const OEMCrypto_KeyRefreshObject* key_array); +OEMCryptoResult Level3_LoadEntitledContentKeys( + OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, + size_t num_keys, const OEMCrypto_EntitledContentKeyObject* key_array); +OEMCryptoResult Level3_CopyBuffer( + OEMCrypto_SESSION session, const uint8_t* data_addr, size_t data_length, + const OEMCrypto_DestBufferDesc* out_buffer_descriptor, + uint8_t subsample_flags); +OEMCryptoResult Level3_PrepAndSignProvisioningRequest( + OEMCrypto_SESSION session, uint8_t* message, size_t message_length, + size_t* core_message_length, uint8_t* signature, size_t* signature_length); +OEMCryptoResult Level3_PrepAndSignLicenseRequest( + OEMCrypto_SESSION session, uint8_t* message, size_t message_length, + size_t* core_message_length, uint8_t* signature, size_t* signature_length); +OEMCryptoResult Level3_PrepAndSignRenewalRequest( + OEMCrypto_SESSION session, uint8_t* message, size_t message_length, + size_t* core_message_length, uint8_t* signature, size_t* signature_length); +size_t Level3_MaximumUsageTableHeaderSize(); +OEMCryptoResult Level3_AllocateSecureBuffer( + OEMCrypto_SESSION session, size_t buffer_size, + OEMCrypto_DestBufferDesc* output_descriptor, int* secure_fd); +OEMCryptoResult Level3_FreeSecureBuffer( + OEMCrypto_SESSION session, OEMCrypto_DestBufferDesc* output_descriptor, + int secure_fd); +OEMCryptoResult Level3_CreateEntitledKeySession(OEMCrypto_SESSION oec_session, + OEMCrypto_SESSION* key_session); +OEMCryptoResult Level3_RemoveEntitledKeySession(OEMCrypto_SESSION key_session); +OEMCryptoResult Level3_GetBootCertificateChain( + uint8_t* bcc, size_t* bcc_size, uint8_t* additional_signature, + size_t* additional_signature_size); +OEMCryptoResult Level3_GenerateCertificateKeyPair( + OEMCrypto_SESSION session, uint8_t* public_key, size_t* public_key_size, + uint8_t* public_key_signature, size_t* public_key_signature_size, + uint8_t* wrapped_private_key, size_t* wrapped_private_key_size, + OEMCrypto_PrivateKeyType* key_type); +OEMCryptoResult Level3_InstallOemPrivateKey(OEMCrypto_SESSION session, + OEMCrypto_PrivateKeyType key_type, + const uint8_t* wrapped_private_key, + size_t wrapped_private_key_length); +OEMCryptoResult Level3_ReassociateEntitledKeySession( + OEMCrypto_SESSION key_session, OEMCrypto_SESSION oec_session); +OEMCryptoResult Level3_LoadCasECMKeys( + OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, + const OEMCrypto_EntitledContentKeyObject* even_key, + const OEMCrypto_EntitledContentKeyObject* odd_key); +OEMCryptoResult Level3_ProductionReady(); +OEMCryptoResult Level3_Idle(OEMCrypto_IdleState state, + uint32_t os_specific_code); +OEMCryptoResult Level3_Wake(); +OEMCryptoResult Level3_ReuseUsageEntry(OEMCrypto_SESSION session, + uint32_t usage_entry_number); +OEMCryptoResult Level3_GetDTCP2Capability( + OEMCrypto_DTCP2_Capability* capability); +OEMCrypto_WatermarkingSupport Level3_GetWatermarkingSupport(); +OEMCryptoResult Level3_GetOEMKeyToken(OEMCrypto_SESSION key_session, + uint8_t* key_token, + size_t* key_token_length); +OEMCryptoResult Level3_GetDeviceInformation(uint8_t* device_info, + size_t* device_info_length); +OEMCryptoResult Level3_GetDeviceSignedCsrPayload( + const uint8_t* challenge, size_t challenge_length, + const uint8_t* encoded_device_info, size_t encoded_device_info_length, + uint8_t* signed_csr_payload, size_t* signed_csr_payload_length); +OEMCryptoResult Level3_SetMaxAPIVersion(uint32_t max_version); +OEMCryptoResult Level3_GetKeyHandle(OEMCrypto_SESSION session, + const uint8_t* content_key_id, + size_t content_key_id_length, + OEMCryptoCipherMode cipher_mode, + uint8_t* key_handle, + size_t* key_handle_length); +OEMCryptoResult Level3_DecryptCENC( + const uint8_t* key_handle, size_t key_handle_length, + const OEMCrypto_SampleDescription* samples, size_t samples_length, + const OEMCrypto_CENCEncryptPatternDesc* pattern); +OEMCryptoResult Level3_Generic_Encrypt(const uint8_t* key_handle, + size_t key_handle_length, + const OEMCrypto_SharedMemory* in_buffer, + size_t in_buffer_length, + const uint8_t* iv, + OEMCrypto_Algorithm algorithm, + OEMCrypto_SharedMemory* out_buffer); +OEMCryptoResult Level3_Generic_Decrypt(const uint8_t* key_handle, + size_t key_handle_length, + const OEMCrypto_SharedMemory* in_buffer, + size_t in_buffer_length, + const uint8_t* iv, + OEMCrypto_Algorithm algorithm, + OEMCrypto_SharedMemory* out_buffer); +OEMCryptoResult Level3_Generic_Sign(const uint8_t* key_handle, + size_t key_handle_length, + const OEMCrypto_SharedMemory* buffer, + size_t buffer_length, + OEMCrypto_Algorithm algorithm, + OEMCrypto_SharedMemory* signature, + size_t* signature_length); +OEMCryptoResult Level3_Generic_Verify(const uint8_t* key_handle, + size_t key_handle_length, + const OEMCrypto_SharedMemory* buffer, + size_t buffer_length, + OEMCrypto_Algorithm algorithm, + const OEMCrypto_SharedMemory* signature, + size_t signature_length); +OEMCryptoResult Level3_GetSignatureHashAlgorithm( + OEMCrypto_SESSION session, OEMCrypto_SignatureHashAlgorithm* algorithm); +OEMCryptoResult Level3_EnterTestMode(void); +OEMCryptoResult Level3_GetEmbeddedDrmCertificate(uint8_t* public_cert, + size_t* public_cert_length); + +// The following are specific to Google's Level 3 implementation and are not +// required. + +enum Level3InitializationState { + LEVEL3_INITIALIZATION_SUCCESS = 0, + LEVEL3_INITIALIZATION_UNKNOWN_FAILURE = 1, + LEVEL3_SEED_FAILURE = 2, + LEVEL3_SAVE_DEVICE_KEYS_FAILURE = 3, + LEVEL3_READ_DEVICE_KEYS_FAILURE = 4, + LEVEL3_VERIFY_DEVICE_KEYS_FAILURE = 5, +}; + +enum Level3RunningMode { + LEVEL3_MODE_HAYSTACK_ONLY = 0, + LEVEL3_MODE_RIKERS_DEFAULT = 1, + LEVEL3_MODE_RIKERS_ONLY = 2, +}; + +/* + * Level3_GetRunningMode + * + * Description: + * Returns the current mode the Level3 is running in. This shouldn't change + * while the processes is running. + * + * Parameters: + * N/A + * + * Threading: + * No other function calls will be made while this function is running. + * + * Version: + * This method is new in API version 19. + */ +Level3RunningMode Level3_GetRunningMode(void); + +/* + * Level3_GetInitializationState + * + * Description: + * Return any warning or error condition which occurred during + * initialization. On some platforms, this value will be logged and metrics + * will be gathered on production devices. This is an optional feature, and + * OEMCrypto may always return 0, even if Level3_Initialize failed. This + * function may be called whether Level3_Initialize succeeded or not. + * + * Parameters: + * N/A + * + * Threading: + * No other function calls will be made while this function is running. + * + * Returns: + * LEVEL3_INITIALIZATION_SUCCESS - no warnings or errors during initialization + * LEVEL3_SEED_FAILURE - error in seeding the software RNG + * LEVEL3_SAVE_DEVICE_KEYS_FAILURE - failed to save device keys to file system + * LEVEL3_READ_DEVICE_KEYS_FAILURE - failed to read device keys from file + * system + * LEVEL3_VERIFY_DEVICE_KEYS_FAILURE - failed to verify decrypted device keys + * + * Version: + * This method is new in API version 14. + */ +Level3InitializationState Level3_GetInitializationState(void); + +/* + * Level3_OutputErrorLogs + * + * Description: + * Call to output any errors in the Level 3 execution if the Level 3 has + * failed. This method should only be called if the Level 3 has failed in + * an unrecoverable state, and needs to be reinitialized. + * + * Parameters: + * N/A + * + * Threading: + * No other function calls will be made while this function is running. + * + * Returns: + * N/A + * + * Version: + * This method is new in API version 15. + */ +void Level3_OutputErrorLogs(); + +} // extern "C" + +namespace wvoec3 { + +// The following are interfaces needed for Google's Level 3 OEMCrypto +// specifically, which partners are expected to implement. + +// Returns a stable, unique identifier for the device. This could be a +// serial number or any other character sequence representing that device. +// The parameter |len| needs to be changed to reflect the length of the +// unique identifier. +const char* getUniqueID(size_t* len); + +// Returns a 64-bit unsigned integer to be used as a random seed for RNG. +// If the operation is unsuccessful, this function returns 0. +// We provide a sample implementation under the name generate_entropy_linux.cpp +// which partners should use if they can. +uint64_t generate_entropy(); + +// Creates and returns an OEMCrypto_Level3FileSystem implementation. +OEMCrypto_Level3FileSystem* createLevel3FileSystem(); + +// Deletes the pointer retrieved by the function above. +void deleteLevel3FileSystem(OEMCrypto_Level3FileSystem* file_system); + +} // namespace wvoec3 + +#endif // LEVEL3_OEMCRYPTO_H_ diff --git a/oemcrypto/include/level3_file_system.h b/oemcrypto/include/level3_file_system.h new file mode 100644 index 0000000..8f8fbc3 --- /dev/null +++ b/oemcrypto/include/level3_file_system.h @@ -0,0 +1,32 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine +// License Agreement. + +/********************************************************************* + * level3_file_system.h + * + * File system for OEMCrypto Level3 file operations. + *********************************************************************/ + +#ifndef LEVEL3_FILE_SYSTEM_H_ +#define LEVEL3_FILE_SYSTEM_H_ + +#include +#include "platform.h" + +namespace wvoec3 { + +class OEMCrypto_Level3FileSystem { + public: + virtual ~OEMCrypto_Level3FileSystem() {} + virtual ssize_t Read(const char *filename, void *buffer, size_t size) = 0; + virtual ssize_t Write(const char *filename, const void *buffer, + size_t size) = 0; + virtual bool Exists(const char *filename) = 0; + virtual ssize_t FileSize(const char *filename) = 0; + virtual bool Remove(const char *filename) = 0; +}; + +} // namespace wvoec3 + +#endif diff --git a/oemcrypto/include/oemcrypto_types.h b/oemcrypto/include/oemcrypto_types.h new file mode 100644 index 0000000..02edc98 --- /dev/null +++ b/oemcrypto/include/oemcrypto_types.h @@ -0,0 +1,96 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine +// License Agreement. + +#ifndef WV_OEMCRYPTO_TYPES_H_ +#define WV_OEMCRYPTO_TYPES_H_ + +#include + +namespace wvoec { + +// This is the format of a Widevine keybox. +typedef struct WidevineKeybox { // 128 bytes total. + // C character string identifying the device. Null terminated. + uint8_t device_id_[32]; + // 128 bit AES key assigned to device. Generated by Widevine. + uint8_t device_key_[16]; + // Key Data. Encrypted data. + uint8_t data_[72]; + // Constant code used to recognize a valid keybox "kbox" = 0x6b626f78. + uint8_t magic_[4]; + // The CRC checksum of the first 124 bytes of the keybox. + uint8_t crc_[4]; +} WidevineKeybox; + +// This is the format for a key control block. +typedef struct { + uint8_t verification[4]; + uint32_t duration; + uint32_t nonce; + uint32_t control_bits; +} KeyControlBlock; + +/* + * SRM_Restriction_Data + * + * Structure passed into LoadKeys to specify required SRM version. + */ +typedef struct { + uint8_t verification[8]; // must be "HDCPDATA" + uint32_t minimum_srm_version; // version number. +} SRM_Restriction_Data; + +// clang-format off +// Key Control Block Bit Masks: +const uint32_t kControlObserveDataPath = (1u << 31); +const uint32_t kControlObserveHDCP = (1u << 30); +const uint32_t kControlObserveCGMS = (1u << 29); +const uint32_t kControlRequireAntiRollbackHardware = (1u << 28); +// The two bits kControlWhiteboxSecurityLevelMask are not used in +// OEMCrypto; they are only used for whitebox testing. +const uint32_t kControlWhiteboxSecurityLevelShift = 26; +const uint32_t kControlWhiteboxSecurityLevelMask = + (0x03u << kControlWhiteboxSecurityLevelShift); +const uint32_t kControlAllowDVRRecording = (1u << 25); +const uint32_t kControlAllowHashVerification = (1u << 24); +const uint32_t kSharedLicense = (1u << 23); +const uint32_t kControlSRMVersionRequired = (1u << 22); +const uint32_t kControlDisableAnalogOutput = (1u << 21); +const uint32_t kControlSecurityPatchLevelShift = 15; +const uint32_t kControlSecurityPatchLevelMask = + (0x3Fu << kControlSecurityPatchLevelShift); +const uint32_t kControlReplayMask = (0x03u << 13); +const uint32_t kControlNonceRequired = (0x01u << 13); +const uint32_t kControlNonceOrEntry = (0x02u << 13); +const uint32_t kControlHDCPVersionShift = 9; +const uint32_t kControlHDCPVersionMask = + (0x0Fu << kControlHDCPVersionShift); +const uint32_t kControlAllowEncrypt = (1u << 8); +const uint32_t kControlAllowDecrypt = (1u << 7); +const uint32_t kControlAllowSign = (1u << 6); +const uint32_t kControlAllowVerify = (1u << 5); +const uint32_t kControlDataPathSecure = (1u << 4); +const uint32_t kControlNonceEnabled = (1u << 3); +const uint32_t kControlHDCPRequired = (1u << 2); +const uint32_t kControlCGMSMask = (0x03); +const uint32_t kControlCGMSCopyFreely = (0x00); +const uint32_t kControlCGMSCopyOnce = (0x02); +const uint32_t kControlCGMSCopyNever = (0x03); +// clang-format on + +// Various constants and sizes: +constexpr size_t KEY_CONTROL_SIZE = 16; +constexpr size_t KEY_ID_SIZE = 16; +constexpr size_t KEY_IV_SIZE = 16; +constexpr size_t KEY_PAD_SIZE = 16; +constexpr size_t KEY_SIZE = 16; +constexpr size_t AES_128_BLOCK_SIZE = 16; +constexpr size_t MAC_KEY_SIZE = 32; +constexpr size_t KEYBOX_KEY_DATA_SIZE = 72; +constexpr size_t SRM_REQUIREMENT_SIZE = 12; +constexpr size_t HMAC_SHA256_SIGNATURE_SIZE = 32; + +} // namespace wvoec + +#endif // WV_OEMCRYPTO_TYPES_H_ diff --git a/oemcrypto/include/pst_report.h b/oemcrypto/include/pst_report.h new file mode 100644 index 0000000..33a1288 --- /dev/null +++ b/oemcrypto/include/pst_report.h @@ -0,0 +1,148 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine +// License Agreement. + +/********************************************************************* + * pst_report.h + * + * Reference APIs needed to support Widevine's crypto algorithms. + *********************************************************************/ + +#ifndef PST_REPORT_H_ +#define PST_REPORT_H_ + +#include +#include +#include + +#include "OEMCryptoCENC.h" +#include "string_conversions.h" // needed for htonll64. + +namespace wvutil { + +class Unpacked_PST_Report { + public: + // This object does not own the buffer, and does not check that buffer + // is not null. + Unpacked_PST_Report(uint8_t *buffer) : buffer_(buffer) {} + + // Copy and move semantics of this class is like that of a pointer. + Unpacked_PST_Report(const Unpacked_PST_Report& other) : + buffer_(other.buffer_) {} + + Unpacked_PST_Report& operator=(const Unpacked_PST_Report& other) { + buffer_ = other.buffer_; + return *this; + } + + size_t report_size() const { + return pst_length() + kraw_pst_report_size; + } + + static size_t report_size(size_t pst_length) { + return pst_length + kraw_pst_report_size; + } + + uint8_t status() const { + return static_cast(* (buffer_ + kstatus_offset)); + } + + void set_status(uint8_t value) { + buffer_[kstatus_offset] = value; + } + + uint8_t* signature() { + return buffer_ + ksignature_offset; + } + + uint8_t clock_security_level() const { + return static_cast(* (buffer_ + kclock_security_level_offset)); + } + + void set_clock_security_level(uint8_t value) { + buffer_[kclock_security_level_offset] = value; + } + + uint8_t pst_length() const { + return static_cast(* (buffer_ + kpst_length_offset)); + } + + void set_pst_length(uint8_t value) { + buffer_[kpst_length_offset] = value; + } + + uint8_t padding() const { + return static_cast(* (buffer_ + kpadding_offset)); + } + + void set_padding(uint8_t value) { + buffer_[kpadding_offset] = value; + } + + // In host byte order. + int64_t seconds_since_license_received() const { + int64_t time; + memcpy(&time, buffer_ + kseconds_since_license_received_offset, + sizeof(int64_t)); + return wvutil::ntohll64(time); + } + + // Parameter time is in host byte order. + void set_seconds_since_license_received(int64_t time) const { + time = wvutil::ntohll64(time); + memcpy(buffer_ + kseconds_since_license_received_offset, &time, + sizeof(int64_t)); + } + + // In host byte order. + int64_t seconds_since_first_decrypt() const { + int64_t time; + memcpy(&time, buffer_ + kseconds_since_first_decrypt_offset, + sizeof(int64_t)); + return ntohll64(time); + } + + // Parameter time is in host byte order. + void set_seconds_since_first_decrypt(int64_t time) const { + time = ntohll64(time); + memcpy(buffer_ + kseconds_since_first_decrypt_offset, &time, + sizeof(int64_t)); + } + + // In host byte order. + int64_t seconds_since_last_decrypt() const { + int64_t time; + memcpy(&time, buffer_ + kseconds_since_last_decrypt_offset, + sizeof(int64_t)); + return ntohll64(time); + } + + // Parameter time is in host byte order. + void set_seconds_since_last_decrypt(int64_t time) const { + time = ntohll64(time); + memcpy(buffer_ + kseconds_since_last_decrypt_offset, &time, + sizeof(int64_t)); + } + + uint8_t* pst() { + return (buffer_ + kpst_offset); + } + + private: + uint8_t *buffer_; + + // Size of the PST_Report without the pst string. + static const size_t kraw_pst_report_size = 48; + static const size_t ksignature_offset = 0; + static const size_t kstatus_offset = 20; + static const size_t kclock_security_level_offset = 21; + static const size_t kpst_length_offset = 22; + static const size_t kpadding_offset = 23; + static const size_t kseconds_since_license_received_offset = 24; + static const size_t kseconds_since_first_decrypt_offset = 32; + static const size_t kseconds_since_last_decrypt_offset = 40; + static const size_t kpst_offset = 48; +}; +} // namespace wvutil + +#endif // PST_REPORT_H_ diff --git a/plugin/Android.bp b/plugin/Android.bp new file mode 100644 index 0000000..f64f89c --- /dev/null +++ b/plugin/Android.bp @@ -0,0 +1,36 @@ +cc_library_static { + name: "libwvcasplugins", + local_include_dirs: [ + "include", + ], + export_include_dirs: ["include"], + srcs: [ + "src/crypto_session.cpp", + "src/widevine_cas_session_map.cpp", + "src/widevine_media_cas.cpp", + "src/cas_license.cpp", + "src/ecm_parser.cpp", + "src/ecm_parser_v2.cpp", + "src/ecm_parser_v3.cpp", + "src/emm_parser.cpp", + "src/license_key_status.cpp", + "src/oemcrypto_interface.cpp", + "src/policy_engine.cpp", + "src/widevine_cas_session.cpp", + "src/widevine_media_cas_plugin.cpp", + "src/widevine_cas_api.cpp", + ], + proprietary: true, + shared_libs: [ + "libcrypto", + "libutils", + ], + static_libs: [ + "//vendor/widevine/libwvmediacas/wvutil:libcasutil", + "//vendor/widevine/libwvmediacas/protos:libcas_protos", + ], + header_libs: [ + "//vendor/widevine/libwvmediacas/oemcrypto:oemcastroheaders", + "media_plugin_headers", + ], +} diff --git a/plugin/include/cas_events.h b/plugin/include/cas_events.h new file mode 100644 index 0000000..af9955d --- /dev/null +++ b/plugin/include/cas_events.h @@ -0,0 +1,128 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#ifndef CAS_EVENTS_H +#define CAS_EVENTS_H + +#define PROVISIONING_EVENT_START 1000 +#define LICENSING_EVENT_START 2000 +#define CAS_SESSION_EVENT_START 3000 +#define CAS_QUERY_EVENT_START 4000 +#define CAS_ERROR_EVENT_START 5000 +#define CAS_PARENTAL_CONTROL_EVENT_START 6000 +#define CAS_FINGERPRINTING_EVENT_START 6100 +#define CAS_SERVICE_BLOCKING_EVENT_START 6200 +#define CAS_TEST_EVENT_START 10000 + +typedef enum { + UNKNOWN = 0, + + INDIVIDUALIZATION_REQUEST = PROVISIONING_EVENT_START, + INDIVIDUALIZATION_RESPONSE, + INDIVIDUALIZATION_COMPLETE, + + LICENSE_REQUEST = LICENSING_EVENT_START, + LICENSE_RESPONSE, + CAS_ERROR_DEPRECATED, + LICENSE_RENEWAL_REQUEST, + LICENSE_RENEWAL_RESPONSE, + LICENSE_RENEWAL_URL, + LICENSE_CAS_READY, + LICENSE_CAS_RENEWAL_READY, + LICENSE_REMOVAL, + LICENSE_REMOVED, + ASSIGN_LICENSE_ID, + LICENSE_ID_ASSIGNED, + LICENSE_NEW_EXPIRY_TIME, + MULTI_CONTENT_LICENSE_INFO, + GROUP_LICENSE_INFO, + LICENSE_ENTITLEMENT_PERIOD_UPDATE_REQUEST, + LICENSE_ENTITLEMENT_PERIOD_UPDATE_RESPONSE, + LICENSE_ENTITLEMENT_PERIOD_UPDATED, + + // TODO(jfore): Evaluate removing this event in favor of return status codes + // from + // frameworks/av/media/libstagefright/include/media/stagefright/MediaErrors.h + CAS_ERROR = CAS_ERROR_EVENT_START, + + CAS_SESSION_ID = CAS_SESSION_EVENT_START, + SET_CAS_SOC_ID, + SET_CAS_SOC_DATA, + + UNIQUE_ID = CAS_QUERY_EVENT_START, + QUERY_UNIQUE_ID, + WV_CAS_PLUGIN_VERSION, + QUERY_WV_CAS_PLUGIN_VERSION, + + SET_PARENTAL_CONTROL_AGE = CAS_PARENTAL_CONTROL_EVENT_START, + DEPRECATED_PARENTAL_CONTROL_AGE_UPDATED, + ACCESS_DENIED_BY_PARENTAL_CONTROL, + AGE_RESTRICTION_UPDATED, + + // The content of FINGERPRINTING_INFO events follows TLV (Type (1 byte) - + // Length (2 bytes) - Value) format. See FingerprintingFieldType for possible + // types. A FINGERPRINTING_INFO event contains {one or more CHANNEL, one + // CONTROL}. + FINGERPRINTING_INFO = CAS_FINGERPRINTING_EVENT_START, + // Fingerprinting control info for a session. The content of the event follows + // TLV (Type (1 byte) - Length (2 bytes) - Value) format. See + // SessionFingerprintingFieldType for possible types. It will contain {one + // FINGERPRINTING_CONTROL}. + SESSION_FINGERPRINTING_INFO, + + // The content of SERVICE_BLOCKING_INFO events follows TLV (Type (1 byte) - + // Length (2 bytes) - Value) format. See ServiceBlockingFieldType for possible + // types. A SERVICE_BLOCKING_INFO event contains {one or more CHANNEL, one or + // more DEVICE_GROUP, zero or one START_TIME_SECONDS, one END_TIME_SECONDS}. + SERVICE_BLOCKING_INFO = CAS_SERVICE_BLOCKING_EVENT_START, + // Service blocking device group for a session. The content of the event + // follows TLV (Type (1 byte) - Length (2 bytes) - Value) format. See + // SessionServiceBlockingFieldType for possible types. It will contain {one or + // more SERVICE_BLOCKING_DEVICE_GROUP}. + SESSION_SERVICE_BLOCKING_INFO, + + TEST_FOR_ECHO = + CAS_TEST_EVENT_START, // Request an ECHO response to test events passing. + ECHO, // Respond to TEST_FOR_ECHO. +} CasEventId; + +// Types used inside an FINGERPRINTING_INFO event. +typedef enum { + FINGERPRINTING_CHANNEL = 0, + FINGERPRINTING_CONTROL, +} FingerprintingFieldType; + +// Types used inside an SERVICE_BLOCKING_INFO event. +typedef enum { + SERVICE_BLOCKING_CHANNEL = 0, + SERVICE_BLOCKING_DEVICE_GROUP, + // Epoch time in seconds. Missing of this field or a value of 0 means + // immediate start. + SERVICE_BLOCKING_START_TIME_SECONDS, + SERVICE_BLOCKING_END_TIME_SECONDS, // Epoch time in seconds. +} ServiceBlockingFieldType; + +// Types used inside an SESSION_FINGERPRINTING_CONTROL event. +typedef enum { + SESSION_FINGERPRINTING_CONTROL = 0, +} SessionFingerprintingFieldType; + +// Types used inside an SESSION_SERVICE_BLOCKING_GROUPS event. +typedef enum { + SESSION_SERVICE_BLOCKING_DEVICE_GROUP = 0, +} SessionServiceBlockingFieldType; + +// Types used inside a MULTI_CONTENT_LICENSE_INFO event. +typedef enum { + MULTI_CONTENT_LICENSE_ID = 0, + MULTI_CONTENT_LICENSE_CONTENT_ID, +} MultiContentLicenseFieldType; + +// Types used inside a GROUP_LICENSE_INFO event. +typedef enum { + GROUP_LICENSE_ID = 0, + GROUP_LICENSE_GROUP_ID, +} GroupLicenseFieldType; + +#endif // CAS_EVENTS_H diff --git a/plugin/include/cas_license.h b/plugin/include/cas_license.h new file mode 100644 index 0000000..b79acc3 --- /dev/null +++ b/plugin/include/cas_license.h @@ -0,0 +1,184 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#ifndef CAS_LICENSE_H +#define CAS_LICENSE_H + +#include +#include + +#include "cas_status.h" +#include "crypto_session.h" +#include "policy_engine.h" +#include "timer.h" + +namespace wvcas { + +// CasLicense implements the core functionality needed to interact with service +// to obtain and manage entitlements. +class CasLicense : public wvutil::TimerHandler, public wvcas::CasEventListener { + public: + CasLicense() {} + virtual ~CasLicense() {} + // Initialize CasLicense with a |crypto_session|. |listener| may be null. + virtual CasStatus initialize(std::shared_ptr crypto_session, + CasEventListener* listener); + + // Generate a request to obtain a device certificate for requesting + // entitlements. The generated message is set in |provisioning_request|. + virtual CasStatus GenerateDeviceProvisioningRequest( + std::string* provisioning_request) const; + + // Process a server response containing a device certificate for use in + // requesting entitlements. The contained certificate data will be extracted + // and wrapped for storage. The public key cert will be returned in + // |device_certificate|. The private key information will be wrapped by the + // crypto session and returned in |wrapped_rsa_key|. + // A secure binary file image containing the device cert is returned in + // |device_file| if not nullptr. This file is suitable for storage on a device + virtual CasStatus HandleDeviceProvisioningResponse( + const std::string& signed_provisioning_response, + std::string* device_certificate, std::string* wrapped_rsa_key, + std::string* device_file) const; + + // Generate a request to obtain an EMM (Entitlement Management Message) to + // use to enable processing of ECM(s) (Encryption Management Message). + // |init_data| is widevine metadata about the stream needed in the request. + // |wrapped_rsa_key| and |signed_license_request| are the device certificate + // obtained by HandleDeviceProvisioningResponse. + virtual CasStatus GenerateEntitlementRequest( + const std::string& init_data, const std::string& device_certificate, + const std::string& wrapped_rsa_key, LicenseType license_type, + std::string* signed_license_request); + + // Restores a stored license making the keys available for use. + virtual CasStatus HandleStoredLicense(const std::string& wrapped_rsa_key, + const std::string& license_file); + + // Process a server response containing a EMM for use in the processing of + // ECM(s). + // If |device_file| is not nullptr and the license policy allows a license to + // be stored |device_file| is populated with the bytes of the license secured + // for storage. + virtual CasStatus HandleEntitlementResponse( + const std::string& entitlement_response, std::string* device_file); + + // Process a previously stored device |certificate| and make it available + // for use in an EMM request. + virtual CasStatus HandleStoredDrmCert(const std::string& certificate, + std::string* device_certificate, + std::string* wrapped_rsa_key); + + // Generate an entitlement renewal request message in + // |signed_renewal_request|. + virtual CasStatus GenerateEntitlementRenewalRequest( + const std::string& device_certificate, + std::string* signed_renewal_request); + + // Process a server response containing a EMM renewal. If |device_file| is not + // nullptr and the license policy allows a license renewal to be stored + // |device_file| is populated with the bytes of the license secured for + // storage. + virtual CasStatus HandleEntitlementRenewalResponse( + const std::string& renewal_response, std::string* device_file); + + // Query the license to see if a key is usable. + virtual bool CanDecryptContent(const KeyId& key_id) const; + + // Update the license after handling license remove. Plugin is disabled to + // playback stream, store and renew license. + virtual void UpdateLicenseForLicenseRemove(); + + // Query the license to see if storage is allowed. + virtual bool CanStoreLicense() const; + + // Returns the group id specified in the license. Group id is expected to be + // non-empty if the license is MULTI_CONTENT_LICENSE or GROUP_LICENSE; and + // empty if the license is SINGLE_CONTENT_LICENSE_DEFAULT. + virtual std::string GetGroupId() const; + + // If the license is MULTI_CONTENT_LICENSE, the returned vector contains all + // content ids that the license is for. Returns empty if the license if not + // MULTI_CONTENT_LICENSE. + virtual std::vector GetContentIdList() const; + + // Returns true if the license is MULTI_CONTENT_LICENSE, and false otherwise. + virtual bool IsMultiContentLicense() const; + + // Returns true if the license is GROUP_LICENSE, and false otherwise. + virtual bool IsGroupLicense() const; + + // Policy timer implentation. + void OnTimerEvent() override; + + // Event listener implementation. + void OnSessionRenewalNeeded() override; + + void OnSessionKeysChange(const KeyStatusMap& keys_status, + bool has_new_usable_key) override; + + void OnExpirationUpdate(int64_t new_expiry_time_seconds) override; + + void OnLicenseExpiration() override; + + void OnNewRenewalServerUrl(const std::string& renewal_server_url) override; + + void OnAgeRestrictionUpdated(const WvCasSessionId& sessionId, + uint8_t ecm_age_restriction) override; + void OnFingerprintingUpdated(const CasData& fingerprinting) override; + void OnServiceBlockingUpdated(const CasData& service_blocking) override; + + void OnSessionFingerprintingUpdated(const WvCasSessionId& sessionId, + const CasData& fingerprinting) override; + + void OnSessionServiceBlockingUpdated( + const WvCasSessionId& sessionId, + const CasData& service_blocking) override; + + void OnEntitlementPeriodUpdateNeeded( + const std::string& signed_license_request) override; + + // Query to see if the license is expired. + virtual bool IsExpired() const; + + // Notify the license that playback decryption has begun. + virtual void BeginDecryption(); + + // Returns NoError if a valid entitlement period index exists in + // |license_file|. The index will be assigned to |entitlement_period_index|. + static CasStatus GetEntitlementPeriodIndexFromStoredLicense( + const std::string& license_file, uint32_t& entitlement_period_index); + + CasLicense(const CasLicense&) = delete; + CasLicense& operator=(const CasLicense&) = delete; + + private: + CasStatus GenerateDeviceProvisioningRequestWithKeybox( + std::string* provisioning_request) const; + CasStatus GenerateDeviceProvisioningRequestWithOEMCert() const; + CasStatus InstallLicense(const std::string& session_key, + const std::string& serialized_license, + const std::string& core_message, + const std::string& signature); + CasStatus InstallLicenseRenewal(const std::string& serialized_license, + const std::string& core_message, + const std::string& signature); + + virtual std::unique_ptr GetPolicyEngine(); + + std::unique_ptr policy_engine_; + std::shared_ptr crypto_session_; + CasEventListener* event_listener_ = nullptr; + video_widevine::License license_; + std::string emm_request_; + std::string emm_response_; + std::string renewal_request_; + std::string renewal_response_; + std::string init_data_; + bool is_renewal_in_license_file_ = false; +}; + +} // namespace wvcas + +#endif // CAS_LICENSE_H diff --git a/plugin/include/cas_media_id.h b/plugin/include/cas_media_id.h new file mode 100644 index 0000000..b20bc0d --- /dev/null +++ b/plugin/include/cas_media_id.h @@ -0,0 +1,25 @@ +#ifndef CAS_MEDIA_ID_H +#define CAS_MEDIA_ID_H + +namespace wvcas { + +class CasMediaId { + protected: + CasMediaId() {} + + public: + CasMediaId(const CasMediaId&) = delete; + CasMediaId& operator=(const CasMediaId&) = delete; + virtual ~CasMediaId() {} + static std::unique_ptr create(); + virtual CasStatus initialize(const std::string& init_data) = 0; + virtual const std::string content_id() = 0; + virtual const std::string provider_id() = 0; + virtual bool is_entitlement_rotation_enabled() { return false; } + virtual uint32_t entitlement_period_index() = 0; + virtual std::string get_init_data() = 0; +}; + +} // namespace wvcas + +#endif // CAS_MEDIA_ID_H diff --git a/plugin/include/cas_status.h b/plugin/include/cas_status.h new file mode 100644 index 0000000..be8ec1a --- /dev/null +++ b/plugin/include/cas_status.h @@ -0,0 +1,53 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#ifndef CAS_STATUS_H +#define CAS_STATUS_H + +#include + +namespace wvcas { + +// TODO(jfore): Add more detailed error status codes. +enum class CasStatusCode : int32_t { + kUnknownError = 0, + kNoError = 1, + kCryptoInterfaceError = 2, + kCryptoSessionError = 3, + kCasLicenseError = 4, + kIndividualizationError = 5, + kInvalidParameter = 6, + kDecryptionError = 7, + kKeyNotFound = 8, + kSessionNotFound = 9, + kUnknownLicenseType = 10, + kLicenseFileParseError = 11, + kInvalidLicenseFile = 12, + kInvalidPesData = 13, + kDeferedEcmProcessing = 14, + kAccessDeniedByParentalControl = 15, + kUnknownEvent = 16, + kOEMCryptoVersionMismatch = 17, +}; + +class CasStatus { + public: + CasStatus(CasStatusCode status = CasStatusCode::kNoError, + const std::string& err_string = std::string()) + : status_(status), err_string_(err_string) {} + static CasStatus OkStatus() { return CasStatus(); } + virtual ~CasStatus() {} + + virtual CasStatusCode status_code() const { return status_; } + virtual const std::string& error_string() const { return err_string_; } + virtual bool ok() const { return status_ == CasStatusCode::kNoError; } + + private: + CasStatusCode status_; + std::string err_string_; +}; + +} // namespace wvcas + +#endif // CAS_STATUS_H diff --git a/plugin/include/cas_types.h b/plugin/include/cas_types.h new file mode 100644 index 0000000..bfa5370 --- /dev/null +++ b/plugin/include/cas_types.h @@ -0,0 +1,116 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#ifndef CAS_TYPES_H +#define CAS_TYPES_H + +#include +#include +#include + +static const int kCryptoSessionErrorStart = 1000; + +namespace wvcas { + +typedef std::vector WvCasSessionId; + +enum class LicenseType { + kStreaming = 0, + kOffline = 1, + // License type decision is left to the provider. + kAutomatic = 2, +}; + +typedef enum { + ProvisioningError = 0, // Device cannot be provisioned. + DrmCertificate = 1, // Device has baked in DRM certificate + // (level 3 only) + Keybox = 2, // Device has factory installed unique keybox. + OEMCertificate = 3 // Device has factory installed OEM certificate. +} CasProvisioningMethod; + +enum class CryptoMode { + kInvalid = -1, + kAesCBC = 0, + kAesCTR = 1, + kDvbCsa2 = 2, + kDvbCsa3 = 3, + kAesOFB = 4, + kAesSCTE = 5, + kAesECB = 6, +}; + +enum KeyStatus { + kKeyStatusKeyUnknown, + kKeyStatusUsable, + kKeyStatusExpired, + kKeyStatusOutputNotAllowed, + kKeyStatusPending, + kKeyStatusInternalError, +}; + +enum SecurityLevel { + kSecurityLevelUninitialized, + kSecurityLevelL1, + kSecurityLevelL2, + kSecurityLevelL3, + kSecurityLevelUnknown +}; + +typedef std::vector CasEcm; +typedef std::vector CasEmm; +typedef std::vector CasData; +typedef std::string KeyId; +typedef std::map KeyStatusMap; + +// Listener for events from the policy engine. +class CasEventListener { + public: + CasEventListener() {} + virtual ~CasEventListener() {} + + // Notify listeners that a license renewal is needed. + virtual void OnSessionRenewalNeeded() = 0; + + // Notify listeners that the keys have changed state. + virtual void OnSessionKeysChange(const KeyStatusMap& keys_status, + bool has_new_usable_key) = 0; + + // |new_expiry_time_seconds| of 0 means "never expires". + virtual void OnExpirationUpdate(int64_t new_expiry_time_seconds) = 0; + + // Notify listeners of the current renewal url. + virtual void OnNewRenewalServerUrl(const std::string& renewal_server_url) = 0; + + // Notify listeners of current license is expired. + virtual void OnLicenseExpiration() = 0; + + // Notify listeners of new age restriction value in processed ECM. + virtual void OnAgeRestrictionUpdated(const WvCasSessionId& sessionId, + uint8_t ecm_age_restriction) = 0; + + // Notifies listeners of new session fingerprinting info. + virtual void OnSessionFingerprintingUpdated( + const WvCasSessionId& sessionId, const CasData& fingerprinting) = 0; + + // Notifies listeners of new session service blocking info. + virtual void OnSessionServiceBlockingUpdated( + const WvCasSessionId& sessionId, const CasData& service_blocking) = 0; + + // Notifies listeners of new fingerprinting info. + virtual void OnFingerprintingUpdated(const CasData& fingerprinting) = 0; + + // Notifies listeners of new service blocking info. + virtual void OnServiceBlockingUpdated(const CasData& service_blocking) = 0; + + virtual void OnEntitlementPeriodUpdateNeeded( + const std::string& signed_license_request) = 0; + + CasEventListener(const CasEventListener&) = delete; + CasEventListener& operator=(const CasEventListener&) = delete; +}; + +} // namespace wvcas + +#endif // CAS_TYPES_H diff --git a/plugin/include/cas_util.h b/plugin/include/cas_util.h new file mode 100644 index 0000000..bc956d1 --- /dev/null +++ b/plugin/include/cas_util.h @@ -0,0 +1,21 @@ +// Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#ifndef CAS_UTIL_H_ +#define CAS_UTIL_H_ + +#include + +#if __cplusplus >= 201402L || \ + (defined __cpp_lib_make_unique && __cpp_lib_make_unique >= 201304L) || \ + (defined(_MSC_VER) && _MSC_VER >= 1900) +using std::make_unique; +#else +template +std::unique_ptr make_unique(Args&&... args) { + return std::unique_ptr(new T(std::forward(args)...)); +} +#endif + +#endif // CAS_UTIL_H_ diff --git a/plugin/include/crypto_session.h b/plugin/include/crypto_session.h new file mode 100644 index 0000000..60cef05 --- /dev/null +++ b/plugin/include/crypto_session.h @@ -0,0 +1,310 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#ifndef CRYPTO_SESSION_H +#define CRYPTO_SESSION_H + +#include +#include +#include +#include + +#include "OEMCryptoCENC.h" +#include "cas_status.h" +#include "cas_types.h" +#include "oemcrypto_interface.h" +#include "rw_lock.h" + +namespace wvcas { + +struct KeySlot { + std::vector key_id; + std::vector entitlement_key_id; + std::vector wrapped_key; + std::vector wrapped_key_iv; + std::vector content_iv; + CryptoMode cipher_mode; +}; + +struct SubSample { + uint32_t num_bytes_of_clear; + uint32_t num_bytes_of_encrypted; +}; + +typedef OEMCrypto_HDCP_Capability HdcpCapability; + +class CryptoLock { + public: + CryptoLock(){}; + // These methods should be used to take the various CryptoSession mutexes in + // preference to taking the mutexes directly. + // + // A lock should be taken on the Static Field Mutex before accessing any of + // CryptoSession's non-atomic static fields. It can be taken as a reader or as + // a writer, depending on how you will be accessing the static fields. + // + // Before calling into OEMCrypto, code must take locks on the OEMCrypto Mutex + // and/or the OEMCrypto Session Mutex. Which of them should be taken and how + // depends on the OEMCrypto function being called; consult the OEMCrypto + // specification's threading guarantees before making any calls. The OEMCrypto + // specification defines several classes of functions for the purposes of + // parallelism. The methods below lock the OEMCrypto Mutex and OEMCrypto + // Session Mutex in the correct order and manner to fulfill the guarantees in + // the specification. + // + // For this function class... | ...use this locking method + // ------------------------------+--------------------------- + // Initialization & Termination | WithOecWriteLock() + // Property | WithOecReadLock() + // Session Initialization | WithOecWriteLock() + // Usage Table | WithOecWriteLock() + // Session | WithOecSessionLock() + // + // Note that accessing |key_session_| often accesses the OEMCrypto session, so + // WithOecSessionLock() should be used before accessing |key_session_| as + // well. + // + // If a function needs to take a lock on both the Static Field Mutex and some + // of the OEMCrypto mutexes simultaneously, it must *always* lock the Static + // Field Mutex before the OEMCrypto mutexes. + // + // In general, all locks should only be held for the minimum time necessary + // (e.g. a lock on the OEMCrypto mutexes should only be held for the duration + // of a single call into OEMCrypto) unless there is a compelling argument + // otherwise, such as making two calls into OEMCrypto immediately after each + // other. + template + static auto WithStaticFieldWriteLock(const char* tag, Func body) + -> decltype(body()); + template + static auto WithStaticFieldReadLock(const char* tag, Func body) + -> decltype(body()); + template + static auto WithOecWriteLock(const char* tag, Func body) -> decltype(body()); + template + static auto WithOecReadLock(const char* tag, Func body) -> decltype(body()); + template + auto WithOecSessionLock(const char* tag, Func body) -> decltype(body()); + + private: + // The locking methods above should be used in preference to taking these + // mutexes directly. If code takes these manually and needs to take more + // than one, it must *always* take them in the order they are defined here. + static wvutil::shared_mutex static_field_mutex_; + static wvutil::shared_mutex oem_crypto_mutex_; + std::mutex oem_crypto_session_mutex_; +}; + +class CryptoInterface { + CryptoInterface(); + + public: + virtual ~CryptoInterface(); + + virtual OEMCryptoResult OEMCrypto_OpenSession(OEMCrypto_SESSION* session); + virtual OEMCryptoResult OEMCrypto_CloseSession(OEMCrypto_SESSION session); + virtual OEMCrypto_ProvisioningMethod OEMCrypto_GetProvisioningMethod(); + virtual OEMCryptoResult OEMCrypto_GetKeyData(uint8_t* keyData, + size_t* keyDataLength); + virtual uint32_t OEMCrypto_SupportedCertificates(); + virtual OEMCryptoResult OEMCrypto_GenerateNonce(OEMCrypto_SESSION session, + uint32_t* nonce); + virtual OEMCryptoResult OEMCrypto_GenerateDerivedKeys( + OEMCrypto_SESSION session, const uint8_t* mac_key_context, + uint32_t mac_key_context_length, const uint8_t* enc_key_context, + uint32_t enc_key_context_length); + virtual OEMCryptoResult OEMCrypto_PrepAndSignLicenseRequest( + OEMCrypto_SESSION session, uint8_t* message, size_t message_length, + size_t* core_message_size, uint8_t* signature, size_t* signature_length); + virtual OEMCryptoResult OEMCrypto_PrepAndSignRenewalRequest( + OEMCrypto_SESSION session, uint8_t* message, size_t message_length, + size_t* core_message_size, uint8_t* signature, size_t* signature_length); + virtual OEMCryptoResult OEMCrypto_PrepAndSignProvisioningRequest( + OEMCrypto_SESSION session, uint8_t* message, size_t message_length, + size_t* core_message_size, uint8_t* signature, size_t* signature_length); + virtual OEMCryptoResult OEMCrypto_LoadProvisioning( + OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, + size_t core_message_length, const uint8_t* signature, + size_t signature_length, uint8_t* wrapped_private_key, + size_t* wrapped_private_key_length); + virtual OEMCryptoResult OEMCrypto_GetOEMPublicCertificate( + OEMCrypto_SESSION session, uint8_t* public_cert, + size_t* public_cert_length); + virtual OEMCryptoResult OEMCrypto_LoadDRMPrivateKey( + OEMCrypto_SESSION session, OEMCrypto_PrivateKeyType key_type, + const uint8_t* wrapped_rsa_key, size_t wrapped_rsa_key_length); + virtual OEMCryptoResult OEMCrypto_GenerateRSASignature( + OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, + uint8_t* signature, size_t* signature_length, + RSA_Padding_Scheme padding_scheme); + virtual OEMCryptoResult OEMCrypto_DeriveKeysFromSessionKey( + OEMCrypto_SESSION session, const uint8_t* enc_session_key, + size_t enc_session_key_length, const uint8_t* mac_key_context, + size_t mac_key_context_length, const uint8_t* enc_key_context, + size_t enc_key_context_length); + virtual OEMCryptoResult OEMCrypto_LoadLicense(OEMCrypto_SESSION session, + const uint8_t* message, + size_t message_length, + size_t core_message_length, + const uint8_t* signature, + size_t signature_length); + virtual OEMCryptoResult OEMCrypto_LoadRenewal(OEMCrypto_SESSION session, + const uint8_t* message, + size_t message_length, + size_t core_message_length, + const uint8_t* signature, + size_t signature_length); + virtual OEMCryptoResult OEMCrypto_LoadCasECMKeys( + OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, + const OEMCrypto_EntitledContentKeyObject* even_key, + const OEMCrypto_EntitledContentKeyObject* odd_key); + virtual OEMCryptoResult OEMCrypto_GetHDCPCapability( + OEMCrypto_HDCP_Capability* current, OEMCrypto_HDCP_Capability* max); + virtual OEMCryptoResult OEMCrypto_GetDeviceID(uint8_t* deviceID, + size_t* idLength); + virtual const char* OEMCrypto_SecurityLevel(); + virtual OEMCryptoResult OEMCrypto_CreateEntitledKeySession( + OEMCrypto_SESSION session, OEMCrypto_SESSION* entitled_key_session_id); + virtual OEMCryptoResult OEMCrypto_RemoveEntitledKeySession( + OEMCrypto_SESSION entitled_key_session_id); + virtual OEMCryptoResult OEMCrypto_ReassociateEntitledKeySession( + OEMCrypto_SESSION key_sid, OEMCrypto_SESSION oec_sid); + virtual uint32_t OEMCrypto_APIVersion(); + virtual OEMCryptoResult OEMCrypto_GetOEMKeyToken( + OEMCrypto_SESSION key_session, uint8_t* key_token, + size_t* key_token_length); + virtual OEMCryptoResult OEMCrypto_GetSignatureHashAlgorithm( + OEMCrypto_SESSION session, OEMCrypto_SignatureHashAlgorithm* algorithm); + + // This is the factory method used to enable the oemcrypto interface. + static OEMCryptoResult create(std::unique_ptr* init) { + // This is *the* oemcrypto interface used in the normal running. There is + // only one and there is not need to destroy it. + static OEMCryptoInterface oemcrypto_interface; + return create_internal(&oemcrypto_interface, init); + } + + // This initializer factory method is templated to allow tests to pass in + // a mocked OEMCryptoInterface. The caller retains ownership of + // |oemcrypto_interface|. + template + static OEMCryptoResult create(std::unique_ptr* init, + CI* oemcrypto_interface) { + return create_internal(oemcrypto_interface, init); + } + + CryptoInterface(const CryptoInterface&) = delete; + CryptoInterface& operator=(const CryptoInterface&) = delete; + + private: + static OEMCryptoResult create_internal( + OEMCryptoInterface* oemcrypto_interface, + std::unique_ptr* init); + + static bool initialized_; + static int session_count_; + static std::unique_ptr lock_; + OEMCryptoInterface* oemcrypto_interface_; +}; + +class SupportedCertificates { + public: + explicit SupportedCertificates(uint32_t supported) : supported_(supported) {} + bool rsa_2048bit() { return OEMCrypto_Supports_RSA_2048bit & supported_; } + bool rsa_3072bit() { return OEMCrypto_Supports_RSA_3072bit & supported_; } + bool rsa_CASTbit() { return OEMCrypto_Supports_RSA_CAST & supported_; } + + private: + uint32_t supported_; +}; + +// CryptoSession implements the core methods need to interface with OEMCrypto. +class CryptoSession { + public: + explicit CryptoSession(); + virtual ~CryptoSession(); + virtual CasStatus initialize(); + virtual CasStatus reset(); + virtual CasStatus close(); + virtual CasProvisioningMethod provisioning_method(); + virtual CasStatus GetKeyData(uint8_t* keyData, size_t* keyDataLength); + virtual SupportedCertificates supported_certificates(); + virtual CasStatus GenerateNonce(uint32_t* nonce); + virtual CasStatus GenerateDerivedKeys(const uint8_t* mac_key_context, + uint32_t mac_key_context_length, + const uint8_t* enc_key_context, + uint32_t enc_key_context_length); + virtual CasStatus PrepareAndSignLicenseRequest( + const std::string& message, std::string* core_message, + std::string* signature, bool& should_specify_algorithm, + OEMCrypto_SignatureHashAlgorithm& algorithm); + virtual CasStatus PrepareAndSignRenewalRequest(const std::string& message, + std::string* core_message, + std::string* signature); + virtual CasStatus PrepareAndSignProvisioningRequest( + const std::string& message, std::string* core_message, + std::string* signature, bool& should_specify_algorithm, + OEMCrypto_SignatureHashAlgorithm& algorithm); + virtual CasStatus LoadProvisioning(const std::string& signed_message, + const std::string& core_message, + const std::string& signature, + std::string* wrapped_private_key); + virtual CasStatus GetOEMPublicCertificate(uint8_t* public_cert, + size_t* public_cert_length); + virtual CasStatus LoadDeviceRSAKey(const uint8_t* wrapped_rsa_key, + size_t wrapped_rsa_key_length); + virtual CasStatus GenerateRSASignature(const uint8_t* message, + size_t message_length, + uint8_t* signature, + size_t* signature_length, + RSA_Padding_Scheme padding_scheme); + virtual CasStatus DeriveKeysFromSessionKey(const uint8_t* enc_session_key, + size_t enc_session_key_length, + const uint8_t* mac_key_context, + size_t mac_key_context_length, + const uint8_t* enc_key_context, + size_t enc_key_context_length); + virtual CasStatus LoadLicense(const std::string& signed_message, + const std::string& core_message, + const std::string& signature); + virtual CasStatus LoadRenewal(const std::string& signed_message, + const std::string& core_message, + const std::string& signature); + // LoadCasECMKeys loads the ecm keys into the crypto library making them + // available for use. + // |odd_key| - if not null, contains control word data. + // |even_key| - if not null, contains control word data. + virtual CasStatus LoadCasECMKeys(OEMCrypto_SESSION session, + const KeySlot* even_key, + const KeySlot* odd_key); + virtual bool GetHdcpCapabilities(HdcpCapability* current, + HdcpCapability* max); + virtual CasStatus GetDeviceID(std::string* buffer); + virtual const char* SecurityLevel(); + virtual CasStatus CreateEntitledKeySession( + OEMCrypto_SESSION* entitled_key_session_id); + virtual CasStatus RemoveEntitledKeySession( + OEMCrypto_SESSION entitled_key_session_id); + virtual CasStatus ReassociateEntitledKeySession( + OEMCrypto_SESSION entitled_key_session_id); + virtual CasStatus APIVersion(uint32_t* api_version); + virtual CasStatus GetOEMKeyToken(OEMCrypto_SESSION entitled_key_session_id, + std::vector& token); + + CryptoSession(const CryptoSession&) = delete; + CryptoSession& operator=(const CryptoSession&) = delete; + + private: + virtual OEMCryptoResult getCryptoInterface( + std::unique_ptr* interface); + + // TODO(jfore, widevine-eng): Merge CryptoInterface into CryptoSession and + // drop this shared pointer. + std::unique_ptr crypto_interface_; + OEMCrypto_SESSION session_; +}; + +} // namespace wvcas + +#endif // CRYPTO_SESSION_H diff --git a/plugin/include/ecm_parser.h b/plugin/include/ecm_parser.h new file mode 100644 index 0000000..4ce731e --- /dev/null +++ b/plugin/include/ecm_parser.h @@ -0,0 +1,59 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#ifndef ECM_PARSER_H +#define ECM_PARSER_H + +#include +#include + +#include "cas_types.h" +#include "media_cas.pb.h" + +namespace wvcas { + +enum class KeySlotId { kEvenKeySlot, kOddKeySlot }; + +// EcmParser allows random access to the fields of an ECM. +class EcmParser { + public: + EcmParser() = default; + virtual ~EcmParser() {} + + // The EcmParser factory method. + // Validates the ecm. If validations is successful returns true and constructs + // an EcmParser in |parser| using |ecm|. + static std::unique_ptr Create(const CasEcm& ecm); + + // Accessor methods. + virtual uint8_t version() const = 0; + virtual CryptoMode crypto_mode() const = 0; + virtual bool rotation_enabled() const = 0; + virtual size_t content_iv_size() const = 0; + virtual uint8_t age_restriction() const = 0; + virtual std::vector entitlement_key_id(KeySlotId id) const = 0; + virtual std::vector content_key_id(KeySlotId id) const = 0; + virtual std::vector wrapped_key_data(KeySlotId id) const = 0; + virtual std::vector wrapped_key_iv(KeySlotId id) const = 0; + virtual std::vector content_iv(KeySlotId id) const = 0; + + // Process group content keys instead of the normal content keys. + virtual bool set_group_id(const std::string& group_id) = 0; + + virtual bool has_fingerprinting() const = 0; + virtual video_widevine::Fingerprinting fingerprinting() const = 0; + virtual bool has_service_blocking() const = 0; + virtual video_widevine::ServiceBlocking service_blocking() const = 0; + // The serialized payload that the signature is calculated on. + virtual std::string ecm_serialized_payload() const = 0; + virtual std::string signature() const = 0; + + virtual bool is_entitlement_rotation_enabled() const = 0; + virtual uint32_t entitlement_period_index() const = 0; + virtual uint32_t entitlement_rotation_window_left() const = 0; +}; + +} // namespace wvcas + +#endif // ECM_PARSER_H diff --git a/plugin/include/ecm_parser_v2.h b/plugin/include/ecm_parser_v2.h new file mode 100644 index 0000000..77e08e1 --- /dev/null +++ b/plugin/include/ecm_parser_v2.h @@ -0,0 +1,85 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#ifndef ECM_PARSER_V2_H +#define ECM_PARSER_V2_H + +#include +#include + +#include "cas_types.h" +#include "ecm_parser.h" + +namespace wvcas { + +struct EcmKeyData; + +// EcmParserV2 allows random access to the fields of an ECM version 2 and under. +// It should be initialized via EcmParser factory create only. +class EcmParserV2 : public EcmParser { + public: + ~EcmParserV2() override = default; + EcmParserV2(const EcmParserV2&) = delete; + EcmParserV2& operator=(const EcmParserV2&) = delete; + + // The EcmParserV2 factory method. + // |ecm| must be Widevine ECM v2 or under without section header. + // Validates the ecm. The only validation performed is to ensure that the ecm + // passed in is large enough to hold a single key entry. If validations is + // successful returns true and constructs an EcmParserV2 in |parser| using + // |ecm|. + static bool create(const CasEcm& cas_ecm, + std::unique_ptr* parser); + + // Accessor methods. + uint8_t version() const override; + CryptoMode crypto_mode() const override; + bool rotation_enabled() const override; + size_t content_iv_size() const override; + uint8_t age_restriction() const override; + std::vector entitlement_key_id(KeySlotId id) const override; + std::vector content_key_id(KeySlotId id) const override; + std::vector wrapped_key_data(KeySlotId id) const override; + std::vector wrapped_key_iv(KeySlotId id) const override; + std::vector content_iv(KeySlotId id) const override; + + // Group keys not supported in v2. + bool set_group_id(const std::string& group_id) override { + return group_id.empty(); + }; + + // ECM v2 or under does not have these fields. + bool has_fingerprinting() const override { return false; } + video_widevine::Fingerprinting fingerprinting() const override { + video_widevine::Fingerprinting fingerprinting; + return fingerprinting; + } + bool has_service_blocking() const override { return false; }; + video_widevine::ServiceBlocking service_blocking() const override { + video_widevine::ServiceBlocking service_blocking; + return service_blocking; + } + std::string ecm_serialized_payload() const override { return ""; } + std::string signature() const override { return ""; } + + bool is_entitlement_rotation_enabled() const override { return false; } + uint32_t entitlement_period_index() const override { return 0; } + uint32_t entitlement_rotation_window_left() const override { return 0; } + + private: + // Constructs an EcmParserV2 using |ecm|. + explicit EcmParserV2(const CasEcm& ecm); + + size_t key_data_size() const; + // Returns false if the ecm used to construct the object is not a valid size. + // TODO(jfore): Add validation using the version field. + bool is_valid_size() const; + const EcmKeyData* key_slot_data(KeySlotId id) const; + + CasEcm ecm_; +}; + +} // namespace wvcas + +#endif // ECM_PARSER_V2_H diff --git a/plugin/include/ecm_parser_v3.h b/plugin/include/ecm_parser_v3.h new file mode 100644 index 0000000..17433bb --- /dev/null +++ b/plugin/include/ecm_parser_v3.h @@ -0,0 +1,68 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#ifndef ECM_PARSER_V3_H +#define ECM_PARSER_V3_H + +#include +#include + +#include "cas_types.h" +#include "ecm_parser.h" +#include "media_cas.pb.h" + +namespace wvcas { + +// EcmParser allows random access to the fields of an ECM. +class EcmParserV3 : public EcmParser { + public: + ~EcmParserV3() override = default; + EcmParserV3(const EcmParserV3&) = delete; + EcmParserV3& operator=(const EcmParserV3&) = delete; + + // The EcmParserV3 factory method. + // |ecm| must be Widevine ECM v3 (or higher if compatible) without section + // header. Validates the ecm. If validations is successful returns an + // EcmParserV3, otherwise an nullptr. + static std::unique_ptr Create(const CasEcm& ecm); + + // Accessor methods. + uint8_t version() const override; + CryptoMode crypto_mode() const override; + bool rotation_enabled() const override; + size_t content_iv_size() const override; + uint8_t age_restriction() const override; + std::vector entitlement_key_id(KeySlotId id) const override; + std::vector content_key_id(KeySlotId id) const override; + std::vector wrapped_key_data(KeySlotId id) const override; + std::vector wrapped_key_iv(KeySlotId id) const override; + std::vector content_iv(KeySlotId id) const override; + + bool set_group_id(const std::string& group_id) override; + + bool has_fingerprinting() const override; + video_widevine::Fingerprinting fingerprinting() const override; + bool has_service_blocking() const override; + video_widevine::ServiceBlocking service_blocking() const override; + // The serialized payload that the signature is calculated on. + std::string ecm_serialized_payload() const override; + std::string signature() const override; + + bool is_entitlement_rotation_enabled() const override; + uint32_t entitlement_period_index() const override; + uint32_t entitlement_rotation_window_left() const override; + + private: + // Constructs an EcmParserV3 using |ecm|. + EcmParserV3(video_widevine::SignedEcmPayload signed_ecm_payload, + video_widevine::EcmPayload ecm_payload); + video_widevine::SignedEcmPayload signed_ecm_payload_; + video_widevine::EcmPayload ecm_payload_; + video_widevine::EcmKeyData even_key_data_; + video_widevine::EcmKeyData odd_key_data_; +}; + +} // namespace wvcas + +#endif // ECM_PARSER_V3_H diff --git a/plugin/include/emm_parser.h b/plugin/include/emm_parser.h new file mode 100644 index 0000000..05f0362 --- /dev/null +++ b/plugin/include/emm_parser.h @@ -0,0 +1,49 @@ +// Copyright 2020 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#ifndef EMM_PARSER_H +#define EMM_PARSER_H + +#include +#include + +#include "cas_types.h" +#include "media_cas.pb.h" + +namespace wvcas { + +using video_widevine::EmmPayload; + +class EmmParser { + public: + EmmParser(const EmmParser&) = delete; + EmmParser& operator=(const EmmParser&) = delete; + virtual ~EmmParser() = default; + + // The EmmParser factory method. + // The methods validates the passed in |emm|. If validation is successful, it + // constructs and returns an EmmParser. Otherwise, nullptr is returned. + static std::unique_ptr Create(const CasEmm& emm); + + // Accessor methods. + virtual uint64_t timestamp() const { return timestamp_; } + virtual std::string signature() const { return signature_; } + virtual EmmPayload emm_payload() const { return emm_payload_; }; + + protected: + // Called by the factory create and unit test. + EmmParser() = default; + + private: + bool Parse(int start_index, const CasEmm& emm); + + uint8_t version_; + uint64_t timestamp_; + EmmPayload emm_payload_; + std::string signature_; +}; + +} // namespace wvcas + +#endif // EMM_PARSER_H \ No newline at end of file diff --git a/plugin/include/license_key_status.h b/plugin/include/license_key_status.h new file mode 100644 index 0000000..79ed59b --- /dev/null +++ b/plugin/include/license_key_status.h @@ -0,0 +1,210 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#ifndef WIDEVINE_CAS_LICENSE_KEY_STATUS_H_ +#define WIDEVINE_CAS_LICENSE_KEY_STATUS_H_ + +#include + +#include "crypto_session.h" +#include "license_protocol.pb.h" + +namespace wvcas { + +class LicenseKeyStatus; + +using video_widevine::WidevinePsshData_EntitledKey; + +// Policy engine HDCP enforcement +static constexpr uint32_t HDCP_UNSPECIFIED_VIDEO_RESOLUTION = 0; +static constexpr int64_t HDCP_DEVICE_CHECK_INTERVAL = 10; + +enum KeySecurityLevel { + kKeySecurityLevelUnset, + kSoftwareSecureCrypto, + kSoftwareSecureDecode, + kHardwareSecureCrypto, + kHardwareSecureDecode, + kHardwareSecureAll, + kKeySecurityLevelUnknown, +}; + +class KeyAllowedUsage { + public: + KeyAllowedUsage() { Clear(); } + + bool Valid() const { return valid_; } + void SetValid() { valid_ = true; } + + void Clear() { + decrypt_to_clear_buffer = false; + decrypt_to_secure_buffer = false; + generic_encrypt = false; + generic_decrypt = false; + generic_sign = false; + generic_verify = false; + key_security_level_ = kKeySecurityLevelUnset; + valid_ = false; + } + + bool Equals(const KeyAllowedUsage& other) { + if (!valid_ || !other.Valid() || + decrypt_to_clear_buffer != other.decrypt_to_clear_buffer || + decrypt_to_secure_buffer != other.decrypt_to_secure_buffer || + generic_encrypt != other.generic_encrypt || + generic_decrypt != other.generic_decrypt || + generic_sign != other.generic_sign || + generic_verify != other.generic_verify || + key_security_level_ != other.key_security_level_) { + return false; + } + return true; + } + + bool decrypt_to_clear_buffer; + bool decrypt_to_secure_buffer; + bool generic_encrypt; + bool generic_decrypt; + bool generic_sign; + bool generic_verify; + KeySecurityLevel key_security_level_; + + private: + bool valid_; +}; + +// Holds all content and operator session keys for a session. +class LicenseKeys { + public: + LicenseKeys() {} + virtual ~LicenseKeys() { Clear(); } + + virtual bool Empty() { return key_statuses_.empty(); } + + // Returns true if the key is a content key (not an operator session key) + virtual bool IsContentKey(const KeyId& key_id); + + // Returns true if the key is currently usable for content decryption. + virtual bool CanDecryptContent(const KeyId& key_id); + + // Returns the allowed usages for a key. + virtual bool GetAllowedUsage(const KeyId& key_id, + KeyAllowedUsage* allowed_usage); + + // Applies a new status to each content key. + // Returns true if any statuses changed, and sets new_usable_keys to + // true if the status changes resulted in keys becoming usable. + virtual bool ApplyStatusChange(KeyStatus new_status, bool* new_usable_keys); + + // Returns current KeyStatus for the given key. + // Returns kKeyStatusKeyUnknown if key_id not found. + virtual KeyStatus GetKeyStatus(const KeyId& key_id); + + // Populates a KeyStatusMap with the current content keys. + virtual void ExtractKeyStatuses(KeyStatusMap* content_keys); + + // Determines whether the specified key can be used under the current + // resolution and/or hdcp constraints. If no constraints have been applied + // to the key, returns true. + virtual bool MeetsConstraints(const KeyId& key_id); + + // Applies a resolution and/or hdcp change to each key, updating their + // useability under their constraints. + virtual void ApplyConstraints(uint32_t new_resolution, + HdcpCapability new_hdcp_level); + + // Extracts the keys from a license and makes them available for + // querying usage and constraint settings. + virtual void SetFromLicense(const video_widevine::License& license); + + // Sets the keys from the input entitled key data. + virtual void SetEntitledKeys( + const std::vector& keys); + + LicenseKeys(const LicenseKeys&) = delete; + LicenseKeys& operator=(const LicenseKeys&) = delete; + + private: + typedef ::video_widevine::License::KeyContainer KeyContainer; + typedef std::map::const_iterator + LicenseKeyStatusIterator; + + void Clear(); + + bool is_initialized_; + // |key_statuses_| can hold either content key statuses, or entitlement key + // statuses. + std::map key_statuses_; + // |content_keyid_to_entitlement_key_id_| maps a content key id to an + // entitlement_key_id. The resulting key id can be used to obtain the current + // key status from |key_statuses_| when using entitlement key licensing. + std::map content_keyid_to_entitlement_key_id_; +}; + +// Holds the current license status of a key. +class LicenseKeyStatus { + friend class LicenseKeys; + + public: + // Returns true if the key is a content key (not an operator session key) + virtual bool IsContentKey() { return is_content_key_; } + + // Returns true if the key is currently usable for content decryption + virtual bool CanDecryptContent(); + + // Returns the usages allowed for this key. + virtual bool GetAllowedUsage(KeyAllowedUsage* allowed_usage); + + // Returns the current status of the key. + virtual KeyStatus GetKeyStatus() const { return key_status_; } + + // Applies a new status to this key. + // Returns true if the status changed, and sets new_usable_keys to + // true if the status changes resulted in the key becoming usable. + virtual bool ApplyStatusChange(KeyStatus new_status, bool* new_usable_keys); + + // Returns the current constraint status of this key. The result + // may change due to calls to ApplyConstraints(). + // Note: this will return true until the first call to ApplyConstraints(). + virtual bool MeetsConstraints() const { return meets_constraints_; } + + // Applies the given changes in resolution or HDCP settings. + virtual void ApplyConstraints(uint32_t new_resolution, + HdcpCapability new_hdcp_level); + + LicenseKeyStatus(const LicenseKeyStatus&) = delete; + LicenseKeyStatus& operator=(const LicenseKeyStatus&) = delete; + + protected: + typedef ::video_widevine::License::KeyContainer KeyContainer; + typedef KeyContainer::OperatorSessionKeyPermissions + OperatorSessionKeyPermissions; + typedef KeyContainer::OutputProtection OutputProtection; + typedef KeyContainer::VideoResolutionConstraint VideoResolutionConstraint; + typedef ::google::protobuf::RepeatedPtrField + ConstraintList; + + LicenseKeyStatus(const KeyContainer& key); + + virtual ~LicenseKeyStatus() {} + + private: + void ParseContentKey(const KeyContainer& key); + void ParseOperatorSessionKey(const KeyContainer& key); + + bool HasConstraints() { return is_content_key_ && constraints_.size() != 0; } + + void SetConstraints(const ConstraintList& constraints); + + bool is_content_key_; + KeyStatus key_status_; + bool meets_constraints_; + KeyAllowedUsage allowed_usage_; + HdcpCapability default_hdcp_level_; + ConstraintList constraints_; +}; + +} // namespace wvcas + +#endif // WIDEVINE_CAS_LICENSE_KEY_STATUS_H_ diff --git a/plugin/include/oemcrypto_interface.h b/plugin/include/oemcrypto_interface.h new file mode 100644 index 0000000..62dbc5f --- /dev/null +++ b/plugin/include/oemcrypto_interface.h @@ -0,0 +1,138 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#ifndef OEMCRYPTO_INTERFACE_H +#define OEMCRYPTO_INTERFACE_H + +#include +#include +#include + +#include "OEMCryptoCENC.h" + +namespace wvcas { + +// InputStreamParams mirrors the parameters in OEMCrypto API. The +// purpose is to allow OEMCrypto_Descramble to be mocked. OEMCrypto_Descramble +// takes 11 parameters as of API V15. GoogleMock allows a maximum of 10. +struct InputStreamParams { + const uint8_t* data_addr; + size_t data_length; + bool is_encrypted; + + InputStreamParams(){}; + InputStreamParams(const uint8_t* data_addr, size_t data_length, + bool is_encrypted) + : data_addr(data_addr), + data_length(data_length), + is_encrypted(is_encrypted) {} +}; + +// Calls to oemcrypto are called via this object. The purpose of this object is +// to allow OEMCrypto to be mocked. The implementation of this object only wraps +// OEMCrypto methods adding limited additional functionality. Added +// functionality is limited to adapt the input parameters to the oemcrypto api. +// Method signatures in this class can only have a maximum of 10 parameters to +// maintain compatibility with googlemock. +class OEMCryptoInterface { + public: + OEMCryptoInterface(); + virtual ~OEMCryptoInterface(); + + virtual OEMCryptoResult OEMCrypto_Initialize(void); + virtual OEMCryptoResult OEMCrypto_Terminate(void); + virtual OEMCryptoResult OEMCrypto_OpenSession( + OEMCrypto_SESSION* session) const; + virtual OEMCryptoResult OEMCrypto_CloseSession( + OEMCrypto_SESSION session) const; + virtual OEMCrypto_ProvisioningMethod OEMCrypto_GetProvisioningMethod() const; + virtual OEMCryptoResult OEMCrypto_GetKeyData(uint8_t* keyData, + size_t* keyDataLength) const; + virtual uint32_t OEMCrypto_SupportedCertificates() const; + virtual OEMCryptoResult OEMCrypto_GenerateNonce(OEMCrypto_SESSION session, + uint32_t* nonce) const; + virtual OEMCryptoResult OEMCrypto_GenerateDerivedKeys( + OEMCrypto_SESSION session, const uint8_t* mac_key_context, + uint32_t mac_key_context_length, const uint8_t* enc_key_context, + uint32_t enc_key_context_length) const; + virtual OEMCryptoResult OEMCrypto_PrepAndSignLicenseRequest( + OEMCrypto_SESSION session, uint8_t* message, size_t message_length, + size_t* core_message_size, uint8_t* signature, + size_t* signature_length) const; + virtual OEMCryptoResult OEMCrypto_PrepAndSignRenewalRequest( + OEMCrypto_SESSION session, uint8_t* message, size_t message_length, + size_t* core_message_size, uint8_t* signature, + size_t* signature_length) const; + virtual OEMCryptoResult OEMCrypto_PrepAndSignProvisioningRequest( + OEMCrypto_SESSION session, uint8_t* message, size_t message_length, + size_t* core_message_size, uint8_t* signature, + size_t* signature_length) const; + virtual OEMCryptoResult OEMCrypto_LoadProvisioning( + OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, + size_t core_message_length, const uint8_t* signature, + size_t signature_length, uint8_t* wrapped_private_key, + size_t* wrapped_private_key_length) const; + virtual OEMCryptoResult OEMCrypto_GetOEMPublicCertificate( + OEMCrypto_SESSION session, uint8_t* public_cert, + size_t* public_cert_length) const; + virtual OEMCryptoResult OEMCrypto_LoadDRMPrivateKey( + OEMCrypto_SESSION session, OEMCrypto_PrivateKeyType key_type, + const uint8_t* wrapped_rsa_key, size_t wrapped_rsa_key_length) const; + virtual OEMCryptoResult OEMCrypto_GenerateRSASignature( + OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, + uint8_t* signature, size_t* signature_length, + RSA_Padding_Scheme padding_scheme) const; + virtual OEMCryptoResult OEMCrypto_DeriveKeysFromSessionKey( + OEMCrypto_SESSION session, const uint8_t* enc_session_key, + size_t enc_session_key_length, const uint8_t* mac_key_context, + size_t mac_key_context_length, const uint8_t* enc_key_context, + size_t enc_key_context_length) const; + virtual OEMCryptoResult OEMCrypto_LoadLicense(OEMCrypto_SESSION session, + const uint8_t* message, + size_t message_length, + size_t core_message_length, + const uint8_t* signature, + size_t signature_length) const; + virtual OEMCryptoResult OEMCrypto_LoadRenewal(OEMCrypto_SESSION session, + const uint8_t* message, + size_t message_length, + size_t core_message_length, + const uint8_t* signature, + size_t signature_length) const; + virtual OEMCryptoResult OEMCrypto_LoadCasECMKeys( + OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, + const OEMCrypto_EntitledContentKeyObject* even_key, + const OEMCrypto_EntitledContentKeyObject* odd_key) const; + virtual OEMCryptoResult OEMCrypto_GetHDCPCapability( + OEMCrypto_HDCP_Capability* current, OEMCrypto_HDCP_Capability* max) const; + virtual OEMCryptoResult OEMCrypto_GetDeviceID(uint8_t* deviceID, + size_t* idLength) const; + virtual OEMCryptoResult OEMCrypto_LoadTestKeybox(const uint8_t* buffer, + size_t length) const; + virtual const char* OEMCrypto_SecurityLevel() const; + virtual OEMCryptoResult OEMCrypto_CreateEntitledKeySession( + OEMCrypto_SESSION oec_session, OEMCrypto_SESSION* key_session) const; + virtual OEMCryptoResult OEMCrypto_RemoveEntitledKeySession( + OEMCrypto_SESSION key_session) const; + virtual OEMCryptoResult OEMCrypto_ReassociateEntitledKeySession( + OEMCrypto_SESSION key_session, OEMCrypto_SESSION oec_session) const; + virtual uint32_t OEMCrypto_APIVersion() const; + virtual OEMCryptoResult OEMCrypto_GetOEMKeyToken( + OEMCrypto_SESSION key_session, uint8_t* key_token, + size_t* key_token_length) const; + virtual OEMCryptoResult OEMCrypto_GetSignatureHashAlgorithm( + OEMCrypto_SESSION session, + OEMCrypto_SignatureHashAlgorithm* algorithm) const; + + OEMCryptoInterface(const OEMCryptoInterface&) = delete; + OEMCryptoInterface& operator=(const OEMCryptoInterface&) = delete; + + private: + class Impl; + std::unique_ptr impl_; +}; + +} // namespace wvcas + +#endif // OEMCRYPTO_INTERFACE_H diff --git a/plugin/include/policy_engine.h b/plugin/include/policy_engine.h new file mode 100644 index 0000000..f4419ab --- /dev/null +++ b/plugin/include/policy_engine.h @@ -0,0 +1,214 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#ifndef WIDEVINE_CAS_POLICY_ENGINE_H_ +#define WIDEVINE_CAS_POLICY_ENGINE_H_ + +#include +#include +#include + +#include "cas_util.h" +#include "clock.h" +#include "crypto_session.h" +#include "license_key_status.h" +#include "license_protocol.pb.h" +#include "timer.h" + +namespace wvcas { + +// This acts as an oracle that basically says "Yes(true) you may still decrypt +// or no(false) you may not decrypt this data anymore." +// TODO(jfore): Just pass in a function object? OnTimerEvent can be a +class PolicyEngine : public wvutil::TimerHandler { + typedef enum { + kLicenseStateInitial, + kLicenseStatePending, // if license is issued for sometime in the future + kLicenseStateCanPlay, + kLicenseStateNeedRenewal, + kLicenseStateWaitingLicenseUpdate, + kLicenseStateExpired + } LicenseState; + + public: + // The default constructor. + PolicyEngine() {} + virtual ~PolicyEngine() {} + + virtual void initialize(std::shared_ptr crypto_session, + CasEventListener* event_listener) { + license_keys_ = CreateLicenseKeys(); + clock_ = CreateClock(); + event_listener_ = event_listener; + crypto_session_ = crypto_session; + } + + // The value returned should be taken as a hint rather than an absolute + // status. It is computed during the last call to either SetLicense/ + // UpdateLicense/OnTimerEvent/BeginDecryption and may be out of sync + // depending on the amount of time elapsed. The current decryption + // status is not calculated to avoid overhead in the decryption path. + virtual bool CanDecryptContent(const KeyId& key_id) const; + + // The value returned indicates if a license renewal is allowed under the + // current policy. + virtual bool CanRenew() const { return policy_.can_renew(); } + + // The value returned indicates if the license is persisted to non-volatile + // storage for offline use. + virtual bool CanPersist() const { return policy_.can_persist(); } + + // The value returned indicates whether or not the client id should be + // included in renewal requests. + virtual bool always_include_client_id() const { + return policy_.always_include_client_id(); + } + + // The value returned is the current license id. + virtual const video_widevine::LicenseIdentification& license_id() const { + return license_id_; + } + + virtual const std::string& renewal_server_url() const { + return policy_.renewal_server_url(); + } + + // SetLicense is used in handling the initial license response. It stores + // an exact copy of the policy information stored in the license. + // The license state transitions to kLicenseStateCanPlay if the license + // permits playback. + virtual void SetLicense(const video_widevine::License& license); + + // UpdateLicense is used in handling a license response for a renewal request. + // The response may only contain any policy fields that have changed. In this + // case an exact copy is not what we want to happen. We also will receive an + // updated license_start_time from the server. The license will transition to + // kLicenseStateCanPlay if the license permits playback. + virtual void UpdateLicense(const video_widevine::License& license); + + // Call this on first decrypt to set the start of playback. + virtual void BeginDecryption(void); + + // OnTimerEvent is called when a timer fires. It notifies the Policy Engine + // that the timer has fired and dispatches the relevant events through + // |event_listener_|. + virtual void OnTimerEvent() override; + + // Used to update the currently loaded entitled content keys. + virtual void SetEntitledLicenseKeys( + const std::vector& entitled_keys); + + // Used to query if the current license state is expired. + virtual bool IsExpired() const { + return license_state_ == kLicenseStateExpired; + } + + // for offline save and restore + int64_t GetPlaybackStartTime() const { return playback_start_time_; } + int64_t GetLastPlaybackTime() const { return last_playback_time_; } + int64_t GetGracePeriodEndTime() const { return grace_period_end_time_; } + void RestorePlaybackTimes(int64_t playback_start_time, + int64_t last_playback_time, + int64_t grace_period_end_time); + + PolicyEngine(const PolicyEngine&) = delete; + PolicyEngine& operator=(const PolicyEngine&) = delete; + + private: + // Notifies updates in keys information and fire OnKeysChange event if + // key changes. + void NotifyKeysChange(KeyStatus new_status); + + void NotifyLicenseExpired(LicenseState key_status); + + bool HasLicenseOrPlaybackDurationExpired(int64_t current_time); + + // Notifies updates in expiry time and fire OnExpirationUpdate event if + // expiry time changes. + void NotifyExpirationUpdate(int64_t current_time); + + // Notify listeners of the current renewal url. + void NotifyRenewalServerUpdate(); + + // Guard against clock rollbacks + int64_t GetCurrentTime(); + + LicenseState license_state_ = kLicenseStateInitial; + + void CheckDeviceHdcpStatus(); + + // Gets the clock time that the license expires based on whether we have + // started playing. This takes into account GetHardLicenseExpiryTime. + int64_t GetExpiryTime(int64_t current_time, + bool ignore_soft_enforce_playback_duration); + + void CheckDeviceHdcpStatusOnTimer(int64_t current_time); + + bool HasPlaybackStarted(int64_t current_time) { + if (playback_start_time_ == 0) return false; + + const int64_t playback_time = current_time - playback_start_time_; + return playback_time >= policy_.play_start_grace_period_seconds(); + } + + // Gets the clock time that the rental duration will expire, using the license + // duration if one is not present. + int64_t GetRentalExpiryTime(); + + // Gets the clock time that the license expires. This is the hard limit that + // all license types must obey at all times. + int64_t GetHardLicenseExpiryTime(); + + bool HasRenewalDelayExpired(int64_t current_time); + + bool HasRenewalRetryIntervalExpired(int64_t current_time); + + void UpdateRenewalRequest(int64_t current_time); + + virtual std::unique_ptr CreateLicenseKeys(); + + virtual std::unique_ptr CreateClock(); + + // This is the current policy information for this license. This gets updated + // as license renewals occur. + video_widevine::License::Policy policy_; + + // This is the license id field from server response. This data gets passed + // back to the server in each renewal request. When we get a renewal response + // from the license server we will get an updated id field. + video_widevine::LicenseIdentification license_id_; + + // The server returns the license start time in the license/license renewal + // response based off the request time sent by the client in the + // license request/renewal + int64_t license_start_time_ = 0; + int64_t playback_start_time_ = 0; + int64_t last_playback_time_ = 0; + int64_t grace_period_end_time_ = 0; + bool last_expiry_time_set_ = false; + bool was_expired_on_load_ = false; + + // This is used as a reference point for policy management. This value + // represents an offset from license_start_time_. This is used to + // calculate the time where renewal retries should occur. + int64_t next_renewal_time_ = 0; + + CasEventListener* event_listener_ = nullptr; + + // Keys associated with license - holds allowed usage, usage constraints, + // and current status (KeyStatus) + std::unique_ptr license_keys_; + std::unique_ptr clock_; + // to assist in clock rollback checks + int64_t last_recorded_current_time_ = 0; + int64_t last_expiry_time_ = 0; + int64_t next_device_check_ = 0; + std::shared_ptr crypto_session_; + uint32_t current_resolution_ = 0; + std::string renewal_server_url_; +}; + +} // namespace wvcas + +#endif // WIDEVINE_CAS_POLICY_ENGINE_H_ diff --git a/plugin/include/widevine_cas_api.h b/plugin/include/widevine_cas_api.h new file mode 100644 index 0000000..0d086f9 --- /dev/null +++ b/plugin/include/widevine_cas_api.h @@ -0,0 +1,210 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#ifndef WIDEVINE_CAS_API_H +#define WIDEVINE_CAS_API_H + +#include +#include +#include + +#include "cas_license.h" +#include "cas_media_id.h" +#include "cas_status.h" +#include "cas_types.h" +#include "crypto_session.h" +#include "ecm_parser.h" +#include "emm_parser.h" +#include "file_store.h" +#include "media_cas.pb.h" +#include "timer.h" +#include "widevine_cas_session.h" + +namespace wvcas { + +// TODO(jfore): Fix the function name inconsistency in this class. These +// functions were migrated from the android plugin api implementation. They +// should not follow Android's style. +class WidevineCas : public wvutil::TimerHandler { + public: + WidevineCas() {} + virtual ~WidevineCas() {} + + virtual CasStatus initialize(CasEventListener* event_listener); + + // Open a session for descrambling a program, or one or more elementary + // streams. + virtual CasStatus openSession(WvCasSessionId* sessionId); + + // Close a previously opened session. + virtual CasStatus closeSession(const WvCasSessionId& sessionId); + + // Process an EMM which may contain fingerprinting and service blocking info. + virtual CasStatus processEmm(const CasEmm& emm); + + // Process an ECM from the ECM stream for this session’s elementary + // stream. + virtual CasStatus processEcm(const WvCasSessionId& sessionId, + const CasEcm& ecm); + + // Generates a device provisioning request message in |provisioning_request|. + virtual CasStatus generateDeviceProvisioningRequest( + std::string* provisioning_request); + + // Processes a |response| to provisioning request. + virtual CasStatus handleProvisioningResponse(const std::string& response); + + // Generates an entitlement license request in |entitlement_request| for the + // media described in |init_data|. + virtual CasStatus generateEntitlementRequest(const std::string& init_data, + std::string* entitlement_request, + std::string& license_id); + + // Processes the entitlement |response| to a entitlement license request. + // |license_id| is the id of the license installed. Can be used to select + // which license to install. + // |multi_content_license_info| contains the message that can be sent to the + // app if the installed license is a multi content license. + // |group_license_info| contains the message that can be sent to the app if + // the installed license is a group license. + virtual CasStatus handleEntitlementResponse( + const std::string& response, std::string& license_id, + std::string& multi_content_license_info, std::string& group_license_info); + + // Generates an entitlement license request in |entitlement_request| for the + // media described in |init_data|. + virtual CasStatus generateEntitlementRenewalRequest( + std::string* entitlement_renewal_request); + + // Processes the entitlement renewal |response| to a entitlement license + // request. + virtual CasStatus handleEntitlementRenewalResponse( + const std::string& response, std::string& license_id); + + // Generates an entitlement license request in a new crypto session, and send + // the license request as an event to the app. + virtual CasStatus generateEntitlementPeriodUpdateRequest( + const std::string& init_data); + + // Processes the license |response| to switch the current license to this + // new one. + virtual CasStatus handleEntitlementPeriodUpdateResponse( + const std::string& response, std::string& license_id); + + // Returns true if the device has been provisioned with a device certificate. + virtual bool is_provisioned() const; + + // Processes the CAS |private_data| from a CAT table. If successful a + // serialized pssh data is retured in |init_data|. + virtual CasStatus ProcessCAPrivateData(const CasData& private_data, + std::string* init_data); + + // Processes the CAS |private_data| from a PMT table. If successful a + // serialized pssh data is retured in |init_data|. The CA private data can be + // unique to the ecm session indicated by |session_id|. + virtual CasStatus ProcessSessionCAPrivateData( + const WvCasSessionId& session_id, const CasData& private_data, + std::string* init_data); + // Returns the device unique identifier. + virtual CasStatus GetUniqueID(std::string* buffer); + + // Set the minimum age required to process ECM. + virtual CasStatus HandleSetParentalControlAge(const CasData& data); + + // Remove the license file given the filename user provides. + virtual CasStatus RemoveLicense(const std::string& file_name); + + // Record the license id that user provides. This license id will be used to + // select license if multiple licenses exist. + virtual CasStatus RecordLicenseId(const std::string& license_id); + + void OnTimerEvent() override; + + // Stops the timer thread. Called by CAS plugin destructor to avoid race. + void StopTimer(); + + private: + virtual CasStatus HandleStoredDrmCert(const std::string& certificate); + virtual CasStatus HandleProcessEcm(const WvCasSessionId& sessionId, + const CasEcm& ecm); + virtual CasStatus HandleDeferredECMs(); + // Extracts the entitlement rotation period index from ECM if specified, and + // store it. The function should be called before any license request and the + // extracted index will be included in the license request. + virtual void TryExtractEntitlementPeriodIndex(const CasEcm& ecm); + // Returns true if an offline license with |filename| is successfully loaded. + virtual bool TryReuseStoredLicense(const std::string& filename); + // Check if a new license is needed due to entitlement period changes. If so, + // it will call generateEntitlementPeriodUpdateRequest(). + void CheckEntitlementPeriodUpdate(uint32_t period_index, + uint32_t window_left); + + virtual std::shared_ptr getCryptoSession(); + virtual std::unique_ptr getCasLicense(); + virtual std::unique_ptr getFileSystem(); + virtual std::shared_ptr newCasSession(); + virtual std::unique_ptr getEcmParser(const CasEcm& ecm) const; + + // Creates an EmmParser. Marked as virtual for easier unit test. + virtual std::unique_ptr getEmmParser( + const CasEmm& emm) const; + std::vector GenerateFingerprintingEventMessage( + const video_widevine::Fingerprinting& fingerprinting) const; + std::vector GenerateServiceBlockingEventMessage( + const video_widevine::ServiceBlocking& service_blocking) const; + + // The CryptoSession will be shared by the all cas sessions. It is also needed + // by the cas api to generate EMM requests. + std::shared_ptr crypto_session_; + std::unique_ptr cas_license_; + std::unique_ptr file_system_; + std::string device_certificate_; + std::string wrapped_rsa_key_; + CasEventListener* event_listener_ = nullptr; + std::mutex lock_; + wvutil::Timer policy_timer_; + LicenseType license_type_; + std::unique_ptr media_id_; + // Sometimes delays in receiving a license or the format in which the content + // is encoded my result in ecms being processed before a valid license has + // been loaded. In this cas |has_license_| will be false and the ecm will be + // stored in |deferred_ecms_|. Once a license has been loaded, the stored ecms + // are processed to set the current content keys. + std::map deferred_ecms_; + // The value |has_license_| will be false when the plugin is created. Once a + // license is loaded, |has_license_| will be set to true. + bool has_license_ = false; + // The age_restriction field in ECM must be greater or equal to + // |parental_control_min_age|. Otherwise, ECM will stop being processed. + uint parental_control_age_ = 0; + // The requested_license_id helps to indicate which license file current + // content will use if multiple licenses exist. + std::string requested_license_id_; + // The current in use license_id. + std::string license_id_; + // The group id of a Group license. Empty if the license is not a Group + // license (multi content license is not a group license). Used in processECM + // to select group keys that can be decrypted by the license. + std::string license_group_id_; + // Fingerprinting events sent in processing last ECM/EMM. Used to avoid + // sending a same event again. + std::set last_fingerprinting_events_; + // Service blocking events sent in processing last ECM/EMM. Used to avoid + // sending a same event again. + std::set last_service_blocking_events_; + // Indicates if |entitlement_period_index_| below is valid or not. + bool is_entitlement_rotation_enabled_ = false; + // The entitlement period index in the last received ECM. + uint32_t entitlement_period_index_; + + // |next_*| used to handle entitlement key rotation. They will be moved to + // normal ones once the license switch completed. + std::shared_ptr next_crypto_session_; + std::unique_ptr next_cas_license_; + std::unique_ptr next_media_id_; +}; // namespace wvcas + +} // namespace wvcas + +#endif // WIDEVINE_CAS_API_H diff --git a/plugin/include/widevine_cas_session.h b/plugin/include/widevine_cas_session.h new file mode 100644 index 0000000..88a9a99 --- /dev/null +++ b/plugin/include/widevine_cas_session.h @@ -0,0 +1,113 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#ifndef WIDEVINE_CAS_SESSION_H +#define WIDEVINE_CAS_SESSION_H + +#include +#include +#include +#include + +#include "cas_types.h" +#include "crypto_session.h" +#include "ecm_parser.h" +#include "media_cas.pb.h" + +namespace wvcas { + +class WidevineCasSession; +typedef std::shared_ptr CasSessionPtr; + +class CasKeySlotData { + public: + CasKeySlotData() {} + ~CasKeySlotData() {} + + KeySlot& operator[](KeySlotId slot_id); + const KeySlot& operator[](KeySlotId slot_id) const; + + private: + KeySlot keys_[2]; // Odd and even key slots. +}; + +enum class ScramblingControl { + kScrambling_Unscrambled = 0, + kScrambling_Reserved = 1, + kScrambling_EvenKey = 2, + kScrambling_OddKey = 3, +}; + +// WidevineCasSession represents an encryption context for a single ECM key +// stream. It processes ECMs for the stream and maintains the key information. +class WidevineCasSession { + public: + WidevineCasSession() {} + virtual ~WidevineCasSession(); + + CasStatus initialize(std::shared_ptr crypto_session, + CasEventListener* event_listener, + WvCasSessionId* session_id); + + CasStatus resetCryptoSession(std::shared_ptr crypto_session); + + // Process an ecm and extract the key slot data. Extracted data will be used + // to update |current_ecm_| and |entitlement_key_id_| and |keys_|. + // |parental_control_age| (if non-zero) must be greater or equal to the + // age_restriction field specified in |ecm|. Otherwise, ECM will not be + // processed and error will be returned. + // |license_group_id| if non empty, processEcm will decrypt content keys that + // are specified by |license_group_id|. + virtual CasStatus processEcm(const CasEcm& ecm, uint8_t parental_control_age, + const std::string& license_group_id); + + // Returns the security level retrieved from OEMCrypto. + const char* securityLevel(); + + // Returns current ecm age restriction value. + uint8_t GetEcmAgeRestriction() const { return ecm_age_restriction_; } + // Returns the entitlement period index specified in the last received ECM. + uint32_t GetEntitlementPeriodIndex() const { + return entitlement_period_index_; + } + // Returns the entitlement rotation window left value specified in the last + // received ECM. + uint32_t GetEntitlementRotationWindowLeft() const { + return entitlement_rotation_window_left_; + } + + WidevineCasSession(const WidevineCasSession&) = delete; + WidevineCasSession& operator=(const WidevineCasSession&) = delete; + + private: + // Creates an EcmParser. + virtual std::unique_ptr getEcmParser(const CasEcm& ecm) const; + + CasKeySlotData keys_; // Odd and even key slots. + std::string entitlement_key_id_; + std::mutex crypto_lock_; + CasEcm current_ecm_; + uint8_t ecm_age_restriction_ = 0; + std::shared_ptr crypto_session_; + // Id of the entitled key session in OEMCrypto associated with this session. + uint32_t key_session_id_; + // This is the session id returned to the app. It is actually the OEM key + // token. + std::vector external_key_session_id_; + CasEventListener* event_listener_ = nullptr; + // Fingerprinting events sent in processing last ECM/EMM. Used to avoid + // sending a same event again. + std::vector last_fingerprinting_message_; + // Service blocking events sent in processing last ECM/EMM. Used to avoid + // sending a same event again. + std::vector last_service_blocking_message_; + // The entitlement period index in the last received ECM. + uint32_t entitlement_period_index_; + // The entitlement rotation window left in the last received ECM. + uint32_t entitlement_rotation_window_left_; +}; + +} // namespace wvcas + +#endif // WIDEVINE_CAS_SESSION_H diff --git a/plugin/include/widevine_cas_session_map.h b/plugin/include/widevine_cas_session_map.h new file mode 100644 index 0000000..cae9786 --- /dev/null +++ b/plugin/include/widevine_cas_session_map.h @@ -0,0 +1,54 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#ifndef WIDEVINE_CAS_SESSION_MAP_H +#define WIDEVINE_CAS_SESSION_MAP_H + +#include +#include +#include +#include +#include + +#include "cas_types.h" +#include "widevine_cas_session.h" + +namespace wvcas { + +typedef std::map CasSessionMap; + +// WidevineCasSessionMap is a singleton. It used as a shared resource used by +// both cas and descrambler plugins. Cas sessions are created by the cas plugin, +// and can be *discovered* by descrambler plugins. +class WidevineCasSessionMap { + public: + virtual ~WidevineCasSessionMap() {} + + // Adds a new Widevine cas session to the map. + // Returns true if the session is successfully added to the map, false + // otherwise. + bool AddSession(const WvCasSessionId& cas_session_id, CasSessionPtr session); + // Obtain a shared pointer to a cas session. If the session does not exist in + // the map, the returned pointer == nullptr. + CasSessionPtr GetSession(const WvCasSessionId& cas_session_id) const; + // Remove an entry in the map. + void RemoveSession(const WvCasSessionId& cas_session_id); + // Retrieves all the session ids. + std::vector GetAllSessions() const; + + // Returns a reference to the map. + static WidevineCasSessionMap& instance(); + + WidevineCasSessionMap(const WidevineCasSessionMap&) = delete; + WidevineCasSessionMap& operator=(const WidevineCasSessionMap&) = delete; + + private: + WidevineCasSessionMap() {} + CasSessionMap map_; + mutable std::mutex lock_; +}; + +} // namespace wvcas + +#endif // WIDEVINE_CAS_SESSION_MAP_H \ No newline at end of file diff --git a/plugin/include/widevine_media_cas.h b/plugin/include/widevine_media_cas.h new file mode 100644 index 0000000..f9e351f --- /dev/null +++ b/plugin/include/widevine_media_cas.h @@ -0,0 +1,59 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#ifndef WIDEVINE_MEDIA_CAS_H +#define WIDEVINE_MEDIA_CAS_H + +#include "media/cas/CasAPI.h" + +using android::CasFactory; +using android::CasPlugin; +using android::CasPluginCallback; +using android::CasPluginCallbackExt; +using android::CasPluginDescriptor; +using android::status_t; +using android::String8; + +extern "C" { +android::CasFactory* createCasFactory(); +} + +namespace wvcas { + +// WidevineCasFactory implements the android::CasFactory interface. +class WidevineCasFactory : public CasFactory { + public: + // Implements android::CasFactory* createCasFactory(). This method is used + // by the MediaCas framework to construct our factory. + static WidevineCasFactory* createCasFactory(); + ~WidevineCasFactory() override {} + + // Test the input |CA_system_id| and return true if it is supported. + // We only support the Widevine CA ID. + bool isSystemIdSupported(int32_t CA_system_id) const override; + + // Add a descriptor to the vector |descriptors| for each CA ID we support. + // We only support the Widevine CA ID. + status_t queryPlugins( + std::vector* descriptors) const override; + + // Construct an instance of our CAS API plugin. + status_t createPlugin(int32_t CA_system_id, void* appData, + CasPluginCallback callback, + CasPlugin** plugin) override; + + // Construct a new extend instance of a CasPlugin given a CA_system_id + status_t createPlugin(int32_t CA_system_id, void* appData, + CasPluginCallbackExt callback, + CasPlugin** plugin) override; + + private: + WidevineCasFactory() {} + WidevineCasFactory(const WidevineCasFactory&); + WidevineCasFactory& operator=(const WidevineCasFactory&); +}; + +} // namespace wvcas + +#endif // WIDEVINE_MEDIA_CAS_H \ No newline at end of file diff --git a/plugin/include/widevine_media_cas_plugin.h b/plugin/include/widevine_media_cas_plugin.h new file mode 100644 index 0000000..c3cd8f3 --- /dev/null +++ b/plugin/include/widevine_media_cas_plugin.h @@ -0,0 +1,154 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#ifndef WIDEVINE_MEDIA_CAS_PLUGIN +#define WIDEVINE_MEDIA_CAS_PLUGIN + +#include + +#include "media/cas/CasAPI.h" +#include "widevine_cas_api.h" + +using android::CasPlugin; +using android::CasPluginCallback; +using android::CasPluginCallbackExt; +using android::CasPluginStatusCallback; +using android::CasSessionId; +using android::status_t; +using android::String8; + +namespace wvcas { + +// WidevineCasPlugin inplements the android::CasPlugin interface. +class WidevineCasPlugin : public CasPlugin, public CasEventListener { + public: + // Constructs a new WidevineCasPlugin. The |callback| is a method to transmit + // events to the user application. To receive these events, a MediaCas + // application must implement The MediaCas.EventListener java interface and + // call MediaCas.setEventListener. More information can be found in the + // MediaCas platform api documentation. + WidevineCasPlugin(void* appData, CasPluginCallback callback); + WidevineCasPlugin(void* appData, CasPluginCallbackExt callback); + ~WidevineCasPlugin() override; + + status_t initialize(); + + // Provide a callback to report plugin status. + status_t setStatusCallback(CasPluginStatusCallback callback) override; + + // Provide the CA private data from a CA_descriptor in the conditional + // access table. + status_t setPrivateData(const CasData& privateData) override; + + // Open a session for descrambling a program, or one or more elementary + // streams. + status_t openSession(CasSessionId* sessionId) override; + + // Open a session with intend and mode for descrambling a program, or one + // or more elementary streams. + status_t openSession(uint32_t intent, uint32_t mode, + CasSessionId* sessionId) override; + + // Close a previously opened session. + status_t closeSession(const CasSessionId& sessionId) override; + + // Provide the CA private data from a CA_descriptor in the program map + // table. + status_t setSessionPrivateData(const CasSessionId& sessionId, + const CasData& privateData) override; + + // Process an ECM from the ECM stream for this session’s elementary stream. + status_t processEcm(const CasSessionId& sessionId, + const CasEcm& ecm) override; + + // Process an in-band EMM from the EMM stream. + status_t processEmm(const CasEmm& emm) override; + + // Deliver an event to the CasPlugin. The format of the event is specific + // to the CA scheme and is opaque to the framework. + status_t sendEvent(int32_t event, int32_t arg, + const CasData& eventData) override; + + // Deliver an session event to the CasPlugin. The format of the event is + // specific to the CA scheme and is opaque to the framework. + status_t sendSessionEvent(const CasSessionId& sessionId, int32_t event, + int32_t arg, const CasData& eventData) override; + + // Native implementation of the MediaCas Java API provision method. + status_t provision(const String8& provisionString) override; + + // Native implementation of the MediaCas Java API refreshEntitlements method. + status_t refreshEntitlements(int32_t refreshType, + const CasData& refreshData) override; + + WidevineCasPlugin(const WidevineCasPlugin&) = delete; + WidevineCasPlugin& operator=(const WidevineCasPlugin&) = delete; + + protected: + // For unit test only. + virtual void SetWidevineCasApi( + std::unique_ptr widevine_cas_api) { + widevine_cas_api_ = std::move(widevine_cas_api); + } + WidevineCasPlugin(){}; + + private: + // |sessionId| is nullptr if the event is not a session event. + CasStatus processEvent(int32_t event, int32_t arg, const CasData& eventData, + const CasSessionId* sessionId); + CasStatus HandleIndividualizationResponse(const CasData& response); + CasStatus HandleEntitlementResponse(const CasData& response); + status_t requestLicense(const std::string& init_data); + CasStatus HandleEntitlementRenewalResponse(const CasData& response); + CasStatus HandleUniqueIDQuery(); + CasStatus HandleSetParentalControlAge(const CasData& data); + CasStatus HandleLicenseRemoval(const CasData& license_id); + CasStatus HandleAssignLicenseID(const CasData& license_id); + CasStatus HandlePluginVersionQuery(); + CasStatus HandleEntitlementPeriodUpdateResponse(const CasData& response); + + // Returns true if the device has been provisioned with a device certificate. + bool is_provisioned() const; + // Event listener implementation + void OnSessionRenewalNeeded() override; + void OnSessionKeysChange(const KeyStatusMap& keys_status, + bool has_new_usable_key) override; + // |new_expiry_time_seconds| of 0 means "never expires". + void OnExpirationUpdate(int64_t new_expiry_time_seconds) override; + void OnNewRenewalServerUrl(const std::string& renewal_server_url) override; + void OnLicenseExpiration() override; + void OnAgeRestrictionUpdated(const WvCasSessionId& sessionId, + uint8_t ecm_age_restriction) override; + void OnSessionFingerprintingUpdated(const WvCasSessionId& sessionId, + const CasData& fingerprinting) override; + void OnSessionServiceBlockingUpdated( + const WvCasSessionId& sessionId, + const CasData& service_blocking) override; + void OnFingerprintingUpdated(const CasData& fingerprinting) override; + void OnServiceBlockingUpdated(const CasData& service_blocking) override; + void OnEntitlementPeriodUpdateNeeded( + const std::string& signed_license_request) override; + + // Choose to use |callback_| or |callback_ext_| to send back information. + // |sessionId| is ignored if |callback_ext_| is null, + virtual void CallBack(void* appData, int32_t event, int32_t arg, + uint8_t* data, size_t size, + const CasSessionId* sessionId) const; + + void* app_data_; + CasPluginCallback callback_; + CasPluginCallbackExt callback_ext_; + // If provision is called with a non-empty string, + // it is taken as a PSSH that overrides data in CA descripor. + // Otherwise, first CA descriptor available to the plugin + // is used to build a PSSH, and others are discarded. + bool is_emm_request_sent_ = false; + // This is always the serialized PSSH data. + std::string init_data_; + std::unique_ptr widevine_cas_api_; +}; + +} // namespace wvcas + +#endif // WIDEVINE_MEDIA_CAS_PLUGIN diff --git a/plugin/src/cas_license.cpp b/plugin/src/cas_license.cpp new file mode 100644 index 0000000..dd0a70b --- /dev/null +++ b/plugin/src/cas_license.cpp @@ -0,0 +1,1138 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#include "cas_license.h" + +#include +#include + +#include +#include +#include +#include + +#include "cas_properties.h" +#include "cas_util.h" +#include "crypto_session.h" +#include "device_files.pb.h" +#include "license_protocol.pb.h" +#include "log.h" +#include "string_conversions.h" + +using video_widevine_client::sdk::DeviceCertificate; +using video_widevine_client::sdk::File; +using video_widevine_client::sdk::HashedFile; +using wvutil::Base64SafeDecode; +using wvutil::Base64SafeEncodeNoPad; +using video_widevine::CASDrmLicenseRequest; +using video_widevine::ClientIdentification; +using video_widevine::ClientIdentification_ClientCapabilities; +using video_widevine:: +ClientIdentification_ClientCapabilities_CertificateKeyType_RSA_2048; +using video_widevine:: +ClientIdentification_ClientCapabilities_CertificateKeyType_RSA_3072; +using video_widevine::ClientIdentification_NameValue; +using video_widevine::HashAlgorithmProto; +using video_widevine::License; +using video_widevine::License_KeyContainer; +using video_widevine::LicenseRequest; +using video_widevine::ProvisioningRequest; +using video_widevine::ProvisioningResponse; +using video_widevine::SignedMessage; +using video_widevine::SignedProvisioningMessage; + +static constexpr size_t kMacKeySizeBytes = 32; +static constexpr size_t kMacKeyPaddingSizeBytes = 16; +static constexpr size_t kIvSizeBytes = 16; +static constexpr size_t kCertificateDataSizeBytes = 4 * 1024; +static constexpr uint32_t kRsaSignatureSizeBytes = 256; +static constexpr size_t kKeyboxTokenSizeBytes = 72; + +namespace wvcas { + +// TODO(jfore): Move the methods in this anonymous namespace to a utility +// module. +namespace { + +constexpr char kKeyCompanyName[] = "company_name"; +constexpr char kKeyModelName[] = "model_name"; +constexpr char kKeyArchitectureName[] = "architecture_name"; +constexpr char kKeyDeviceName[] = "device_name"; +constexpr char kKeyProductName[] = "product_name"; +constexpr char kKeyWvCasVersion[] = "widevine_cdm_version"; + +// TODO(jfore): These variables are currently unused and are flagged as build +// errors in android. These values will be used in a future cl. +/*static constexpr char kKeyBuildInfo[] = "build_info";*/ +/*static constexpr char kKeyDeviceId[] ="device_id";*/ +/*static constexpr char kKeyOemCryptoSecurityPatchLevel[] = + "oem_crypto_security_patch_level";*/ + +// Encode unsigned integer into a big endian formatted string +std::string EncodeUint32(uint32_t u) { + std::string s; + s.append(1, (u >> 24) & 0xFF); + s.append(1, (u >> 16) & 0xFF); + s.append(1, (u >> 8) & 0xFF); + s.append(1, (u >> 0) & 0xFF); + return s; +} + +void GenerateMacContext(const std::string& input_context, + std::string* deriv_context) { + if (!deriv_context) { + return; + } + + const std::string kSigningKeyLabel = "AUTHENTICATION"; + const size_t kSigningKeySizeBits = 256; + + deriv_context->assign(kSigningKeyLabel); + deriv_context->append(1, '\0'); + deriv_context->append(input_context); + deriv_context->append(EncodeUint32(kSigningKeySizeBits * 2)); +} + +void GenerateEncryptContext(const std::string& input_context, + std::string* deriv_context) { + if (!deriv_context) { + return; + } + + const std::string kEncryptionKeyLabel = "ENCRYPTION"; + const size_t kEncryptionKeySizeBits = 128; + + deriv_context->assign(kEncryptionKeyLabel); + deriv_context->append(1, '\0'); + deriv_context->append(input_context); + deriv_context->append(EncodeUint32(kEncryptionKeySizeBits)); +} + +/* + * Provisioning response is a base64-encoded protobuf, optionally within a + * JSON wrapper. If the JSON wrapper is present, extract the embedded response + * message. Then perform the base64 decode and return the result. + * + * If an error occurs during the parse or the decode, return an empty string. + */ +void ExtractAndDecodeSignedMessage(const std::string& provisioning_response, + std::string* result) { + const std::string json_start_substr("\"signedResponse\": \""); + const std::string json_end_substr("\""); + std::string message_string; + const size_t start = provisioning_response.find(json_start_substr); + if (start == provisioning_response.npos) { + // Message is not properly wrapped - reject it. + LOGE("ExtractAndDecodeSignedMessage: cannot locate start substring"); + result->clear(); + return; + } else { + // Appears to be JSON-wrapped protobuf - find end of protobuf portion. + const size_t end = provisioning_response.find( + json_end_substr, start + json_start_substr.length()); + if (end == provisioning_response.npos) { + LOGE("ExtractAndDecodeSignedMessage: cannot locate end substring"); + result->clear(); + return; + } + const size_t b64_string_size = end - start - json_start_substr.length(); + message_string.assign(provisioning_response, + start + json_start_substr.length(), b64_string_size); + } + if (message_string.empty()) { + LOGE("ExtractAndDecodeSignedMessage: CdmProvisioningResponse is empty"); + result->clear(); + return; + } + // Decode the base64-encoded message. + const std::vector decoded_message = Base64SafeDecode(message_string); + result->assign(decoded_message.begin(), decoded_message.end()); +} + +int64_t GetCurrentTime() { + struct timeval tv; + tv.tv_sec = tv.tv_usec = 0; + gettimeofday(&tv, NULL); + return tv.tv_sec; +} + +bool Hash(const std::string& data, std::string* hash) { + if (!hash) return false; + hash->resize(SHA256_DIGEST_LENGTH); + const unsigned char* input = + reinterpret_cast(data.data()); + unsigned char* output = reinterpret_cast(&(*hash)[0]); + SHA256(input, data.size(), output); + return true; +} + +CasStatus GenerateLicenseFile( + const std::string& emm_request, const std::string& emm_response, + const std::string& renewal_request, const std::string& renewal_response, + int64_t playback_start_time, int64_t last_playback_time, + int64_t grace_period_end_time, std::string* device_file) { + if (device_file == nullptr) { + return CasStatus(CasStatusCode::kCasLicenseError, + "missing output parameter"); + } + File file; + file.set_type(File::LICENSE); + + auto* license_file = file.mutable_license(); + license_file->set_license_request(emm_request); + license_file->set_license(emm_response); + license_file->set_renewal(renewal_response); + license_file->set_renewal_request(renewal_request); + license_file->set_playback_start_time(playback_start_time); + license_file->set_state( + video_widevine_client::sdk::License_LicenseState_ACTIVE); + + HashedFile hash_file; + file.SerializeToString(hash_file.mutable_file()); + if (!Hash(hash_file.file(), hash_file.mutable_hash())) { + return CasStatus(CasStatusCode::kCasLicenseError, + ("generating file hash fails")); + } + hash_file.SerializeToString(device_file); + return CasStatusCode::kNoError; +} + +bool OecAlgorithmToProtoAlgorithm( + OEMCrypto_SignatureHashAlgorithm oec_algorithm, + HashAlgorithmProto& proto_algorithm) { + switch (oec_algorithm) { + case OEMCrypto_SHA1: + proto_algorithm = HashAlgorithmProto::HASH_ALGORITHM_SHA_1; + return true; + case OEMCrypto_SHA2_256: + proto_algorithm = HashAlgorithmProto::HASH_ALGORITHM_SHA_256; + return true; + case OEMCrypto_SHA2_384: + proto_algorithm = HashAlgorithmProto::HASH_ALGORITHM_SHA_384; + return true; + case OEMCrypto_SHA2_512: + // TODO(b/259268439): The service does not support SHA-512 + return false; + } + return false; +} + +} // namespace + +CasStatus CasLicense::initialize(std::shared_ptr crypto_session, + CasEventListener* listener) { + if (!crypto_session) { + return CasStatus(CasStatusCode::kCasLicenseError, "missing crypto session"); + } + policy_engine_ = GetPolicyEngine(); + policy_engine_->initialize(crypto_session, this); + crypto_session_ = crypto_session; + event_listener_ = listener; + return CasStatusCode::kNoError; +} + +CasStatus CasLicense::GenerateDeviceProvisioningRequest( + std::string* serialized_provisioning_request) const { + if (!crypto_session_) { + return CasStatus(CasStatusCode::kCasLicenseError, "no crypto session"); + } + if (serialized_provisioning_request == nullptr) { + return CasStatus(CasStatusCode::kInvalidParameter, + "GenerateDeviceProvisioningRequest: " + "serialized_provisioning_request parameter is missing"); + } + + switch (crypto_session_->provisioning_method()) { + case Keybox: + return GenerateDeviceProvisioningRequestWithKeybox( + serialized_provisioning_request); + default: + return CasStatus(CasStatusCode::kCasLicenseError, + "unknown provisioning method"); + } + + return CasStatusCode::kNoError; +} + +CasStatus CasLicense::HandleDeviceProvisioningResponse( + const std::string& signed_provisioning_response, + std::string* device_certificate, std::string* wrapped_rsa_key, + std::string* device_file) const { + if (nullptr == device_certificate || nullptr == wrapped_rsa_key) { + return CasStatus(CasStatusCode::kCasLicenseError, + "missing device credentials"); + } + if (!crypto_session_) { + return CasStatus(CasStatusCode::kCasLicenseError, "no crypto session"); + } + + std::string response; + ExtractAndDecodeSignedMessage(signed_provisioning_response, &response); + if (response.empty()) { + return CasStatus(CasStatusCode::kIndividualizationError, + "response message is an invalid JSON/base64 string"); + } + SignedProvisioningMessage signed_response; + if (!signed_response.ParseFromString(response)) { + return CasStatus(CasStatusCode::kIndividualizationError, + "unable to parse response"); + } + if (!signed_response.has_signature()) { + return CasStatus(CasStatusCode::kIndividualizationError, + "signature not found"); + } + + if (!signed_response.has_message()) { + return CasStatus(CasStatusCode::kIndividualizationError, + "message not found"); + } + + if (!signed_response.has_oemcrypto_core_message() || + signed_response.oemcrypto_core_message().empty()) { + return CasStatus(CasStatusCode::kIndividualizationError, + "Signed response does not have core message"); + } + + const std::string& signed_message = signed_response.message(); + const std::string& signature = signed_response.signature(); + const std::string& core_message = signed_response.oemcrypto_core_message(); + ProvisioningResponse provisioning_response; + if (!provisioning_response.ParseFromString(signed_message)) { + return CasStatus(CasStatusCode::kIndividualizationError, + "unable to parse signed message"); + } + + CasStatus status = crypto_session_->LoadProvisioning( + signed_message, core_message, signature, wrapped_rsa_key); + if (status.status_code() != CasStatusCode::kNoError) { + return CasStatus(CasStatusCode::kIndividualizationError, + ("rewrapCertificate fails")); + } + + if (nullptr != device_file) { + File file; + file.set_type(File::DEVICE_CERTIFICATE); + file.mutable_device_certificate()->set_certificate( + provisioning_response.device_certificate()); + file.mutable_device_certificate()->set_wrapped_private_key( + *wrapped_rsa_key); + + HashedFile hash_file; + file.SerializeToString(hash_file.mutable_file()); + if (!Hash(hash_file.file(), hash_file.mutable_hash())) { + return CasStatus(CasStatusCode::kIndividualizationError, + ("generating file hash fails")); + } + hash_file.SerializeToString(device_file); + } + + *device_certificate = provisioning_response.device_certificate(); + return crypto_session_->reset(); +} + +CasStatus CasLicense::GenerateEntitlementRequest( + const std::string& init_data, const std::string& device_certificate, + const std::string& wrapped_rsa_key, LicenseType license_type, + std::string* signed_license_request) { + if (!signed_license_request) { + return CasStatus(CasStatusCode::kCasLicenseError, "invalid parameters"); + } + + LicenseRequest license_request; + ClientIdentification* client_id = license_request.mutable_client_id(); + client_id->set_type(ClientIdentification::DRM_DEVICE_CERTIFICATE); + client_id->set_token(device_certificate); + + // Call OEMCrypto_GetOEMPublicCertificate before OEMCrypto_LoadDeviceRSAKey + // so it caches the OEMCrypto Public Key and then throw away result. (Prov3.0) + std::vector buf(kCertificateDataSizeBytes, 0); + size_t buf_size = buf.size(); + CasStatus status = + crypto_session_->GetOEMPublicCertificate(buf.data(), &buf_size); + status = crypto_session_->LoadDeviceRSAKey( + reinterpret_cast(wrapped_rsa_key.data()), + wrapped_rsa_key.size()); + if (!status.ok()) { + return status; + } + + std::string value; + ClientIdentification_NameValue* client_info = nullptr; + if (Properties::GetCompanyName(&value)) { + client_info = client_id->add_client_info(); + client_info->set_name(kKeyCompanyName); + client_info->set_value(value); + } + if (Properties::GetModelName(&value)) { + client_info = client_id->add_client_info(); + client_info->set_name(kKeyModelName); + client_info->set_value(value); + } + if (Properties::GetProductName(&value)) { + client_info = client_id->add_client_info(); + client_info->set_name(kKeyProductName); + client_info->set_value(value); + } + if (Properties::GetArchitectureName(&value)) { + client_info = client_id->add_client_info(); + client_info->set_name(kKeyArchitectureName); + client_info->set_value(value); + } + if (Properties::GetDeviceName(&value)) { + client_info = client_id->add_client_info(); + client_info->set_name(kKeyDeviceName); + client_info->set_value(value); + } + if (Properties::GetWvCasPluginVersion(value)) { + client_info = client_id->add_client_info(); + client_info->set_name(kKeyWvCasVersion); + client_info->set_value(value); + } + + ClientIdentification_ClientCapabilities* client_capabilities = + client_id->mutable_client_capabilities(); + + // TODO(jfore): + // client_capabilities->set_session_token(supports_usage_information)? Is this + // for usage tables? + + // TODO(jfore): Anti rollback support? + + uint32_t api_version = 0; + status = crypto_session_->APIVersion(&api_version); + if (!status.ok()) { + return status; + } + client_capabilities->set_oem_crypto_api_version(api_version); + + // TODO(jfore): Handle hdcp capabilities. + + video_widevine::LicenseRequest_ContentIdentification* content_id = + license_request.mutable_content_id(); + + video_widevine::LicenseRequest_ContentIdentification_CencDeprecated* + cenc_content_id = content_id->mutable_cenc_id_deprecated(); + cenc_content_id->add_pssh(init_data); + cenc_content_id->set_license_type(video_widevine::AUTOMATIC); + + license_request.set_type(video_widevine::LicenseRequest::NEW); + license_request.set_request_time(GetCurrentTime()); + + uint32_t nonce; + status = crypto_session_->GenerateNonce(&nonce); + if (status.status_code() != CasStatusCode::kNoError) { + return status; + } + license_request.set_key_control_nonce(nonce); + license_request.set_protocol_version(video_widevine::VERSION_2_1); + + // License request is complete. Serialize it. + std::string serialized_license_req; + license_request.SerializeToString(&serialized_license_req); + + // Derive signing and encryption keys and construct core message and + // signature. + std::string core_message; + std::string license_request_signature; + bool should_specify_algorithm = false; + OEMCrypto_SignatureHashAlgorithm oec_algorithm = OEMCrypto_SHA1; + status = crypto_session_->PrepareAndSignLicenseRequest( + serialized_license_req, &core_message, &license_request_signature, + should_specify_algorithm, oec_algorithm); + + if (status.status_code() != CasStatusCode::kNoError) { + return status; + } + if (license_request_signature.empty()) { + LOGE("License request signature is empty"); + signed_license_request->clear(); + return CasStatus(CasStatusCode::kCasLicenseError, + "License request signature is empty"); + } + + // Put serialized license request and signature together + SignedMessage signed_message; + signed_message.set_type(SignedMessage::CAS_LICENSE_REQUEST); + signed_message.set_signature(license_request_signature); + signed_message.set_msg(serialized_license_req); + signed_message.set_oemcrypto_core_message(core_message); + if (should_specify_algorithm) { + HashAlgorithmProto proto_algorithm = + HashAlgorithmProto::HASH_ALGORITHM_UNSPECIFIED; + if (!OecAlgorithmToProtoAlgorithm(oec_algorithm, proto_algorithm)) { + return CasStatus(CasStatusCode::kCasLicenseError, + "unsupported signature hash algorithm"); + } + signed_message.set_hash_algorithm(proto_algorithm); + } + signed_message.SerializeToString(signed_license_request); + + init_data_ = init_data; + emm_request_ = serialized_license_req; + + return CasStatusCode::kNoError; +} + +CasStatus CasLicense::HandleStoredLicense(const std::string& wrapped_rsa_key, + const std::string& license_file) { + HashedFile hash_file; + if (!hash_file.ParseFromString(license_file)) { + return CasStatus(CasStatusCode::kLicenseFileParseError, + "unable to parse license file"); + } + std::string file_hash; + if (!Hash(hash_file.file(), &file_hash)) { + return CasStatus(CasStatusCode::kLicenseFileParseError, + ("generating file hash fails")); + } + if (hash_file.hash() != file_hash) { + return CasStatus(CasStatusCode::kLicenseFileParseError, + ("corrupt license file data")); + } + File file; + if (!file.ParseFromString(hash_file.file())) { + return CasStatus(CasStatusCode::kLicenseFileParseError, + ("unable to parse the file data")); + } + if (file.type() != File::LICENSE) { + return CasStatus(CasStatusCode::kLicenseFileParseError, + ("invalid file type")); + } + const video_widevine_client::sdk::License& license = file.license(); + + if (license.license_request().empty()) { + return CasStatus(CasStatusCode::kInvalidLicenseFile, ("missing request")); + } + if (license.license().empty()) { + return CasStatus(CasStatusCode::kInvalidLicenseFile, ("missing license")); + } + + emm_request_ = license.license_request(); + emm_response_ = license.license(); + renewal_request_ = license.renewal_request(); + renewal_response_ = license.renewal(); + init_data_ = license.pssh_data(); + + video_widevine::SignedMessage signed_message; + if (!signed_message.ParseFromString(license.license())) { + return CasStatus(CasStatusCode::kInvalidLicenseFile, + "unable to parse license"); + } + + // Install the rsa provisioning key. + CasStatus status = crypto_session_->LoadDeviceRSAKey( + reinterpret_cast(wrapped_rsa_key.data()), + wrapped_rsa_key.size()); + if (!status.ok()) { + return status; + } + + video_widevine::SignedMessage signed_message_renewal; + is_renewal_in_license_file_ = false; + if (!renewal_response_.empty()) { + if (!signed_message_renewal.ParseFromString(renewal_response_)) { + return CasStatus(CasStatusCode::kInvalidLicenseFile, + "unable to parse license renewal response"); + } + // Disable the expiration callback when renewal file is a part of license + // file. + is_renewal_in_license_file_ = true; + } + + status = InstallLicense(signed_message.session_key(), signed_message.msg(), + signed_message.oemcrypto_core_message(), + signed_message.signature()); + if (!status.ok()) { + return status; + } + + if (is_renewal_in_license_file_) { + status = + InstallLicenseRenewal(signed_message_renewal.msg(), + signed_message_renewal.oemcrypto_core_message(), + signed_message_renewal.signature()); + // Reset the flag so expiration callback is enabled. + is_renewal_in_license_file_ = false; + // Expiration event is expected to be fired if license is shown expired. + OnLicenseExpiration(); + return status; + } + policy_engine_->RestorePlaybackTimes(license.playback_start_time(), + license.last_playback_time(), + license.grace_period_end_time()); + return CasStatusCode::kNoError; +} + +CasStatus CasLicense::GetEntitlementPeriodIndexFromStoredLicense( + const std::string& license_file, uint32_t& entitlement_period_index) { + HashedFile hash_file; + if (!hash_file.ParseFromString(license_file)) { + return CasStatus(CasStatusCode::kLicenseFileParseError, + "unable to parse license file"); + } + std::string file_hash; + if (!Hash(hash_file.file(), &file_hash)) { + return CasStatus(CasStatusCode::kLicenseFileParseError, + "generating file hash fails"); + } + if (hash_file.hash() != file_hash) { + return CasStatus(CasStatusCode::kLicenseFileParseError, + "corrupt license file data"); + } + File file; + if (!file.ParseFromString(hash_file.file())) { + return CasStatus(CasStatusCode::kLicenseFileParseError, + "unable to parse the file data"); + } + if (file.type() != File::LICENSE) { + return CasStatus(CasStatusCode::kLicenseFileParseError, + "invalid file type"); + } + // Get PSSH from license request. + LicenseRequest license_request; + if (!license_request.ParseFromString(file.license().license_request())) { + return CasStatus(CasStatusCode::kLicenseFileParseError, + "invalid license request"); + } + if (license_request.content_id().cenc_id_deprecated().pssh_size() == 0) { + return CasStatus(CasStatusCode::kLicenseFileParseError, "no pssh"); + } + // Only one PSSH should exist in the request. + video_widevine::WidevinePsshData pssh; + if (!pssh.ParseFromString( + license_request.content_id().cenc_id_deprecated().pssh(0))) { + return CasStatus(CasStatusCode::kLicenseFileParseError, "invalid pssh"); + } + + if (!pssh.has_entitlement_period_index()) { + return CasStatus(CasStatusCode::kCasLicenseError, + "no entitlement period index"); + } + entitlement_period_index = pssh.entitlement_period_index(); + return CasStatusCode::kNoError; +} + +CasStatus CasLicense::HandleEntitlementResponse( + const std::string& entitlement_response, std::string* device_file) { + video_widevine::SignedMessage signed_message; + if (!signed_message.ParseFromString(entitlement_response)) { + return CasStatus(CasStatusCode::kCasLicenseError, + "unable to parse entitlement message"); + } + if (signed_message.type() != video_widevine::SignedMessage::CAS_LICENSE) { + return CasStatus(CasStatusCode::kCasLicenseError, + "invalid entitlement response type"); + } + if (!signed_message.has_signature()) { + return CasStatus(CasStatusCode::kCasLicenseError, + "missing entitlement signature"); + } + if (!signed_message.has_session_key()) { + return CasStatus(CasStatusCode::kCasLicenseError, + "no session keys present"); + } + if (!signed_message.has_oemcrypto_core_message()) { + return CasStatus(CasStatusCode::kCasLicenseError, + "no oemcrypto core message present"); + } + + CasStatus status = InstallLicense( + signed_message.session_key(), signed_message.msg(), + signed_message.oemcrypto_core_message(), signed_message.signature()); + if (!status.ok()) { + return status; + } + + emm_response_ = entitlement_response; + if (CanStoreLicense() && device_file) { + status = GenerateLicenseFile( + emm_request_, emm_response_, renewal_request_, renewal_response_, + policy_engine_->GetPlaybackStartTime(), + policy_engine_->GetLastPlaybackTime(), + policy_engine_->GetGracePeriodEndTime(), device_file); + if (!status.ok()) { + // Log a warning, playback should continue. + LOGW("Unable to generate license file: %s", + status.error_string().c_str()); + device_file->clear(); + } + } + return CasStatusCode::kNoError; +} + +// TODO(jfore): Refactor this. Much of this code is likely common to using +// prov 3.0. +CasStatus CasLicense::GenerateDeviceProvisioningRequestWithKeybox( + std::string* serialized_provisioning_request) const { + ProvisioningRequest provisioning_request; + + ClientIdentification* client_id = provisioning_request.mutable_client_id(); + client_id->set_type(video_widevine::ClientIdentification::KEYBOX); + std::vector token(kKeyboxTokenSizeBytes, 0); + size_t size = kKeyboxTokenSizeBytes; + CasStatus status = crypto_session_->GetKeyData(token.data(), &size); + if (status.status_code() != CasStatusCode::kNoError) { + return status; + } + client_id->mutable_token()->assign(token.begin(), token.end()); + + ClientIdentification_ClientCapabilities* client_capabilities = + client_id->mutable_client_capabilities(); + + SupportedCertificates supported_certs = + crypto_session_->supported_certificates(); + if (supported_certs.rsa_2048bit()) { + client_capabilities->add_supported_certificate_key_type( + ClientIdentification_ClientCapabilities_CertificateKeyType_RSA_2048); + } + if (supported_certs.rsa_3072bit()) { + client_capabilities->add_supported_certificate_key_type( + ClientIdentification_ClientCapabilities_CertificateKeyType_RSA_3072); + } + + // TODO(jfore): Handle service certificate. + + uint32_t nonce = 0; + status = crypto_session_->GenerateNonce(&nonce); + if (status.status_code() != CasStatusCode::kNoError) { + return CasStatus(CasStatusCode::kCasLicenseError, "forced error"); + } + provisioning_request.mutable_nonce()->assign(reinterpret_cast(&nonce), + sizeof(nonce)); + + video_widevine::ProvisioningOptions* options = + provisioning_request.mutable_options(); + options->set_certificate_type( + video_widevine::ProvisioningOptions_CertificateType_WIDEVINE_DRM); + status = crypto_session_->GetDeviceID(provisioning_request.mutable_spoid()); + if (!status.ok()) { + return status; + } + + SignedProvisioningMessage signed_provisioning_msg; + std::string* serialized_message = signed_provisioning_msg.mutable_message(); + provisioning_request.SerializeToString(serialized_message); + + std::string mac_deriv_message; + std::string enc_deriv_message; + GenerateMacContext(*serialized_message, &mac_deriv_message); + GenerateEncryptContext(*serialized_message, &enc_deriv_message); + status = crypto_session_->GenerateDerivedKeys( + reinterpret_cast(mac_deriv_message.data()), + mac_deriv_message.size(), + reinterpret_cast(enc_deriv_message.data()), + enc_deriv_message.size()); + if (status.status_code() != CasStatusCode::kNoError) { + return status; + } + + // Construct signature and core message. + std::string core_message; + std::string provisioning_request_signature; + bool should_specify_algorithm = false; + OEMCrypto_SignatureHashAlgorithm oec_algorithm = OEMCrypto_SHA1; + status = crypto_session_->PrepareAndSignProvisioningRequest( + *serialized_message, &core_message, &provisioning_request_signature, + should_specify_algorithm, oec_algorithm); + if (status.status_code() != CasStatusCode::kNoError) { + return status; + } + if (provisioning_request_signature.empty()) { + LOGE("Provisioning request signature is empty"); + return CasStatus(CasStatusCode::kCryptoSessionError, + "Provisioning request signature is empty"); + } + + signed_provisioning_msg.set_signature(provisioning_request_signature); + signed_provisioning_msg.set_oemcrypto_core_message(core_message); + signed_provisioning_msg.set_protocol_version( + SignedProvisioningMessage::PROVISIONING_20); + if (should_specify_algorithm) { + HashAlgorithmProto proto_algorithm = + HashAlgorithmProto::HASH_ALGORITHM_UNSPECIFIED; + if (!OecAlgorithmToProtoAlgorithm(oec_algorithm, proto_algorithm)) { + return CasStatus(CasStatusCode::kCryptoSessionError, + "unsupported signature hash algorithm"); + } + signed_provisioning_msg.set_hash_algorithm(proto_algorithm); + } + signed_provisioning_msg.SerializeToString(serialized_provisioning_request); + + std::vector buffer; + buffer.assign(serialized_provisioning_request->begin(), + serialized_provisioning_request->end()); + serialized_provisioning_request->assign(Base64SafeEncodeNoPad(buffer)); + + return CasStatusCode::kNoError; +} + +// TODO(jfore): Currently not functional, implement me as needed. +CasStatus CasLicense::GenerateDeviceProvisioningRequestWithOEMCert() const { + ProvisioningRequest provisioning_request; + ClientIdentification* client_id = provisioning_request.mutable_client_id(); + client_id->set_type( + video_widevine::ClientIdentification::OEM_DEVICE_CERTIFICATE); + return CasStatusCode::kNoError; +} + +CasStatus CasLicense::InstallLicense(const std::string& session_key, + const std::string& serialized_license, + const std::string& core_message, + const std::string& signature) { + video_widevine::License license; + if (!license.ParseFromString(serialized_license)) { + return CasStatus(CasStatusCode::kCasLicenseError, + "unable to parse entitlement response"); + } + + std::string mac_deriv_message; + std::string enc_deriv_message; + GenerateMacContext(emm_request_, &mac_deriv_message); + GenerateEncryptContext(emm_request_, &enc_deriv_message); + CasStatus status = crypto_session_->DeriveKeysFromSessionKey( + reinterpret_cast(session_key.data()), session_key.size(), + reinterpret_cast(mac_deriv_message.data()), + mac_deriv_message.size(), + reinterpret_cast(enc_deriv_message.data()), + enc_deriv_message.size()); + if (status.status_code() != CasStatusCode::kNoError) { + return status; + } + + // Extract mac key + std::string mac_key_iv_str; + std::string mac_key_str; + for (int i = 0; i < license.key_size(); ++i) { + if (license.key(i).type() == License_KeyContainer::SIGNING) { + mac_key_iv_str.assign(license.key(i).iv()); + mac_key_str.assign(license.key(i).key()); + } + } + if (license.policy().can_renew() || + (mac_key_iv_str.size() != 0 || mac_key_str.size() != 0)) { + // In V2.1 license protocol, the expected mac key size is 80 bytes: 32 bytes + // server key; 32 bytes client key; 16 bytes of padding. + if (mac_key_iv_str.size() != kIvSizeBytes || + mac_key_str.size() != 2 * kMacKeySizeBytes + kMacKeyPaddingSizeBytes) { + std::ostringstream err_string; + err_string << "mac key/iv size error (key/iv size expected: " + << 2 * kMacKeySizeBytes + kMacKeyPaddingSizeBytes << "/" + << kIvSizeBytes << " actual: " << mac_key_str.size() << "/" + << mac_key_iv_str.size(); + return CasStatus(CasStatusCode::kCryptoSessionError, err_string.str()); + } + // Strip off the padding. + mac_key_str.resize(2 * kMacKeySizeBytes); + } + + status = + crypto_session_->LoadLicense(serialized_license, core_message, signature); + if (!status.ok()) { + return status; + } + policy_engine_->SetLicense(license); + license_.CopyFrom(license); + return CasStatusCode::kNoError; +} + +CasStatus CasLicense::InstallLicenseRenewal( + const std::string& serialized_license, const std::string& core_message, + const std::string& signature) { + video_widevine::License license; + if (!license.ParseFromString(serialized_license)) { + return CasStatus(CasStatusCode::kCasLicenseError, + "unable to parse license renewal"); + } + + CasStatus status = + crypto_session_->LoadRenewal(serialized_license, core_message, signature); + if (!status.ok()) { + return status; + } + + policy_engine_->UpdateLicense(license); + license_.CopyFrom(license); + return CasStatusCode::kNoError; +} + +CasStatus CasLicense::HandleStoredDrmCert(const std::string& certificate, + std::string* device_certificate, + std::string* wrapped_rsa_key) { + if (nullptr == device_certificate || nullptr == wrapped_rsa_key) { + return CasStatus(CasStatusCode::kIndividualizationError, + ("missing output parameters")); + } + HashedFile hash_file; + if (!hash_file.ParseFromString(certificate)) { + return CasStatus(CasStatusCode::kIndividualizationError, + ("unable to parse the certificate data")); + } + std::string file_hash; + if (!Hash(hash_file.file(), &file_hash)) { + return CasStatus(CasStatusCode::kIndividualizationError, + ("generating file hash fails")); + } + + if (hash_file.hash() != file_hash) { + return CasStatus(CasStatusCode::kIndividualizationError, + ("corrupt certificate file data")); + } + + File file; + if (!file.ParseFromString(hash_file.file())) { + return CasStatus(CasStatusCode::kIndividualizationError, + ("unable to parse the file data")); + } + if (file.type() != File::DEVICE_CERTIFICATE) { + return CasStatus(CasStatusCode::kIndividualizationError, + ("invalid certificate type")); + } + + if (file.device_certificate().certificate().empty() || + file.device_certificate().wrapped_private_key().empty()) { + return CasStatus(CasStatusCode::kIndividualizationError, + ("unable to use this certificate")); + } + + *device_certificate = file.device_certificate().certificate(); + *wrapped_rsa_key = file.device_certificate().wrapped_private_key(); + return CasStatusCode::kNoError; +} + +bool CasLicense::CanDecryptContent(const KeyId& key_id) const { + return policy_engine_->CanDecryptContent(key_id); +} + +bool CasLicense::CanStoreLicense() const { + return policy_engine_->CanPersist(); +} + +std::string CasLicense::GetGroupId() const { + return license_.license_category_spec().group_id(); +} + +std::vector CasLicense::GetContentIdList() const { + std::set content_ids; + if (IsMultiContentLicense()) { + for (const auto& license_key : license_.key()) { + if (license_key.type() == License_KeyContainer::ENTITLEMENT) { + if (license_key.key_category_spec().has_content_id()) { + content_ids.insert(license_key.key_category_spec().content_id()); + } else if (license_key.key_category_spec().has_group_id()) { + content_ids.insert(license_key.key_category_spec().group_id()); + } + } + } + } + return {content_ids.begin(), content_ids.end()}; +} + +bool CasLicense::IsMultiContentLicense() const { + return license_.license_category_spec().license_category() == + video_widevine::LicenseCategorySpec::MULTI_CONTENT_LICENSE; +} + +bool CasLicense::IsGroupLicense() const { + return license_.license_category_spec().license_category() == + video_widevine::LicenseCategorySpec::GROUP_LICENSE; +} + +CasStatus CasLicense::GenerateEntitlementRenewalRequest( + const std::string& device_certificate, + std::string* signed_renewal_request) { + if (!signed_renewal_request) { + return CasStatus(CasStatusCode::kCasLicenseError, + "invalid renewal request parameters"); + } + if (!policy_engine_->CanRenew()) { + return CasStatus(CasStatusCode::kCasLicenseError, + "policy inhibits renewal"); + } + LicenseRequest license_request; + license_request.set_type(LicenseRequest::RENEWAL); + license_request.set_request_time(GetCurrentTime()); + license_request.mutable_content_id() + ->mutable_existing_license() + ->mutable_license_id() + ->CopyFrom(policy_engine_->license_id()); + + if (policy_engine_->always_include_client_id()) { + ClientIdentification* client_id = license_request.mutable_client_id(); + client_id->set_type(ClientIdentification::DRM_DEVICE_CERTIFICATE); + client_id->set_token(device_certificate); + } + // OEMCrypto_GenerateNonce can only be called once in each session, and it + // must be called before signing either a provisioning request or a license + // request. + license_request.set_key_control_nonce(0); + license_request.set_protocol_version(video_widevine::VERSION_2_1); + + // License request is complete. Serialize it. + std::string serialized_license_req; + license_request.SerializeToString(&serialized_license_req); + + // Construct signature and core message. + std::string core_message; + std::string license_request_signature; + CasStatus status = crypto_session_->PrepareAndSignRenewalRequest( + serialized_license_req, &core_message, &license_request_signature); + + if (status.status_code() != CasStatusCode::kNoError) { + return status; + } + if (license_request_signature.empty()) { + LOGE("License request signature is empty"); + return CasStatus(CasStatusCode::kCryptoSessionError, + "License request signature is empty"); + } + + // Put serialize license request and signature together + SignedMessage signed_message; + signed_message.set_type(SignedMessage::CAS_LICENSE_REQUEST); + signed_message.set_signature(license_request_signature); + signed_message.set_msg(serialized_license_req); + signed_message.set_oemcrypto_core_message(core_message); + signed_message.SerializeToString(signed_renewal_request); + + renewal_request_ = serialized_license_req; + return CasStatusCode::kNoError; +} + +CasStatus CasLicense::HandleEntitlementRenewalResponse( + const std::string& renewal_response, std::string* device_file) { + video_widevine::SignedMessage signed_message; + if (!signed_message.ParseFromString(renewal_response)) { + return CasStatus(CasStatusCode::kCasLicenseError, + "unable to parse renewal message"); + } + if (signed_message.type() != video_widevine::SignedMessage::CAS_LICENSE) { + return CasStatus(CasStatusCode::kCasLicenseError, + "invalid renewal response type"); + } + if (!signed_message.has_signature()) { + return CasStatus(CasStatusCode::kCasLicenseError, + "missing renewal signature"); + } + if (!signed_message.has_msg()) { + return CasStatus(CasStatusCode::kCasLicenseError, + "missing renewal message"); + } + if (!signed_message.has_oemcrypto_core_message()) { + return CasStatus(CasStatusCode::kCasLicenseError, + "no oemcrypto core message present"); + } + + CasStatus status = InstallLicenseRenewal( + signed_message.msg(), signed_message.oemcrypto_core_message(), + signed_message.signature()); + if (!status.ok()) { + return status; + } + + renewal_response_ = renewal_response; + if (CanStoreLicense() && device_file) { + return GenerateLicenseFile( + emm_request_, emm_response_, renewal_request_, renewal_response_, + policy_engine_->GetPlaybackStartTime(), + policy_engine_->GetLastPlaybackTime(), + policy_engine_->GetGracePeriodEndTime(), device_file); + } + return CasStatusCode::kNoError; +} + +bool CasLicense::IsExpired() const { return policy_engine_->IsExpired(); } + +void CasLicense::BeginDecryption() { policy_engine_->BeginDecryption(); } + +void CasLicense::OnTimerEvent() { policy_engine_->OnTimerEvent(); } + +std::unique_ptr CasLicense::GetPolicyEngine() { + return make_unique(); +} + +void CasLicense::UpdateLicenseForLicenseRemove() { + if (!license_.has_policy()) { + return; + } + + license_.mutable_policy()->set_can_persist(false); + license_.mutable_policy()->set_can_play(false); + license_.mutable_policy()->set_can_renew(false); + + policy_engine_->UpdateLicense(license_); +} + +void CasLicense::OnLicenseExpiration() { + if (event_listener_ && IsExpired() && !is_renewal_in_license_file_) { + event_listener_->OnLicenseExpiration(); + } +} + +void CasLicense::OnNewRenewalServerUrl(const std::string& renewal_server_url) { + if (event_listener_) { + event_listener_->OnNewRenewalServerUrl(renewal_server_url); + } +} + +void CasLicense::OnSessionRenewalNeeded() { + if (event_listener_) { + event_listener_->OnSessionRenewalNeeded(); + } +} + +void CasLicense::OnSessionKeysChange(const wvcas::KeyStatusMap& keys_status, + bool has_new_usable_key) { + if (event_listener_) { + event_listener_->OnSessionKeysChange(keys_status, has_new_usable_key); + } +} + +void CasLicense::OnExpirationUpdate(int64_t new_expiry_time_seconds) { + if (event_listener_) { + event_listener_->OnExpirationUpdate(new_expiry_time_seconds); + } +} + +void CasLicense::OnAgeRestrictionUpdated(const WvCasSessionId& sessionId, + uint8_t ecm_age_restriction) { + if (event_listener_) { + event_listener_->OnAgeRestrictionUpdated(sessionId, ecm_age_restriction); + } +} + +void CasLicense::OnSessionFingerprintingUpdated(const WvCasSessionId& sessionId, + const CasData& fingerprinting) { + if (event_listener_ != nullptr) { + event_listener_->OnSessionFingerprintingUpdated(sessionId, fingerprinting); + } +} + +void CasLicense::OnSessionServiceBlockingUpdated( + const WvCasSessionId& sessionId, const CasData& service_blocking) { + if (event_listener_ != nullptr) { + event_listener_->OnSessionServiceBlockingUpdated(sessionId, + service_blocking); + } +} + +void CasLicense::OnFingerprintingUpdated(const CasData& fingerprinting) { + if (event_listener_ != nullptr) { + event_listener_->OnFingerprintingUpdated(fingerprinting); + } +} + +void CasLicense::OnServiceBlockingUpdated(const CasData& fingerprinting) { + if (event_listener_ != nullptr) { + event_listener_->OnServiceBlockingUpdated(fingerprinting); + } +} + +void CasLicense::OnEntitlementPeriodUpdateNeeded( + const std::string& signed_license_request) { + if (event_listener_ != nullptr) { + event_listener_->OnEntitlementPeriodUpdateNeeded(signed_license_request); + } +} + +} // namespace wvcas diff --git a/plugin/src/crypto_session.cpp b/plugin/src/crypto_session.cpp new file mode 100644 index 0000000..3e22d54 --- /dev/null +++ b/plugin/src/crypto_session.cpp @@ -0,0 +1,1083 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#include "crypto_session.h" + +#include + +#include +#include + +#include "cas_util.h" +#include "log.h" + +namespace wvcas { +namespace { +constexpr uint32_t kExpectedOEMCryptoVersion = 18; + +OEMCryptoCipherMode CipherModeFromKeyData(CryptoMode key_cipher) { + switch (key_cipher) { + case CryptoMode::kAesCBC: + return OEMCrypto_CipherMode_CBC; + case CryptoMode::kAesCTR: + return OEMCrypto_CipherMode_CTR; + case CryptoMode::kDvbCsa2: + return OEMCrypto_CipherMode_CSA2; + case CryptoMode::kDvbCsa3: + return OEMCrypto_CipherMode_CSA3; + case CryptoMode::kAesOFB: + return OEMCrypto_CipherMode_OFB; + case CryptoMode::kAesSCTE: + return OEMCrypto_CipherMode_SCTE; + case CryptoMode::kAesECB: + return OEMCrypto_CipherMode_ECB; + default: + return OEMCrypto_CipherMode_CTR; + }; +} + +// Fill an oemcrypto entitled key object from the provided key data. +void FillEntitledContentKeyObjectFromKeyData( + std::string& message, const KeySlot& src, + OEMCrypto_EntitledContentKeyObject* dest) { + if (nullptr == dest) { + return; + } + std::string entitlement_key_id(src.entitlement_key_id.begin(), + src.entitlement_key_id.end()); + std::string content_key_id(src.key_id.begin(), src.key_id.end()); + std::string content_key_data_iv(src.wrapped_key_iv.begin(), + src.wrapped_key_iv.end()); + std::string content_key_data(src.wrapped_key.begin(), src.wrapped_key.end()); + std::string content_iv(src.content_iv.begin(), src.content_iv.end()); + + dest->entitlement_key_id.offset = message.size(); + message += entitlement_key_id; + dest->entitlement_key_id.length = entitlement_key_id.length(); + + dest->content_key_id.offset = message.size(); + message += content_key_id; + dest->content_key_id.length = content_key_id.length(); + + dest->content_key_data_iv.offset = message.size(); + message += content_key_data_iv; + dest->content_key_data_iv.length = content_key_data_iv.length(); + + dest->content_key_data.offset = message.size(); + message += content_key_data; + dest->content_key_data.length = content_key_data.length(); + + dest->content_iv.offset = message.size(); + message += content_iv; + dest->content_iv.length = content_iv.length(); + + dest->cipher_mode = CipherModeFromKeyData(src.cipher_mode); +} + +} // namespace + +wvutil::shared_mutex CryptoLock::static_field_mutex_; +wvutil::shared_mutex CryptoLock::oem_crypto_mutex_; +template +auto CryptoLock::WithStaticFieldWriteLock(const char* tag, + Func body) -> decltype(body()) { + LOGV("Static field write lock: %s", tag); + std::unique_lock auto_lock(static_field_mutex_); + return body(); +} +template +auto CryptoLock::WithStaticFieldReadLock(const char* tag, + Func body) -> decltype(body()) { + LOGV("Static field read lock: %s", tag); + wvutil::shared_lock auto_lock(static_field_mutex_); + return body(); +} +template +auto CryptoLock::WithOecWriteLock(const char* tag, + Func body) -> decltype(body()) { + LOGV("OEMCrypto write lock: %s", tag); + std::unique_lock auto_lock(oem_crypto_mutex_); + return body(); +} +template +auto CryptoLock::WithOecReadLock(const char* tag, + Func body) -> decltype(body()) { + LOGV("OEMCrypto read lock: %s", tag); + wvutil::shared_lock auto_lock(oem_crypto_mutex_); + return body(); +} +template +auto CryptoLock::WithOecSessionLock(const char* tag, + Func body) -> decltype(body()) { + LOGV("OEMCrypto Session Lock - %s", tag); + wvutil::shared_lock oec_auto_lock(oem_crypto_mutex_); + std::unique_lock session_auto_lock(oem_crypto_session_mutex_); + return body(); +} + +bool CryptoInterface::initialized_ = false; +int CryptoInterface::session_count_ = 0; +std::unique_ptr CryptoInterface::lock_ = make_unique(); + +OEMCryptoResult CryptoInterface::OEMCrypto_OpenSession( + OEMCrypto_SESSION* session) { + return lock_->WithOecWriteLock("OpenSession", [&] { + return oemcrypto_interface_->OEMCrypto_OpenSession(session); + }); +} + +OEMCryptoResult CryptoInterface::OEMCrypto_CloseSession( + OEMCrypto_SESSION session) { + return lock_->WithOecWriteLock("CloseSession", [&] { + return oemcrypto_interface_->OEMCrypto_CloseSession(session); + }); +} + +OEMCrypto_ProvisioningMethod +CryptoInterface::OEMCrypto_GetProvisioningMethod() { + return lock_->WithOecReadLock("GetProvisioningMethod", [&] { + return oemcrypto_interface_->OEMCrypto_GetProvisioningMethod(); + }); +} + +OEMCryptoResult CryptoInterface::OEMCrypto_GetKeyData(uint8_t* keyData, + size_t* keyDataLength) { + return lock_->WithOecReadLock("GetKeyData", [&] { + return oemcrypto_interface_->OEMCrypto_GetKeyData(keyData, keyDataLength); + }); +} + +uint32_t CryptoInterface::OEMCrypto_SupportedCertificates() { + return lock_->WithOecReadLock("SupportedCertificates", [&] { + return oemcrypto_interface_->OEMCrypto_SupportedCertificates(); + }); +} + +OEMCryptoResult CryptoInterface::OEMCrypto_GenerateNonce( + OEMCrypto_SESSION session, uint32_t* nonce) { + return lock_->WithOecWriteLock("GenerateNonce", [&] { + return oemcrypto_interface_->OEMCrypto_GenerateNonce(session, nonce); + }); +} + +OEMCryptoResult CryptoInterface::OEMCrypto_GenerateDerivedKeys( + OEMCrypto_SESSION session, const uint8_t* mac_key_context, + uint32_t mac_key_context_length, const uint8_t* enc_key_context, + uint32_t enc_key_context_length) { + return lock_->WithOecSessionLock("GenerateDerivedKeys", [&] { + return oemcrypto_interface_->OEMCrypto_GenerateDerivedKeys( + session, mac_key_context, mac_key_context_length, enc_key_context, + enc_key_context_length); + }); +} + +OEMCryptoResult CryptoInterface::OEMCrypto_PrepAndSignLicenseRequest( + OEMCrypto_SESSION session, uint8_t* message, size_t message_length, + size_t* core_message_size, uint8_t* signature, size_t* signature_length) { + return lock_->WithOecSessionLock("PrepAndSignLicenseRequest", [&] { + return oemcrypto_interface_->OEMCrypto_PrepAndSignLicenseRequest( + session, message, message_length, core_message_size, signature, + signature_length); + }); +} + +OEMCryptoResult CryptoInterface::OEMCrypto_PrepAndSignRenewalRequest( + OEMCrypto_SESSION session, uint8_t* message, size_t message_length, + size_t* core_message_size, uint8_t* signature, size_t* signature_length) { + return lock_->WithOecSessionLock("PrepareAndSignRenewalRequest", [&] { + return oemcrypto_interface_->OEMCrypto_PrepAndSignRenewalRequest( + session, message, message_length, core_message_size, signature, + signature_length); + }); +} + +OEMCryptoResult CryptoInterface::OEMCrypto_PrepAndSignProvisioningRequest( + OEMCrypto_SESSION session, uint8_t* message, size_t message_length, + size_t* core_message_size, uint8_t* signature, size_t* signature_length) { + return lock_->WithOecSessionLock("PrepareAndSignProvisioningRequest", [&] { + return oemcrypto_interface_->OEMCrypto_PrepAndSignProvisioningRequest( + session, message, message_length, core_message_size, signature, + signature_length); + }); +} + +OEMCryptoResult CryptoInterface::OEMCrypto_LoadProvisioning( + OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, + size_t core_message_length, const uint8_t* signature, + size_t signature_length, uint8_t* wrapped_private_key, + size_t* wrapped_private_key_length) { + return lock_->WithOecSessionLock("LoadProvisioning", [&] { + return oemcrypto_interface_->OEMCrypto_LoadProvisioning( + session, message, message_length, core_message_length, signature, + signature_length, wrapped_private_key, wrapped_private_key_length); + }); +} + +OEMCryptoResult CryptoInterface::OEMCrypto_GetOEMPublicCertificate( + OEMCrypto_SESSION session, uint8_t* public_cert, + size_t* public_cert_length) { + return lock_->WithOecSessionLock("GetOEMPublicCertificate", [&] { + return oemcrypto_interface_->OEMCrypto_GetOEMPublicCertificate( + session, public_cert, public_cert_length); + }); +} + +OEMCryptoResult CryptoInterface::OEMCrypto_LoadDRMPrivateKey( + OEMCrypto_SESSION session, OEMCrypto_PrivateKeyType key_type, + const uint8_t* wrapped_rsa_key, size_t wrapped_rsa_key_length) { + return lock_->WithOecSessionLock("LoadDRMPrivateKey", [&] { + return oemcrypto_interface_->OEMCrypto_LoadDRMPrivateKey( + session, key_type, wrapped_rsa_key, wrapped_rsa_key_length); + }); +} + +OEMCryptoResult CryptoInterface::OEMCrypto_GenerateRSASignature( + OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, + uint8_t* signature, size_t* signature_length, + RSA_Padding_Scheme padding_scheme) { + return lock_->WithOecSessionLock("GenerateRsaSignature", [&] { + return oemcrypto_interface_->OEMCrypto_GenerateRSASignature( + session, message, message_length, signature, signature_length, + padding_scheme); + }); +} + +OEMCryptoResult CryptoInterface::OEMCrypto_DeriveKeysFromSessionKey( + OEMCrypto_SESSION session, const uint8_t* enc_session_key, + size_t enc_session_key_length, const uint8_t* mac_key_context, + size_t mac_key_context_length, const uint8_t* enc_key_context, + size_t enc_key_context_length) { + return lock_->WithOecSessionLock("DeriveKeysFromSessionKey", [&] { + return oemcrypto_interface_->OEMCrypto_DeriveKeysFromSessionKey( + session, enc_session_key, enc_session_key_length, mac_key_context, + mac_key_context_length, enc_key_context, enc_key_context_length); + }); +} + +OEMCryptoResult CryptoInterface::OEMCrypto_LoadLicense( + OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, + size_t core_message_length, const uint8_t* signature, + size_t signature_length) { + return lock_->WithOecSessionLock("LoadLicense", [&] { + return oemcrypto_interface_->OEMCrypto_LoadLicense( + session, message, message_length, core_message_length, signature, + signature_length); + }); +} + +OEMCryptoResult CryptoInterface::OEMCrypto_LoadRenewal( + OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, + size_t core_message_length, const uint8_t* signature, + size_t signature_length) { + return lock_->WithOecSessionLock("LoadRenewal", [&] { + return oemcrypto_interface_->OEMCrypto_LoadRenewal( + session, message, message_length, core_message_length, signature, + signature_length); + }); +} + +OEMCryptoResult CryptoInterface::OEMCrypto_LoadCasECMKeys( + OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, + const OEMCrypto_EntitledContentKeyObject* even_key, + const OEMCrypto_EntitledContentKeyObject* odd_key) { + return lock_->WithOecSessionLock("LoadCasECMKeys", [&] { + return oemcrypto_interface_->OEMCrypto_LoadCasECMKeys( + session, message, message_length, even_key, odd_key); + }); +} + +OEMCryptoResult CryptoInterface::OEMCrypto_GetHDCPCapability( + OEMCrypto_HDCP_Capability* current, OEMCrypto_HDCP_Capability* max) { + return lock_->WithOecReadLock("GetHdcpCapabilities", [&] { + return oemcrypto_interface_->OEMCrypto_GetHDCPCapability(current, max); + }); +} + +OEMCryptoResult CryptoInterface::OEMCrypto_GetDeviceID(uint8_t* deviceID, + size_t* idLength) { + return lock_->WithOecReadLock("GetDeviceID", [&] { + return oemcrypto_interface_->OEMCrypto_GetDeviceID(deviceID, idLength); + }); +} + +const char* CryptoInterface::OEMCrypto_SecurityLevel() { + return lock_->WithOecReadLock("SecurityLevel", [&] { + return oemcrypto_interface_->OEMCrypto_SecurityLevel(); + }); +} + +OEMCryptoResult CryptoInterface::OEMCrypto_CreateEntitledKeySession( + OEMCrypto_SESSION oec_session, OEMCrypto_SESSION* key_session) { + return lock_->WithOecSessionLock("CreateEntitledKeySession", [&] { + return oemcrypto_interface_->OEMCrypto_CreateEntitledKeySession( + oec_session, key_session); + }); +} + +OEMCryptoResult CryptoInterface::OEMCrypto_RemoveEntitledKeySession( + OEMCrypto_SESSION key_session) { + return lock_->WithOecSessionLock("RemoveEntitledKeySession", [&] { + return oemcrypto_interface_->OEMCrypto_RemoveEntitledKeySession( + key_session); + }); +} + +OEMCryptoResult CryptoInterface::OEMCrypto_ReassociateEntitledKeySession( + OEMCrypto_SESSION key_sid, OEMCrypto_SESSION oec_sid) { + return lock_->WithOecSessionLock("ReassociateEntitledKeySession", [&] { + return oemcrypto_interface_->OEMCrypto_ReassociateEntitledKeySession( + key_sid, oec_sid); + }); +} + +uint32_t CryptoInterface::OEMCrypto_APIVersion() { + return lock_->WithOecReadLock("APIVersion", [&] { + return oemcrypto_interface_->OEMCrypto_APIVersion(); + }); +} + +OEMCryptoResult CryptoInterface::OEMCrypto_GetOEMKeyToken( + OEMCrypto_SESSION key_session, uint8_t* key_token, + size_t* key_token_length) { + return lock_->WithOecSessionLock("GetOEMKeyToken", [&] { + return oemcrypto_interface_->OEMCrypto_GetOEMKeyToken( + key_session, key_token, key_token_length); + }); +} + +OEMCryptoResult CryptoInterface::OEMCrypto_GetSignatureHashAlgorithm( + OEMCrypto_SESSION session, OEMCrypto_SignatureHashAlgorithm* algorithm) { + return lock_->WithOecSessionLock("GetSignatureHashAlgorithm", [&] { + return oemcrypto_interface_->OEMCrypto_GetSignatureHashAlgorithm(session, + algorithm); + }); +} + +OEMCryptoResult CryptoInterface::create_internal( + OEMCryptoInterface* oemcrypto_interface, + std::unique_ptr* init) { + static OEMCryptoInterface default_oemcrypto_interface; + // The oemcrypto interface currently used. It may point to different + // oemcrypto_interface implementations in normal running v.s. test running. + static OEMCryptoInterface* oemcrypto_interface_in_use; + + if (init == nullptr) { + return OEMCrypto_ERROR_INIT_FAILED; + } + + std::unique_ptr new_oemcrypto_interface; + + OEMCryptoResult status = OEMCrypto_SUCCESS; + lock_->WithStaticFieldWriteLock("Initialize", [&] { + if (!oemcrypto_interface) + oemcrypto_interface = &default_oemcrypto_interface; + if (session_count_ == 0) { + lock_->WithOecWriteLock("Initialize", [&] { + status = oemcrypto_interface->OEMCrypto_Initialize(); + }); + if (status != OEMCrypto_SUCCESS) { + return; + } + oemcrypto_interface_in_use = oemcrypto_interface; + } + // Using 'new' to access a non-public constructor. + new_oemcrypto_interface = + std::unique_ptr(new CryptoInterface()); + new_oemcrypto_interface->oemcrypto_interface_ = oemcrypto_interface_in_use; + ++session_count_; + }); + if (status != OEMCrypto_SUCCESS) { + return status; + } + + *init = std::move(new_oemcrypto_interface); + return OEMCrypto_SUCCESS; +} + +CryptoInterface::CryptoInterface() : oemcrypto_interface_(nullptr) {} +CryptoInterface::~CryptoInterface() { + lock_->WithStaticFieldWriteLock("Terminate", [&] { + if (session_count_ > 0) { + if (--session_count_ == 0) { + lock_->WithOecWriteLock( + "Terminate", [&] { oemcrypto_interface_->OEMCrypto_Terminate(); }); + } + } + }); +} + +/************************************************************************/ + +CryptoSession::CryptoSession() {} +CryptoSession::~CryptoSession() { close(); } + +CasStatus CryptoSession::initialize() { + OEMCryptoResult result; + result = getCryptoInterface(&crypto_interface_); + if (result != OEMCrypto_SUCCESS) { + return CasStatus(CasStatusCode::kCryptoSessionError, + "unable to ceate interface"); + } + // Open a crypto session here. + result = crypto_interface_->OEMCrypto_OpenSession(&session_); + if (result != OEMCrypto_SUCCESS) { + std::ostringstream err_string; + err_string << "OEMCrypto_OpenSession returned " << result; + return CasStatus(CasStatusCode::kCryptoSessionError, err_string.str()); + } + return CasStatus::OkStatus(); +} + +CasStatus CryptoSession::reset() { + CasStatus status = close(); + if (status.status_code() != CasStatusCode::kNoError) { + return status; + } + return initialize(); +} + +CasStatus CryptoSession::close() { + if (!crypto_interface_) { + return CasStatus(CasStatusCode::kCryptoSessionError, + "missing crypto interface"); + } + OEMCryptoResult result = crypto_interface_->OEMCrypto_CloseSession(session_); + if (result != OEMCrypto_SUCCESS) { + std::ostringstream err_string; + err_string << "OEMCrypto_CloseSession returned " << result; + return CasStatus(CasStatusCode::kCryptoSessionError, err_string.str()); + } + return CasStatus::OkStatus(); +} + +CasProvisioningMethod CryptoSession::provisioning_method() { + if (!crypto_interface_) { + return ProvisioningError; + } + switch (crypto_interface_->OEMCrypto_GetProvisioningMethod()) { + case OEMCrypto_Keybox: + return Keybox; + case OEMCrypto_OEMCertificate: + return OEMCertificate; + case OEMCrypto_DrmCertificate: + return DrmCertificate; + default: + return ProvisioningError; + } +} + +CasStatus CryptoSession::GetKeyData(uint8_t* keyData, size_t* keyDataLength) { + if (!crypto_interface_) { + return CasStatus(CasStatusCode::kCryptoSessionError, + "missing crypto interface"); + } + OEMCryptoResult result = + crypto_interface_->OEMCrypto_GetKeyData(keyData, keyDataLength); + if (result != OEMCrypto_SUCCESS) { + std::ostringstream err_string; + err_string << "OEMCrypto_GetKeyData returned " << result; + return CasStatus(CasStatusCode::kCryptoSessionError, err_string.str()); + } + return CasStatus::OkStatus(); +} + +SupportedCertificates CryptoSession::supported_certificates() { + if (!crypto_interface_) { + return SupportedCertificates(0); + } + return SupportedCertificates( + crypto_interface_->OEMCrypto_SupportedCertificates()); +} + +CasStatus CryptoSession::GenerateNonce(uint32_t* nonce) { + if (!crypto_interface_) { + return CasStatus(CasStatusCode::kCryptoSessionError, + "missing crypto interface"); + } + OEMCryptoResult result = + crypto_interface_->OEMCrypto_GenerateNonce(session_, nonce); + if (result != OEMCrypto_SUCCESS) { + std::ostringstream err_string; + err_string << "OEMCrypto_GenerateNonce returned " << result; + return CasStatus(CasStatusCode::kCryptoSessionError, err_string.str()); + } + return CasStatus::OkStatus(); +} + +CasStatus CryptoSession::GenerateDerivedKeys(const uint8_t* mac_key_context, + uint32_t mac_key_context_length, + const uint8_t* enc_key_context, + uint32_t enc_key_context_length) { + if (!crypto_interface_) { + return CasStatus(CasStatusCode::kCryptoSessionError, + "missing crypto interface"); + } + OEMCryptoResult result = crypto_interface_->OEMCrypto_GenerateDerivedKeys( + session_, mac_key_context, mac_key_context_length, enc_key_context, + enc_key_context_length); + if (result != OEMCrypto_SUCCESS) { + std::ostringstream err_string; + err_string << "OEMCrypto_GenerateDerivedKeys returned " << result; + return CasStatus(CasStatusCode::kCryptoSessionError, err_string.str()); + } + return CasStatus::OkStatus(); +} + +CasStatus CryptoSession::PrepareAndSignLicenseRequest( + const std::string& message, std::string* core_message, + std::string* signature, bool& should_specify_algorithm, + OEMCrypto_SignatureHashAlgorithm& algorithm) { + if (!crypto_interface_) { + return CasStatus(CasStatusCode::kCryptoSessionError, + "missing crypto interface"); + } + if (core_message == nullptr || signature == nullptr) { + LOGE("Missing core_message or signature."); + return CasStatus(CasStatusCode::kInvalidParameter, + "Missing core_message or signature."); + } + + OEMCryptoResult sts; + sts = crypto_interface_->OEMCrypto_GetSignatureHashAlgorithm(session_, + &algorithm); + if (sts == OEMCrypto_SUCCESS) { + should_specify_algorithm = true; + } else if (sts == OEMCrypto_ERROR_NOT_IMPLEMENTED) { + should_specify_algorithm = false; + } else { + std::ostringstream err_string; + err_string << "OEMCrypto_GetSignatureHashAlgorithm returned " << sts; + return CasStatus(CasStatusCode::kCryptoSessionError, err_string.str()); + } + + size_t signature_length = 0; + size_t core_message_length = 0; + *core_message = ""; + std::string combined_message = *core_message + message; + // First call is intended to determine the required size of the + // output buffers. + sts = crypto_interface_->OEMCrypto_PrepAndSignLicenseRequest( + session_, + reinterpret_cast(const_cast(combined_message.data())), + combined_message.size(), &core_message_length, nullptr, + &signature_length); + if (OEMCrypto_ERROR_SHORT_BUFFER != sts) { + std::ostringstream err_string; + err_string << "OEMCrypto_PrepareAndSignLicenseRequest returned " << sts; + return CasStatus(CasStatusCode::kCryptoSessionError, err_string.str()); + } + + // Resize. + core_message->resize(core_message_length); + signature->resize(signature_length); + + combined_message = *core_message + message; + sts = crypto_interface_->OEMCrypto_PrepAndSignLicenseRequest( + session_, + reinterpret_cast(const_cast(combined_message.data())), + combined_message.size(), &core_message_length, + reinterpret_cast(const_cast(signature->data())), + &signature_length); + if (sts != OEMCrypto_SUCCESS) { + std::ostringstream err_string; + err_string << "OEMCrypto_PrepareAndSignLicenseRequest returned " << sts; + return CasStatus(CasStatusCode::kCryptoSessionError, err_string.str()); + } + + signature->resize(signature_length); + *core_message = std::move(combined_message); + // Truncate combined message to only contain the core message. + core_message->resize(core_message_length); + return CasStatus::OkStatus(); +} + +CasStatus CryptoSession::PrepareAndSignRenewalRequest( + const std::string& message, std::string* core_message, + std::string* signature) { + if (!crypto_interface_) { + return CasStatus(CasStatusCode::kCryptoSessionError, + "missing crypto interface"); + } + if (core_message == nullptr || signature == nullptr) { + LOGE("Missing core_message or signature."); + return CasStatus(CasStatusCode::kInvalidParameter, + "Missing core_message or signature."); + } + + OEMCryptoResult sts; + size_t signature_length = 0; + size_t core_message_length = 0; + *core_message = ""; + std::string combined_message = *core_message + message; + // First call is intended to determine the required size of the + // output buffers. + sts = crypto_interface_->OEMCrypto_PrepAndSignRenewalRequest( + session_, + reinterpret_cast(const_cast(combined_message.data())), + combined_message.size(), &core_message_length, nullptr, + &signature_length); + if (OEMCrypto_ERROR_SHORT_BUFFER != sts) { + std::ostringstream err_string; + err_string << "OEMCrypto_PrepAndSignRenewalRequest returned " << sts; + return CasStatus(CasStatusCode::kCryptoSessionError, err_string.str()); + } + + // Resize. + core_message->resize(core_message_length); + signature->resize(signature_length); + + combined_message = *core_message + message; + sts = crypto_interface_->OEMCrypto_PrepAndSignRenewalRequest( + session_, + reinterpret_cast(const_cast(combined_message.data())), + combined_message.size(), &core_message_length, + reinterpret_cast(const_cast(signature->data())), + &signature_length); + if (sts != OEMCrypto_SUCCESS) { + std::ostringstream err_string; + err_string << "OEMCrypto_PrepAndSignRenewalRequest returned " << sts; + return CasStatus(CasStatusCode::kCryptoSessionError, err_string.str()); + } + + signature->resize(signature_length); + *core_message = std::move(combined_message); + // Truncate combined message to only contain the core message. + core_message->resize(core_message_length); + return CasStatus::OkStatus(); +} + +CasStatus CryptoSession::PrepareAndSignProvisioningRequest( + const std::string& message, std::string* core_message, + std::string* signature, bool& should_specify_algorithm, + OEMCrypto_SignatureHashAlgorithm& algorithm) { + if (!crypto_interface_) { + return CasStatus(CasStatusCode::kCryptoSessionError, + "missing crypto interface"); + } + if (core_message == nullptr || signature == nullptr) { + LOGE("Missing core_message or signature."); + return CasStatus(CasStatusCode::kInvalidParameter, + "Missing core_message or signature."); + } + + OEMCryptoResult sts; + // Keybox based provisioning does not need hash algorithm. + should_specify_algorithm = false; + size_t signature_length = 0; + size_t core_message_length = 0; + *core_message = ""; + std::string combined_message = *core_message + message; + // First call is intended to determine the required size of the + // output buffers. + sts = crypto_interface_->OEMCrypto_PrepAndSignProvisioningRequest( + session_, + reinterpret_cast(const_cast(combined_message.data())), + combined_message.size(), &core_message_length, nullptr, + &signature_length); + if (OEMCrypto_ERROR_SHORT_BUFFER != sts) { + std::ostringstream err_string; + err_string << "OEMCrypto_PrepAndSignProvisioningRequest returned " << sts; + return CasStatus(CasStatusCode::kCryptoSessionError, err_string.str()); + } + + // Resize. + core_message->resize(core_message_length); + signature->resize(signature_length); + + combined_message = *core_message + message; + sts = crypto_interface_->OEMCrypto_PrepAndSignProvisioningRequest( + session_, + reinterpret_cast(const_cast(combined_message.data())), + combined_message.size(), &core_message_length, + reinterpret_cast(const_cast(signature->data())), + &signature_length); + if (sts != OEMCrypto_SUCCESS) { + std::ostringstream err_string; + err_string << "OEMCrypto_PrepAndSignProvisioningRequest returned " << sts; + return CasStatus(CasStatusCode::kCryptoSessionError, err_string.str()); + } + + signature->resize(signature_length); + *core_message = std::move(combined_message); + // Truncate combined message to only contain the core message. + core_message->resize(core_message_length); + return CasStatus::OkStatus(); +} + +CasStatus CryptoSession::LoadProvisioning(const std::string& signed_message, + const std::string& core_message, + const std::string& signature, + std::string* wrapped_private_key) { + LOGV("Loading provisioning certificate: id = %u", session_); + if (!crypto_interface_) { + return CasStatus(CasStatusCode::kCryptoSessionError, + "missing crypto interface"); + } + if (wrapped_private_key == nullptr) { + LOGE("Missing wrapped |wrapped_private_key|."); + return CasStatus(CasStatusCode::kInvalidParameter, + "Missing wrapped |wrapped_private_key|."); + } + + const std::string combined_message = core_message + signed_message; + // Round 1, get the size of the wrapped private key buffer. + size_t wrapped_private_key_length = 0; + OEMCryptoResult result = crypto_interface_->OEMCrypto_LoadProvisioning( + session_, reinterpret_cast(combined_message.data()), + combined_message.size(), core_message.size(), + reinterpret_cast(signature.data()), signature.size(), + nullptr, &wrapped_private_key_length); + if (result != OEMCrypto_ERROR_SHORT_BUFFER) { + std::ostringstream err_string; + err_string << "OEMCrypto_LoadProvisioning returned " << result; + return CasStatus(CasStatusCode::kIndividualizationError, err_string.str()); + } + + wrapped_private_key->resize(wrapped_private_key_length); + result = crypto_interface_->OEMCrypto_LoadProvisioning( + session_, reinterpret_cast(combined_message.data()), + combined_message.size(), core_message.size(), + reinterpret_cast(signature.data()), signature.size(), + reinterpret_cast(&wrapped_private_key->front()), + &wrapped_private_key_length); + if (result != OEMCrypto_SUCCESS) { + std::ostringstream err_string; + err_string << "OEMCrypto_LoadProvisioning returned " << result; + return CasStatus(CasStatusCode::kIndividualizationError, err_string.str()); + } + return CasStatus::OkStatus(); +} + +CasStatus CryptoSession::GetOEMPublicCertificate(uint8_t* public_cert, + size_t* public_cert_length) { + if (!crypto_interface_) { + return CasStatus(CasStatusCode::kCryptoSessionError, + "missing crypto interface"); + } + if (!public_cert || !public_cert_length) { + return CasStatus(CasStatusCode::kCryptoSessionError, "invalid argument"); + } + OEMCryptoResult result = crypto_interface_->OEMCrypto_GetOEMPublicCertificate( + session_, public_cert, public_cert_length); + if (result != OEMCrypto_SUCCESS) { + std::ostringstream err_string; + err_string << "OEMCrypto_GetOEMPublicCertificate returned " << result; + return CasStatus(CasStatusCode::kCryptoSessionError, err_string.str()); + } + return CasStatus::OkStatus(); +} + +CasStatus CryptoSession::LoadDeviceRSAKey(const uint8_t* wrapped_rsa_key, + size_t wrapped_rsa_key_length) { + if (!crypto_interface_) { + return CasStatus(CasStatusCode::kCryptoSessionError, + "missing crypto interface"); + } + if (!wrapped_rsa_key) { + return CasStatus(CasStatusCode::kCryptoSessionError, "invalid argument"); + } + OEMCryptoResult result = crypto_interface_->OEMCrypto_LoadDRMPrivateKey( + session_, OEMCrypto_RSA_Private_Key, wrapped_rsa_key, + wrapped_rsa_key_length); + if (result != OEMCrypto_SUCCESS) { + std::ostringstream err_string; + err_string << "OEMCrypto_LoadDRMPrivateKey returned " << result; + return CasStatus(CasStatusCode::kCryptoSessionError, err_string.str()); + } + return CasStatus::OkStatus(); +} + +CasStatus CryptoSession::GenerateRSASignature( + const uint8_t* message, size_t message_length, uint8_t* signature, + size_t* signature_length, RSA_Padding_Scheme padding_scheme) { + if (!crypto_interface_) { + return CasStatus(CasStatusCode::kCryptoSessionError, + "missing crypto interface"); + } + if (!message || !signature || signature_length == 0) { + return CasStatus(CasStatusCode::kCryptoSessionError, "invalid argument"); + } + OEMCryptoResult result = crypto_interface_->OEMCrypto_GenerateRSASignature( + session_, message, message_length, signature, signature_length, + padding_scheme); + if (result != OEMCrypto_SUCCESS) { + std::ostringstream err_string; + err_string << "OEMCrypto_GenerateRSASignature returned " << result; + return CasStatus(CasStatusCode::kCryptoSessionError, err_string.str()); + } + return CasStatus::OkStatus(); +} + +CasStatus CryptoSession::DeriveKeysFromSessionKey( + const uint8_t* enc_session_key, size_t enc_session_key_length, + const uint8_t* mac_key_context, size_t mac_key_context_length, + const uint8_t* enc_key_context, size_t enc_key_context_length) { + if (!crypto_interface_) { + return CasStatus(CasStatusCode::kCryptoSessionError, + "missing crypto interface"); + } + if (!enc_session_key || !mac_key_context || !enc_key_context) { + return CasStatus(CasStatusCode::kCryptoSessionError, "invalid argument"); + } + OEMCryptoResult result = + crypto_interface_->OEMCrypto_DeriveKeysFromSessionKey( + session_, enc_session_key, enc_session_key_length, mac_key_context, + mac_key_context_length, enc_key_context, enc_key_context_length); + if (result != OEMCrypto_SUCCESS) { + std::ostringstream err_string; + err_string << "OEMCrypto_DeriveKeysFromSessionKey returned " << result; + return CasStatus(CasStatusCode::kCryptoSessionError, err_string.str()); + } + return CasStatus::OkStatus(); +} + +CasStatus CryptoSession::LoadLicense(const std::string& signed_message, + const std::string& core_message, + const std::string& signature) { + LOGV("Loading license: id = %u", session_); + if (!crypto_interface_) { + return CasStatus(CasStatusCode::kCryptoSessionError, + "missing crypto interface"); + } + + const std::string combined_message = core_message + signed_message; + OEMCryptoResult result = crypto_interface_->OEMCrypto_LoadLicense( + session_, reinterpret_cast(combined_message.data()), + combined_message.size(), core_message.size(), + reinterpret_cast(signature.data()), signature.size()); + if (result != OEMCrypto_SUCCESS) { + std::ostringstream err_string; + err_string << "OEMCrypto_LoadLicense returned " << result; + return CasStatus(CasStatusCode::kCryptoSessionError, err_string.str()); + } + return CasStatus::OkStatus(); +} + +CasStatus CryptoSession::LoadRenewal(const std::string& signed_message, + const std::string& core_message, + const std::string& signature) { + LOGV("Loading license renewal: id = %u", session_); + if (!crypto_interface_) { + return CasStatus(CasStatusCode::kCryptoSessionError, + "missing crypto interface"); + } + + const std::string combined_message = core_message + signed_message; + OEMCryptoResult result = crypto_interface_->OEMCrypto_LoadRenewal( + session_, reinterpret_cast(combined_message.data()), + combined_message.size(), core_message.size(), + reinterpret_cast(signature.data()), signature.size()); + if (result != OEMCrypto_SUCCESS) { + std::ostringstream err_string; + err_string << "OEMCrypto_LoadRenewal returned " << result; + return CasStatus(CasStatusCode::kCryptoSessionError, err_string.str()); + } + return CasStatus::OkStatus(); +} + +CasStatus CryptoSession::LoadCasECMKeys(OEMCrypto_SESSION session, + const KeySlot* even_key, + const KeySlot* odd_key) { + if (!crypto_interface_) { + return CasStatus(CasStatusCode::kCryptoSessionError, + "missing crypto interface"); + } + if (nullptr == even_key && nullptr == odd_key) { + return CasStatus::OkStatus(); + } + OEMCrypto_EntitledContentKeyObject even_ecko; + OEMCrypto_EntitledContentKeyObject odd_ecko; + std::string message; + if (nullptr != even_key) { + FillEntitledContentKeyObjectFromKeyData(message, *even_key, &even_ecko); + } + if (nullptr != odd_key) { + FillEntitledContentKeyObjectFromKeyData(message, *odd_key, &odd_ecko); + } + OEMCryptoResult result = crypto_interface_->OEMCrypto_LoadCasECMKeys( + session, reinterpret_cast(message.data()), message.size(), + (nullptr == even_key ? nullptr : &even_ecko), + (nullptr == odd_key ? nullptr : &odd_ecko)); + if (result != OEMCrypto_SUCCESS) { + std::ostringstream err_string; + err_string << "OEMCrypto_LoadCasECMKeys returned " << result; + return CasStatus(CasStatusCode::kCryptoSessionError, err_string.str()); + } + return CasStatus::OkStatus(); +} + +bool CryptoSession::GetHdcpCapabilities(HdcpCapability* current, + HdcpCapability* max) { + if (!crypto_interface_) { + return false; + } + if (current == nullptr || max == nullptr) { + LOGE( + "CryptoSession::GetHdcpCapabilities: |current|, |max| cannot be " + "NULL"); + return false; + } + OEMCryptoResult status = + crypto_interface_->OEMCrypto_GetHDCPCapability(current, max); + + if (OEMCrypto_SUCCESS != status) { + LOGW("OEMCrypto_GetHDCPCapability fails with %d", status); + return false; + } + return true; +} + +OEMCryptoResult CryptoSession::getCryptoInterface( + std::unique_ptr* interface) { + return CryptoInterface::create(interface); +} + +CasStatus CryptoSession::GetDeviceID(std::string* buffer) { + if (!crypto_interface_) { + return CasStatus(CasStatusCode::kCryptoSessionError, + "missing crypto interface"); + } + if (buffer == nullptr) { + return CasStatus(CasStatusCode::kCryptoSessionError, + "missing buffer for device id"); + } + std::string intermediate; + intermediate.resize(32); + size_t buf_size = 32; + OEMCryptoResult result = crypto_interface_->OEMCrypto_GetDeviceID( + reinterpret_cast(&intermediate[0]), &buf_size); + // If the error is for a short buffer, resize the destination and retry + // reading the id. This should never happen. + if (result == OEMCrypto_ERROR_SHORT_BUFFER) { + intermediate.resize(buf_size); + result = crypto_interface_->OEMCrypto_GetDeviceID( + reinterpret_cast(&intermediate[0]), &buf_size); + } + if (result != OEMCrypto_SUCCESS) { + std::ostringstream err_string; + err_string << "OEMCrypto_GetDeviceID returned " << result; + return CasStatus(CasStatusCode::kCryptoSessionError, err_string.str()); + } + buffer->resize(SHA256_DIGEST_LENGTH); + SHA256(reinterpret_cast(intermediate.data()), + intermediate.size(), reinterpret_cast(&(*buffer)[0])); + return CasStatus::OkStatus(); +} + +const char* CryptoSession::SecurityLevel() { + if (!crypto_interface_) { + return nullptr; + } + return crypto_interface_->OEMCrypto_SecurityLevel(); +} + +CasStatus CryptoSession::CreateEntitledKeySession( + OEMCrypto_SESSION* entitled_key_session_id) { + if (!crypto_interface_) { + return CasStatus(CasStatusCode::kCryptoSessionError, + "missing crypto interface"); + } + if (entitled_key_session_id == nullptr) { + return CasStatus(CasStatusCode::kCryptoSessionError, + "entitled_key_session_id is nullptr"); + } + + OEMCryptoResult result = + crypto_interface_->OEMCrypto_CreateEntitledKeySession( + session_, entitled_key_session_id); + if (result != OEMCrypto_SUCCESS) { + std::ostringstream err_string; + err_string << "OEMCrypto_CreateEntitledKeySession returned " << result; + return CasStatus(CasStatusCode::kCryptoSessionError, err_string.str()); + } + return CasStatus::OkStatus(); +} + +CasStatus CryptoSession::RemoveEntitledKeySession( + OEMCrypto_SESSION entitled_key_session_id) { + if (!crypto_interface_) { + return CasStatus(CasStatusCode::kCryptoSessionError, + "missing crypto interface"); + } + + OEMCryptoResult result = + crypto_interface_->OEMCrypto_RemoveEntitledKeySession( + entitled_key_session_id); + if (result != OEMCrypto_SUCCESS) { + std::ostringstream err_string; + err_string << "OEMCrypto_RemoveEntitledKeySession returned " << result; + return CasStatus(CasStatusCode::kCryptoSessionError, err_string.str()); + } + return CasStatus::OkStatus(); +} + +CasStatus CryptoSession::ReassociateEntitledKeySession( + OEMCrypto_SESSION entitled_key_session_id) { + if (!crypto_interface_) { + return CasStatus(CasStatusCode::kCryptoSessionError, + "missing crypto interface"); + } + + OEMCryptoResult result = + crypto_interface_->OEMCrypto_ReassociateEntitledKeySession( + entitled_key_session_id, session_); + if (result != OEMCrypto_SUCCESS) { + std::ostringstream err_string; + err_string << "OEMCrypto_ReassociateEntitledKeySession returned " << result; + return CasStatus(CasStatusCode::kCryptoSessionError, err_string.str()); + } + return CasStatus::OkStatus(); +} + +CasStatus CryptoSession::APIVersion(uint32_t* api_version) { + if (!crypto_interface_) { + return CasStatus(CasStatusCode::kCryptoSessionError, + "missing crypto interface"); + } + uint32_t oemcrypto_api_version = crypto_interface_->OEMCrypto_APIVersion(); + + if (oemcrypto_api_version != kExpectedOEMCryptoVersion) { + std::ostringstream err_string; + err_string << "OEMCrypto_APIVersion returned: " << oemcrypto_api_version + << ". While the correct API version should be: " + << kExpectedOEMCryptoVersion; + return CasStatus(CasStatusCode::kOEMCryptoVersionMismatch, + err_string.str()); + } + *api_version = oemcrypto_api_version; + return CasStatus::OkStatus(); +} + +CasStatus CryptoSession::GetOEMKeyToken( + OEMCrypto_SESSION entitled_key_session_id, std::vector& token) { + if (!crypto_interface_) { + return CasStatus(CasStatusCode::kCryptoSessionError, + "missing crypto interface"); + } + token.resize(0); + size_t token_size = 0; + OEMCryptoResult result = crypto_interface_->OEMCrypto_GetOEMKeyToken( + entitled_key_session_id, nullptr, &token_size); + if (result == OEMCrypto_ERROR_NOT_IMPLEMENTED) { + // entitled_key_session_id is the token. Little endian order is used here. + token.reserve(sizeof(OEMCrypto_SESSION)); + for (int i = 0; i < static_cast(sizeof(OEMCrypto_SESSION)); ++i) { + token.push_back((entitled_key_session_id >> (8 * i)) & 0xff); + } + return CasStatus::OkStatus(); + } + if (result == OEMCrypto_ERROR_SHORT_BUFFER) { + token.resize(token_size); + result = crypto_interface_->OEMCrypto_GetOEMKeyToken( + entitled_key_session_id, token.data(), &token_size); + } + if (result != OEMCrypto_SUCCESS) { + std::ostringstream err_string; + err_string << "OEMCrypto_GetOEMKeyToken returned " << result; + return CasStatus(CasStatusCode::kCryptoSessionError, err_string.str()); + } + return CasStatus::OkStatus(); +} + +} // namespace wvcas diff --git a/plugin/src/ecm_parser.cpp b/plugin/src/ecm_parser.cpp new file mode 100644 index 0000000..b3e43b2 --- /dev/null +++ b/plugin/src/ecm_parser.cpp @@ -0,0 +1,84 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#include "ecm_parser.h" + +#include "ecm_parser_v2.h" +#include "ecm_parser_v3.h" +#include "log.h" + +namespace wvcas { + +namespace { +// 2 bytes cas id, 1 byte version. +constexpr int kEcmHeaderSize = 3; +constexpr int kCasIdIndex = 0; +constexpr int kVersionIndex = 2; + +// Legacy Widevine CAS ID +constexpr uint16_t kWidevineCasId = 0x4AD4; +// New Widevine CAS IDs 0x56C0 to 0x56C9 (all inclusive). +constexpr uint16_t kWidevineNewCasIdLowerBound = 0x56C0; +constexpr uint16_t kWidevineNewCasIdUpperBound = 0x56C9; + +// Two values of the table_id field (0x80 and 0x81) are reserved for +// transmission of ECM data. A change of these two table_id values signals +// that a change of ECM contents has occurred. +constexpr uint8_t kSectionHeader1 = 0x80; +constexpr uint8_t kSectionHeader2 = 0x81; +constexpr size_t kSectionHeaderSize = 3; +constexpr size_t kSectionHeaderWithPointerSize = 4; +constexpr uint8_t kPointerFieldZero = 0x00; + +// Returns the possible starting index of ECM. It assumes the pointer field will +// always set to 0, if present. +int find_ecm_start_index(const CasEcm& cas_ecm) { + if (cas_ecm.empty()) { + return 0; + } + // Case 1: Pointer field (always set to 0); section header; ECM. + if (cas_ecm[0] == kPointerFieldZero) { + return kSectionHeaderWithPointerSize; + } + // Case 2: Section header (3 bytes), ECM. + if (cas_ecm[0] == kSectionHeader1 || cas_ecm[0] == kSectionHeader2) { + return kSectionHeaderSize; + } + // Case 3: ECM. + return 0; +} + +} // namespace + +std::unique_ptr EcmParser::Create(const CasEcm& cas_ecm) { + // Detect and strip optional section header. + const int offset = find_ecm_start_index(cas_ecm); + if (offset < 0 || + (offset + kEcmHeaderSize > static_cast(cas_ecm.size()))) { + LOGE("Unable to find start of ECM"); + return nullptr; + } + const CasEcm ecm(cas_ecm.begin() + offset, cas_ecm.end()); + // Confirm ecm data starts with valid Widevine CAS ID. + uint16_t cas_id_val = (ecm[kCasIdIndex] << 8) | ecm[kCasIdIndex + 1]; + if (cas_id_val != kWidevineCasId && + (cas_id_val < kWidevineNewCasIdLowerBound || + cas_id_val > kWidevineNewCasIdUpperBound)) { + LOGE("Supported Widevine CAS IDs not found at the start of ECM. Found: %u", + cas_id_val); + return nullptr; + } + + if (ecm[kVersionIndex] <= 2) { + std::unique_ptr parser; + if (!EcmParserV2::create(ecm, &parser)) { + return nullptr; + } + return parser; + } else { + return EcmParserV3::Create(ecm); + } +} + +} // namespace wvcas diff --git a/plugin/src/ecm_parser_v2.cpp b/plugin/src/ecm_parser_v2.cpp new file mode 100644 index 0000000..c351665 --- /dev/null +++ b/plugin/src/ecm_parser_v2.cpp @@ -0,0 +1,181 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#include "ecm_parser_v2.h" + +#include + +namespace wvcas { + +namespace { +// ECM constants +constexpr uint8_t kCryptoModeFlags = (0x3 << 1); // Mode bits 1..2 +constexpr uint8_t kCryptoModeFlagsV2 = (0xF << 1); // Mode bits 1..4 +constexpr uint8_t kRotationFlag = (0x1 << 0); // Mode bit 0 +constexpr uint8_t kContentIVSizeFlag = (0x1 << 6); +constexpr uint8_t kAgeRestrictionMask = (0x1F << 1); + +constexpr size_t kEntitlementKeyIDSizeBytes = 16; +constexpr size_t kContentKeyIDSizeBytes = 16; +constexpr size_t kContentKeyDataSizeBytes = 16; +constexpr size_t kWrappedKeyIVSizeBytes = 16; +// Size is either 8 or 16 bytes, depending on ContentIVSize flag. +constexpr size_t kContentKeyMaxIVSizeBytes = 16; + +constexpr size_t kMaxTsPayloadSizeBytes = 184; +} // namespace + +#pragma pack(push, 1) // No padding in ecm struct definition. +struct EcmKeyData { + const uint8_t entitlement_key_id[kEntitlementKeyIDSizeBytes]; + const uint8_t content_key_id[kContentKeyIDSizeBytes]; + const uint8_t control_word[kContentKeyDataSizeBytes]; + const uint8_t control_word_iv[kWrappedKeyIVSizeBytes]; + // Actual size can be either 8 or 16 bytes. + const uint8_t content_iv[kContentKeyMaxIVSizeBytes]; +}; + +struct EcmDescriptor { + const uint16_t ca_id; + const uint8_t version; + const uint8_t flags_cipher_rotation; + const uint8_t flags_iv_age; +}; + +static_assert((sizeof(EcmDescriptor) + 2 * sizeof(EcmKeyData)) <= + kMaxTsPayloadSizeBytes, + "Maximum possible ecm size is larger than a ts payload"); +#pragma pack(pop) // Revert padding value to previous. + +EcmParserV2::EcmParserV2(const CasEcm& ecm) : ecm_(ecm) {} + +size_t EcmParserV2::key_data_size() const { + return sizeof(EcmKeyData) + content_iv_size() - kContentKeyMaxIVSizeBytes; +} + +bool EcmParserV2::is_valid_size() const { + size_t expected_size = + sizeof(EcmDescriptor) + key_data_size() * (rotation_enabled() ? 2 : 1); + // Parser always receives entire ts payload of 184 bytes. + return ecm_.size() >= expected_size; +} + +const EcmKeyData* EcmParserV2::key_slot_data(KeySlotId id) const { + // ECM descriptor is followed by either one or two ECM key data. + size_t key_data_offset = sizeof(EcmDescriptor); + if (rotation_enabled()) { + if (id == KeySlotId::kOddKeySlot) { + key_data_offset += key_data_size(); + } else if (id != KeySlotId::kEvenKeySlot) { + return nullptr; + } + } else { + // No rotation enabled. + if (id != KeySlotId::kEvenKeySlot) { + return nullptr; + } + } + return reinterpret_cast(&ecm_[key_data_offset]); +} + +bool EcmParserV2::create(const CasEcm& cas_ecm, + std::unique_ptr* parser) { + if (parser == nullptr) { + return false; + } + // Using 'new' to access a non-public constructor. + auto new_parser = std::unique_ptr(new EcmParserV2(cas_ecm)); + if (!new_parser->is_valid_size()) { + return false; + } + *parser = std::move(new_parser); + return true; +} + +uint8_t EcmParserV2::version() const { + const EcmDescriptor* ecm = reinterpret_cast(&ecm_[0]); + return ecm->version; +} + +CryptoMode EcmParserV2::crypto_mode() const { + const EcmDescriptor* ecm = reinterpret_cast(&ecm_[0]); + if (version() == 1) { + return static_cast( + (ecm->flags_cipher_rotation & kCryptoModeFlags) >> 1); + } + return static_cast( + (ecm->flags_cipher_rotation & kCryptoModeFlagsV2) >> 1); +} + +bool EcmParserV2::rotation_enabled() const { + const EcmDescriptor* ecm = reinterpret_cast(&ecm_[0]); + return (ecm->flags_cipher_rotation & kRotationFlag); +} + +size_t EcmParserV2::content_iv_size() const { + const EcmDescriptor* ecm = reinterpret_cast(&ecm_[0]); + // Content key IV size is 8 bytes if flag is zero, and 16 if flag is set. + return (ecm->flags_iv_age & kContentIVSizeFlag) ? 16 : 8; +} + +uint8_t EcmParserV2::age_restriction() const { + if (version() == 1) { + return 0; + } + const EcmDescriptor* ecm = reinterpret_cast(&ecm_[0]); + return (ecm->flags_iv_age & kAgeRestrictionMask) >> 1; +} + +std::vector EcmParserV2::entitlement_key_id(KeySlotId id) const { + std::vector ekey_id; + const EcmKeyData* key_data = key_slot_data(id); + if (key_data) { + ekey_id.assign( + &key_data->entitlement_key_id[0], + &key_data->entitlement_key_id[0] + kEntitlementKeyIDSizeBytes); + } + return ekey_id; +} + +std::vector EcmParserV2::content_key_id(KeySlotId id) const { + std::vector ckey_id; + const EcmKeyData* key_data = key_slot_data(id); + if (key_data) { + ckey_id.assign(&key_data->content_key_id[0], + &key_data->content_key_id[0] + kContentKeyIDSizeBytes); + } + return ckey_id; +} + +std::vector EcmParserV2::wrapped_key_data(KeySlotId id) const { + std::vector ckey_data; + const EcmKeyData* key_data = key_slot_data(id); + if (key_data) { + ckey_data.assign(&key_data->control_word[0], + &key_data->control_word[0] + kContentKeyDataSizeBytes); + } + return ckey_data; +} + +std::vector EcmParserV2::wrapped_key_iv(KeySlotId id) const { + std::vector iv; + const EcmKeyData* key_data = key_slot_data(id); + if (key_data) { + iv.assign(&key_data->control_word_iv[0], + &key_data->control_word_iv[0] + kWrappedKeyIVSizeBytes); + } + return iv; +} + +std::vector EcmParserV2::content_iv(KeySlotId id) const { + std::vector iv; + const EcmKeyData* key_data = key_slot_data(id); + if (key_data) { + iv.assign(&key_data->content_iv[0], + &key_data->content_iv[0] + content_iv_size()); + } + return iv; +} + +} // namespace wvcas diff --git a/plugin/src/ecm_parser_v3.cpp b/plugin/src/ecm_parser_v3.cpp new file mode 100644 index 0000000..ca1c786 --- /dev/null +++ b/plugin/src/ecm_parser_v3.cpp @@ -0,0 +1,223 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#include "ecm_parser_v3.h" + +#include + +#include "log.h" + +namespace wvcas { + +namespace { +using video_widevine::EcmKeyData; +using video_widevine::EcmMetaData; +using video_widevine::EcmPayload; +using video_widevine::SignedEcmPayload; + +constexpr int kEcmHeaderSize = 3; +constexpr uint8_t kEcmVersion = 3; +// 16 bytes fixed content key ids +constexpr uint8_t kEvenContentKeyId[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00}; +constexpr uint8_t kOddContentKeyId[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01}; + +CryptoMode ConvertProtoCipherMode(EcmMetaData::CipherMode cipher_mode) { + switch (cipher_mode) { + case EcmMetaData::AES_CBC: + return CryptoMode::kAesCBC; + case EcmMetaData::AES_CTR: + return CryptoMode::kAesCTR; + case EcmMetaData::DVB_CSA2: + return CryptoMode::kDvbCsa2; + case EcmMetaData::DVB_CSA3: + return CryptoMode::kDvbCsa3; + case EcmMetaData::AES_OFB: + return CryptoMode::kAesOFB; + case EcmMetaData::AES_SCTE52: + return CryptoMode::kAesSCTE; + case EcmMetaData::AES_ECB: + return CryptoMode::kAesECB; + case EcmMetaData::UNSPECIFIED: + default: + return CryptoMode::kInvalid; + } +} + +} // namespace + +EcmParserV3::EcmParserV3(SignedEcmPayload signed_ecm_payload, + EcmPayload ecm_payload) + : signed_ecm_payload_(std::move(signed_ecm_payload)), + ecm_payload_(std::move(ecm_payload)) { + even_key_data_ = ecm_payload_.even_key_data(); + odd_key_data_ = ecm_payload_.odd_key_data(); +} + +std::unique_ptr EcmParserV3::Create(const CasEcm& cas_ecm) { + if (cas_ecm.size() <= kEcmHeaderSize) { + LOGE("ECM is too short. Size: %lu", cas_ecm.size()); + return nullptr; + } + + SignedEcmPayload signed_ecm_payload; + // The 3 byte ecm header is ignored. + if (!signed_ecm_payload.ParseFromArray(cas_ecm.data() + kEcmHeaderSize, + cas_ecm.size() - kEcmHeaderSize)) { + LOGE("Unable to parse signed ecm payload"); + return nullptr; + } + + EcmPayload ecm_payload; + if (!ecm_payload.ParseFromString(signed_ecm_payload.serialized_payload())) { + LOGE("Unable to parse ecm payload"); + return nullptr; + } + + // Using 'new' to access a non-public constructor. + return std::unique_ptr( + new EcmParserV3(signed_ecm_payload, ecm_payload)); +} + +uint8_t EcmParserV3::version() const { return kEcmVersion; } + +CryptoMode EcmParserV3::crypto_mode() const { + return ConvertProtoCipherMode(ecm_payload_.meta_data().cipher_mode()); +} + +bool EcmParserV3::rotation_enabled() const { + return ecm_payload_.has_odd_key_data(); +} + +size_t EcmParserV3::content_iv_size() const { + return ecm_payload_.even_key_data().content_iv().size(); +} + +uint8_t EcmParserV3::age_restriction() const { + return static_cast(ecm_payload_.meta_data().age_restriction()); +} + +std::vector EcmParserV3::entitlement_key_id(KeySlotId id) const { + // Use the even entitlement_key_id if the odd one is empty (omitted). + const EcmKeyData& key_data = + id == KeySlotId::kOddKeySlot && + !odd_key_data_.entitlement_key_id().empty() + ? odd_key_data_ + : even_key_data_; + + return {key_data.entitlement_key_id().begin(), + key_data.entitlement_key_id().end()}; +} + +std::vector EcmParserV3::content_key_id(KeySlotId id) const { + if (id == KeySlotId::kEvenKeySlot && ecm_payload_.has_even_key_data()) { + return {kEvenContentKeyId, kEvenContentKeyId + sizeof(kEvenContentKeyId)}; + } else if (id == KeySlotId::kOddKeySlot && ecm_payload_.has_odd_key_data()) { + return {kOddContentKeyId, kOddContentKeyId + sizeof(kOddContentKeyId)}; + } + return {}; +} + +std::vector EcmParserV3::wrapped_key_data(KeySlotId id) const { + const EcmKeyData& key_data = + id == KeySlotId::kOddKeySlot ? odd_key_data_ : even_key_data_; + + return {key_data.wrapped_key_data().begin(), + key_data.wrapped_key_data().end()}; +} + +std::vector EcmParserV3::wrapped_key_iv(KeySlotId id) const { + // Use the even wrapped_key_iv if the odd one is empty (omitted). + const EcmKeyData* key_data = + id == KeySlotId::kOddKeySlot && !odd_key_data_.wrapped_key_iv().empty() + ? &odd_key_data_ + : &even_key_data_; + // Wrapped key IV may be omitted for group keys. + if (key_data->wrapped_key_iv().empty()) { + key_data = id == KeySlotId::kOddKeySlot && + !ecm_payload_.odd_key_data().wrapped_key_iv().empty() + ? &ecm_payload_.odd_key_data() + : &ecm_payload_.even_key_data(); + } + + return {key_data->wrapped_key_iv().begin(), key_data->wrapped_key_iv().end()}; +} + +std::vector EcmParserV3::content_iv(KeySlotId id) const { + // Use the even content_iv if the odd one is empty (omitted). + const EcmKeyData* key_data = + id == KeySlotId::kOddKeySlot && !odd_key_data_.content_iv().empty() + ? &odd_key_data_ + : &even_key_data_; + // Content IV may be omitted for group keys. + if (key_data->content_iv().empty()) { + key_data = id == KeySlotId::kOddKeySlot && + !ecm_payload_.odd_key_data().content_iv().empty() + ? &ecm_payload_.odd_key_data() + : &ecm_payload_.even_key_data(); + } + + return {key_data->content_iv().begin(), key_data->content_iv().end()}; +} + +bool EcmParserV3::has_fingerprinting() const { + return ecm_payload_.has_fingerprinting(); +} + +video_widevine::Fingerprinting EcmParserV3::fingerprinting() const { + return ecm_payload_.fingerprinting(); +} + +bool EcmParserV3::has_service_blocking() const { + return ecm_payload_.has_service_blocking(); +} + +video_widevine::ServiceBlocking EcmParserV3::service_blocking() const { + return ecm_payload_.service_blocking(); +} + +std::string EcmParserV3::ecm_serialized_payload() const { + return signed_ecm_payload_.serialized_payload(); +} +std::string EcmParserV3::signature() const { + return signed_ecm_payload_.signature(); +} + +bool EcmParserV3::set_group_id(const std::string& group_id) { + if (group_id.empty()) { + even_key_data_ = ecm_payload_.even_key_data(); + odd_key_data_ = ecm_payload_.odd_key_data(); + return true; + } + + bool found = false; + for (int i = 0; i < ecm_payload_.group_key_data_size(); ++i) { + const video_widevine::EcmGroupKeyData& group_key_data = + ecm_payload_.group_key_data(i); + if (group_key_data.group_id() == group_id) { + found = true; + even_key_data_ = group_key_data.even_key_data(); + odd_key_data_ = group_key_data.odd_key_data(); + break; + } + } + return found; +} + +bool EcmParserV3::is_entitlement_rotation_enabled() const { + return ecm_payload_.meta_data().has_entitlement_period_index(); +} + +uint32_t EcmParserV3::entitlement_period_index() const { + return ecm_payload_.meta_data().entitlement_period_index(); +} + +uint32_t EcmParserV3::entitlement_rotation_window_left() const { + return ecm_payload_.meta_data().entitlement_rotation_window_left(); +} + +} // namespace wvcas diff --git a/plugin/src/emm_parser.cpp b/plugin/src/emm_parser.cpp new file mode 100644 index 0000000..ec5ce38 --- /dev/null +++ b/plugin/src/emm_parser.cpp @@ -0,0 +1,75 @@ +// Copyright 2020 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#include "emm_parser.h" + +#include "log.h" + +namespace wvcas { +namespace { +// ETSI ETR 289 specifies table ids 0x82 to 0x8F are for CA System private +// usage, which are typically used by EMM, with one table id for each EMM type. +constexpr uint16_t kSectionHeader = 0x82; +constexpr size_t kSectionHeaderSize = 3; +constexpr size_t kSectionHeaderWithPointerSize = 4; +constexpr uint8_t kPointerFieldZero = 0x00; + +// Returns the possible starting index of EMM. -1 will be returned in case of +// error. It assumes the pointer field will always set to 0, if present. +int find_emm_start_index(const CasEmm& cas_emm) { + if (cas_emm.empty()) { + return -1; + } + // Case 1: Pointer field (always set to 0); section header; EMM. + if (cas_emm[0] == kPointerFieldZero) { + return kSectionHeaderWithPointerSize < cas_emm.size() + ? kSectionHeaderWithPointerSize + : -1; + } + // Case 2: Section header (3 bytes), EMM. + if (cas_emm[0] == kSectionHeader) { + return kSectionHeaderSize < cas_emm.size() ? kSectionHeaderSize : -1; + } + // Case 3: EMM. + return 0; +} + +} // namespace + +std::unique_ptr EmmParser::Create(const CasEmm& emm) { + auto parser = std::unique_ptr(new EmmParser()); + if (!parser->Parse(find_emm_start_index(emm), emm)) { + return nullptr; + } + return parser; +} + +bool EmmParser::Parse(int start_index, const CasEmm& emm) { + if (start_index < 0) { + return false; + } + + video_widevine::SignedEmmPayload signed_emm; + if (!signed_emm.ParseFromArray(emm.data() + start_index, + emm.size() - start_index)) { + LOGE("Failed to parse signed EMM."); + return false; + } + + signature_ = signed_emm.signature(); + if (signature_.empty()) { + LOGE("No signature in the EMM."); + return false; + } + + if (!emm_payload_.ParseFromString(signed_emm.serialized_payload())) { + LOGE("Failed to parse EMM payload."); + return false; + } + timestamp_ = emm_payload_.timestamp_secs(); + + return true; +} + +} // namespace wvcas diff --git a/plugin/src/license_key_status.cpp b/plugin/src/license_key_status.cpp new file mode 100644 index 0000000..d183ece --- /dev/null +++ b/plugin/src/license_key_status.cpp @@ -0,0 +1,355 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#include "license_key_status.h" + +#include + +#include "log.h" + +namespace { +// License protocol aliases +typedef ::video_widevine::License::KeyContainer KeyContainer; +typedef KeyContainer::OutputProtection OutputProtection; +typedef KeyContainer::VideoResolutionConstraint VideoResolutionConstraint; +typedef ::google::protobuf::RepeatedPtrField + ConstraintList; + +// Map the HDCP protection associated with a key in the license to +// an equivalent OEMCrypto HDCP protection level +wvcas::HdcpCapability ProtobufHdcpToOemCryptoHdcp( + const OutputProtection::HDCP& input) { + switch (input) { + case OutputProtection::HDCP_NONE: + return HDCP_NONE; + case OutputProtection::HDCP_V1: + return HDCP_V1; + case OutputProtection::HDCP_V2: + return HDCP_V2; + case OutputProtection::HDCP_V2_1: + return HDCP_V2_1; + case OutputProtection::HDCP_V2_2: + return HDCP_V2_2; + case OutputProtection::HDCP_V2_3: + return HDCP_V2_3; + case OutputProtection::HDCP_NO_DIGITAL_OUTPUT: + return HDCP_NO_DIGITAL_OUTPUT; + default: + LOGE( + "ContentKeyStatus::ProtobufHdcpToOemCryptoHdcp: " + "Unknown HDCP Level: input=%d, returning HDCP_NO_DIGITAL_OUTPUT", + input); + return HDCP_NO_DIGITAL_OUTPUT; + } +} + +// Returns the constraint from a set of constraints that matches the +// specified resolution, or null if none match +VideoResolutionConstraint* GetConstraintForRes( + uint32_t res, ConstraintList& constraints_from_key) { + typedef ConstraintList::pointer_iterator Iterator; + for (Iterator i = constraints_from_key.pointer_begin(); + i != constraints_from_key.pointer_end(); ++i) { + VideoResolutionConstraint* constraint = *i; + if (constraint->has_min_resolution_pixels() && + constraint->has_max_resolution_pixels() && + res >= constraint->min_resolution_pixels() && + res <= constraint->max_resolution_pixels()) { + return constraint; + } + } + return NULL; +} + +} // namespace + +namespace wvcas { + +bool LicenseKeys::IsContentKey(const std::string& key_id) { + if (key_statuses_.count(key_id) > 0) { + return key_statuses_[key_id]->IsContentKey(); + } else if (content_keyid_to_entitlement_key_id_.count(key_id) > 0) { + return true; + } else { + return false; + } +} + +bool LicenseKeys::CanDecryptContent(const std::string& key_id) { + if (key_statuses_.count(key_id) > 0) { + return key_statuses_[key_id]->CanDecryptContent(); + } else if (content_keyid_to_entitlement_key_id_.count(key_id) > 0) { + if (key_statuses_.count(content_keyid_to_entitlement_key_id_[key_id]) > 0) { + return key_statuses_[content_keyid_to_entitlement_key_id_[key_id]] + ->CanDecryptContent(); + } + return false; + } else { + return false; + } +} + +bool LicenseKeys::GetAllowedUsage(const KeyId& key_id, + KeyAllowedUsage* allowed_usage) { + if (key_statuses_.count(key_id) > 0) { + return key_statuses_[key_id]->GetAllowedUsage(allowed_usage); + } else if (content_keyid_to_entitlement_key_id_.count(key_id) > 0) { + if (key_statuses_.count(content_keyid_to_entitlement_key_id_[key_id]) > 0) { + return key_statuses_[content_keyid_to_entitlement_key_id_[key_id]] + ->CanDecryptContent(); + } + return false; + } else { + return false; + } +} + +bool LicenseKeys::ApplyStatusChange(KeyStatus new_status, + bool* new_usable_keys) { + bool keys_changed = false; + bool newly_usable = false; + *new_usable_keys = false; + for (LicenseKeyStatusIterator it = key_statuses_.begin(); + it != key_statuses_.end(); ++it) { + bool usable; + if (it->second->ApplyStatusChange(new_status, &usable)) { + newly_usable |= usable; + keys_changed = true; + } + } + *new_usable_keys = newly_usable; + return keys_changed; +} + +KeyStatus LicenseKeys::GetKeyStatus(const KeyId& key_id) { + if (key_statuses_.count(key_id) == 0) { + return kKeyStatusKeyUnknown; + } + return key_statuses_[key_id]->GetKeyStatus(); +} + +void LicenseKeys::ExtractKeyStatuses(KeyStatusMap* content_keys) { + content_keys->clear(); + for (LicenseKeyStatusIterator it = key_statuses_.begin(); + it != key_statuses_.end(); ++it) { + if (it->second->IsContentKey()) { + const KeyId key_id = it->first; + KeyStatus key_status = it->second->GetKeyStatus(); + (*content_keys)[key_id] = key_status; + } + } +} + +bool LicenseKeys::MeetsConstraints(const KeyId& key_id) { + if (key_statuses_.count(key_id) > 0) { + return key_statuses_[key_id]->MeetsConstraints(); + } else { + // If a Key ID is unknown to us, we don't know of any constraints for it, + // so never block decryption. + return true; + } +} + +void LicenseKeys::ApplyConstraints(uint32_t new_resolution, + HdcpCapability new_hdcp_level) { + for (LicenseKeyStatusIterator i = key_statuses_.begin(); + i != key_statuses_.end(); ++i) { + i->second->ApplyConstraints(new_resolution, new_hdcp_level); + } +} + +void LicenseKeys::SetFromLicense(const video_widevine::License& license) { + this->Clear(); + for (int32_t key_index = 0; key_index < license.key_size(); ++key_index) { + const KeyContainer& key = license.key(key_index); + if (key.has_id() && (key.type() == KeyContainer::CONTENT || + key.type() == KeyContainer::OPERATOR_SESSION || + key.type() == KeyContainer::ENTITLEMENT)) { + const KeyId& key_id = key.id(); + key_statuses_[key_id] = new LicenseKeyStatus(key); + } + } +} + +void LicenseKeys::SetEntitledKeys( + const std::vector& keys) { + for (std::vector::const_iterator key = + keys.begin(); + key != keys.end(); key++) { + // Check to see if we have an entitlement key for this content key. + std::map::iterator entitlement = + key_statuses_.find(key->entitlement_key_id()); + if (entitlement == key_statuses_.end()) { + continue; + } + // And set the new content key id. + content_keyid_to_entitlement_key_id_[key->key_id()] = + key->entitlement_key_id(); + } +} + +LicenseKeyStatus::LicenseKeyStatus(const KeyContainer& key) + : is_content_key_(false), + key_status_(kKeyStatusInternalError), + meets_constraints_(true), + default_hdcp_level_(HDCP_NONE) { + allowed_usage_.Clear(); + constraints_.Clear(); + + if (key.type() == KeyContainer::CONTENT || + key.type() == KeyContainer::ENTITLEMENT) { + ParseContentKey(key); + } else if (key.type() == KeyContainer::OPERATOR_SESSION) { + ParseOperatorSessionKey(key); + } +} + +void LicenseKeyStatus::ParseContentKey(const KeyContainer& key) { + is_content_key_ = true; + if (key.has_level() && ((key.level() == KeyContainer::HW_SECURE_DECODE) || + (key.level() == KeyContainer::HW_SECURE_ALL))) { + allowed_usage_.decrypt_to_clear_buffer = false; + allowed_usage_.decrypt_to_secure_buffer = true; + } else { + allowed_usage_.decrypt_to_clear_buffer = true; + allowed_usage_.decrypt_to_secure_buffer = true; + } + + if (key.has_level()) { + switch (key.level()) { + case KeyContainer::SW_SECURE_CRYPTO: + allowed_usage_.key_security_level_ = kSoftwareSecureCrypto; + break; + case KeyContainer::SW_SECURE_DECODE: + allowed_usage_.key_security_level_ = kSoftwareSecureDecode; + break; + case KeyContainer::HW_SECURE_CRYPTO: + allowed_usage_.key_security_level_ = kHardwareSecureCrypto; + break; + case KeyContainer::HW_SECURE_DECODE: + allowed_usage_.key_security_level_ = kHardwareSecureDecode; + break; + case KeyContainer::HW_SECURE_ALL: + allowed_usage_.key_security_level_ = kHardwareSecureAll; + break; + default: + allowed_usage_.key_security_level_ = kKeySecurityLevelUnknown; + break; + } + } else { + allowed_usage_.key_security_level_ = kKeySecurityLevelUnset; + } + allowed_usage_.SetValid(); + + if (key.video_resolution_constraints_size() > 0) { + SetConstraints(key.video_resolution_constraints()); + } + + if (key.has_required_protection()) { + default_hdcp_level_ = + ProtobufHdcpToOemCryptoHdcp(key.required_protection().hdcp()); + } +} + +void LicenseKeyStatus::ParseOperatorSessionKey(const KeyContainer& key) { + is_content_key_ = false; + if (key.has_operator_session_key_permissions()) { + OperatorSessionKeyPermissions permissions = + key.operator_session_key_permissions(); + if (permissions.has_allow_encrypt()) + allowed_usage_.generic_encrypt = permissions.allow_encrypt(); + if (permissions.has_allow_decrypt()) + allowed_usage_.generic_decrypt = permissions.allow_decrypt(); + if (permissions.has_allow_sign()) + allowed_usage_.generic_sign = permissions.allow_sign(); + if (permissions.has_allow_signature_verify()) + allowed_usage_.generic_verify = permissions.allow_signature_verify(); + } else { + allowed_usage_.generic_encrypt = false; + allowed_usage_.generic_decrypt = false; + allowed_usage_.generic_sign = false; + allowed_usage_.generic_verify = false; + } + allowed_usage_.SetValid(); +} + +void LicenseKeys::Clear() { + for (LicenseKeyStatusIterator i = key_statuses_.begin(); + i != key_statuses_.end(); ++i) { + delete i->second; + } + key_statuses_.clear(); +} + +bool LicenseKeyStatus::CanDecryptContent() { + return is_content_key_ && key_status_ == kKeyStatusUsable; +} + +bool LicenseKeyStatus::GetAllowedUsage(KeyAllowedUsage* allowed_usage) { + if (NULL == allowed_usage) return false; + *allowed_usage = allowed_usage_; + return true; +} + +bool LicenseKeyStatus::ApplyStatusChange(KeyStatus new_status, + bool* new_usable_key) { + *new_usable_key = false; + if (!is_content_key_) { + return false; + } + KeyStatus updated_status = new_status; + if (updated_status == kKeyStatusUsable) { + if (!MeetsConstraints()) { + updated_status = kKeyStatusOutputNotAllowed; + } + } + if (key_status_ != updated_status) { + key_status_ = updated_status; + if (updated_status == kKeyStatusUsable) { + *new_usable_key = true; + } + return true; + } + return false; +} + +// If the key has constraints, find the constraint that applies. +// If none found, then the constraint test fails. +// If a constraint is found, verify that the device's current HDCP +// level is sufficient. If the constraint has an HDCP setting, use it, +// If the key has no constraints, or if the constraint has no HDCP +// requirement, use the key's default HDCP setting to check against the +// device's current HDCP level. +void LicenseKeyStatus::ApplyConstraints(uint32_t video_pixels, + HdcpCapability new_hdcp_level) { + VideoResolutionConstraint* current_constraint = NULL; + if (HasConstraints() && video_pixels != HDCP_UNSPECIFIED_VIDEO_RESOLUTION) { + current_constraint = GetConstraintForRes(video_pixels, constraints_); + if (NULL == current_constraint) { + meets_constraints_ = false; + return; + } + } + + HdcpCapability desired_hdcp_level; + if (current_constraint && current_constraint->has_required_protection()) { + desired_hdcp_level = ProtobufHdcpToOemCryptoHdcp( + current_constraint->required_protection().hdcp()); + } else { + desired_hdcp_level = default_hdcp_level_; + } + + meets_constraints_ = (new_hdcp_level >= desired_hdcp_level); +} + +void LicenseKeyStatus::SetConstraints(const ConstraintList& constraints) { + if (!is_content_key_) { + return; + } + constraints_.Clear(); + constraints_.MergeFrom(constraints); + meets_constraints_ = true; +} + +} // namespace wvcas diff --git a/plugin/src/oemcrypto_interface.cpp b/plugin/src/oemcrypto_interface.cpp new file mode 100644 index 0000000..bd915c7 --- /dev/null +++ b/plugin/src/oemcrypto_interface.cpp @@ -0,0 +1,413 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#include "oemcrypto_interface.h" + +#include + +#include + +#include "cas_properties.h" +#include "cas_util.h" +#include "log.h" + +// These macros lookup the obfuscated name used for oemcrypto. +#define QUOTE_DEFINE(A) #A +#define QUOTE(A) QUOTE_DEFINE(A) +#define LOOKUP(handle, name) dlsym(handle, QUOTE(name)) +#define LOAD_SYM(name) \ + name = reinterpret_cast(LOOKUP(handle_, OEMCrypto_##name)); \ + if (nullptr == name) { \ + LOGE("%s", dlerror()); \ + return false; \ + } +#define TRY_LOAD_SYM(name) \ + name = reinterpret_cast(LOOKUP(handle_, OEMCrypto_##name)); \ + if (nullptr == name) { \ + LOGW("%s", dlerror()); \ + } + +namespace wvcas { +class OEMCryptoInterface::Impl { + public: + ~Impl() {} + static std::unique_ptr create() { + // Using 'new' to access a non-public constructor. + std::unique_ptr interface = std::unique_ptr(new Impl()); + if (!interface->initialize()) { + return std::unique_ptr(); + } + return interface; + } + + typedef OEMCryptoResult (*Initialize_t)(); + typedef OEMCryptoResult (*Terminate_t)(); + typedef OEMCryptoResult (*OpenSession_t)(OEMCrypto_SESSION*); + typedef OEMCryptoResult (*CloseSession_t)(OEMCrypto_SESSION); + typedef OEMCrypto_ProvisioningMethod (*GetProvisioningMethod_t)(); + typedef OEMCryptoResult (*GetKeyData_t)(uint8_t*, size_t*); + typedef uint32_t (*SupportedCertificates_t)(); + typedef OEMCryptoResult (*GenerateNonce_t)(OEMCrypto_SESSION session, + uint32_t* nonce); + typedef OEMCryptoResult (*PrepAndSignLicenseRequest_t)(OEMCrypto_SESSION, + const uint8_t*, size_t, + size_t*, uint8_t*, + size_t*); + typedef OEMCryptoResult (*PrepAndSignRenewalRequest_t)(OEMCrypto_SESSION, + const uint8_t*, size_t, + size_t*, uint8_t*, + size_t*); + typedef OEMCryptoResult (*PrepAndSignProvisioningRequest_t)( + OEMCrypto_SESSION, const uint8_t*, size_t, size_t*, uint8_t*, size_t*); + typedef OEMCryptoResult (*LoadProvisioning_t)(OEMCrypto_SESSION, + const uint8_t*, size_t, size_t, + const uint8_t*, size_t, + uint8_t*, size_t*); + typedef OEMCryptoResult (*GenerateDerivedKeys_t)( + OEMCrypto_SESSION session, const uint8_t* mac_key_context, + uint32_t mac_key_context_length, const uint8_t* enc_key_context, + uint32_t enc_key_context_length); + typedef OEMCryptoResult (*GetOEMPublicCertificate_t)(OEMCrypto_SESSION, + uint8_t*, size_t*); + typedef OEMCryptoResult (*LoadDRMPrivateKey_t)(OEMCrypto_SESSION, + OEMCrypto_PrivateKeyType, + const uint8_t*, size_t); + typedef OEMCryptoResult (*GenerateRSASignature_t)(OEMCrypto_SESSION, + const uint8_t*, size_t, + uint8_t*, size_t*, + RSA_Padding_Scheme); + typedef OEMCryptoResult (*DeriveKeysFromSessionKey_t)(OEMCrypto_SESSION, + const uint8_t*, size_t, + const uint8_t*, size_t, + const uint8_t*, size_t); + typedef OEMCryptoResult (*LoadLicense_t)(OEMCrypto_SESSION session, + const uint8_t* message, + size_t message_length, + size_t core_message_length, + const uint8_t* signature, + size_t signature_length); + typedef OEMCryptoResult (*LoadRenewal_t)(OEMCrypto_SESSION session, + const uint8_t* message, + size_t message_length, + size_t core_message_length, + const uint8_t* signature, + size_t signature_length); + typedef OEMCryptoResult (*LoadCasECMKeys_t)( + OEMCrypto_SESSION, const uint8_t*, size_t, + const OEMCrypto_EntitledContentKeyObject*, + const OEMCrypto_EntitledContentKeyObject*); + typedef OEMCryptoResult (*GetHDCPCapability_t)(OEMCrypto_HDCP_Capability*, + OEMCrypto_HDCP_Capability*); + typedef OEMCryptoResult (*GetDeviceID_t)(uint8_t* deviceID, size_t* idLength); + typedef OEMCryptoResult (*LoadTestKeybox_t)(const uint8_t* buffer, + size_t length); + typedef const char* (*SecurityLevel_t)(); + typedef OEMCryptoResult (*CreateEntitledKeySession_t)( + OEMCrypto_SESSION oec_session, OEMCrypto_SESSION* key_session); + typedef OEMCryptoResult (*RemoveEntitledKeySession_t)( + OEMCrypto_SESSION key_session); + typedef OEMCryptoResult (*ReassociateEntitledKeySession_t)( + OEMCrypto_SESSION key_session, OEMCrypto_SESSION oec_session); + typedef uint32_t (*APIVersion_t)(); + typedef OEMCryptoResult (*GetOEMKeyToken_t)(OEMCrypto_SESSION key_session, + uint8_t* key_token, + size_t* key_token_length); + typedef OEMCryptoResult (*GetSignatureHashAlgorithm_t)( + OEMCrypto_SESSION session, OEMCrypto_SignatureHashAlgorithm* algorithm); + + Initialize_t Initialize = nullptr; + Terminate_t Terminate = nullptr; + OpenSession_t OpenSession = nullptr; + CloseSession_t CloseSession = nullptr; + GetProvisioningMethod_t GetProvisioningMethod = nullptr; + GetKeyData_t GetKeyData = nullptr; + SupportedCertificates_t SupportedCertificates = nullptr; + GenerateNonce_t GenerateNonce = nullptr; + GenerateDerivedKeys_t GenerateDerivedKeys = nullptr; + PrepAndSignLicenseRequest_t PrepAndSignLicenseRequest = nullptr; + PrepAndSignRenewalRequest_t PrepAndSignRenewalRequest = nullptr; + PrepAndSignProvisioningRequest_t PrepAndSignProvisioningRequest = nullptr; + LoadProvisioning_t LoadProvisioning = nullptr; + GetOEMPublicCertificate_t GetOEMPublicCertificate = nullptr; + LoadDRMPrivateKey_t LoadDRMPrivateKey = nullptr; + GenerateRSASignature_t GenerateRSASignature = nullptr; + DeriveKeysFromSessionKey_t DeriveKeysFromSessionKey = nullptr; + LoadLicense_t LoadLicense = nullptr; + LoadRenewal_t LoadRenewal = nullptr; + LoadCasECMKeys_t LoadCasECMKeys = nullptr; + GetHDCPCapability_t GetHDCPCapability = nullptr; + GetDeviceID_t GetDeviceID = nullptr; + LoadTestKeybox_t LoadTestKeybox = nullptr; + SecurityLevel_t SecurityLevel = nullptr; + CreateEntitledKeySession_t CreateEntitledKeySession = nullptr; + RemoveEntitledKeySession_t RemoveEntitledKeySession = nullptr; + ReassociateEntitledKeySession_t ReassociateEntitledKeySession = nullptr; + APIVersion_t APIVersion = nullptr; + GetOEMKeyToken_t GetOEMKeyToken = nullptr; + GetSignatureHashAlgorithm_t GetSignatureHashAlgorithm = nullptr; + + private: + bool initialize() { + dlerror(); + std::string oemcrypto_path; + if (!Properties::GetOEMCryptoPath(&oemcrypto_path)) { + LOGE("Can't get oemc library path"); + return false; + } + handle_ = dlopen(oemcrypto_path.c_str(), RTLD_NOW); + if (nullptr == handle_) { + LOGE("Can't open oemc library: %s", dlerror()); + return false; + } + + LOAD_SYM(Initialize); + LOAD_SYM(Terminate); + LOAD_SYM(OpenSession); + LOAD_SYM(CloseSession); + LOAD_SYM(GetProvisioningMethod); + LOAD_SYM(GetKeyData); + LOAD_SYM(SupportedCertificates); + LOAD_SYM(GenerateNonce); + LOAD_SYM(GenerateDerivedKeys); + LOAD_SYM(PrepAndSignLicenseRequest); + LOAD_SYM(PrepAndSignRenewalRequest); + LOAD_SYM(PrepAndSignProvisioningRequest); + LOAD_SYM(LoadProvisioning); + LOAD_SYM(GetOEMPublicCertificate); + LOAD_SYM(LoadDRMPrivateKey); + LOAD_SYM(GenerateRSASignature); + LOAD_SYM(DeriveKeysFromSessionKey); + LOAD_SYM(LoadLicense); + LOAD_SYM(LoadRenewal); + LOAD_SYM(LoadCasECMKeys); + LOAD_SYM(GetHDCPCapability); + LOAD_SYM(GetDeviceID); + LOAD_SYM(SecurityLevel); + LOAD_SYM(CreateEntitledKeySession); + LOAD_SYM(RemoveEntitledKeySession); + LOAD_SYM(APIVersion); + LOAD_SYM(ReassociateEntitledKeySession); + TRY_LOAD_SYM(GetOEMKeyToken); + TRY_LOAD_SYM(GetSignatureHashAlgorithm); + + // Optional methods that may be available. + TRY_LOAD_SYM(LoadTestKeybox); + return true; + } + Impl() : handle_(nullptr) {} + void* handle_ = nullptr; +}; + +OEMCryptoInterface::OEMCryptoInterface() {} +OEMCryptoInterface::~OEMCryptoInterface() = default; + +OEMCryptoResult OEMCryptoInterface::OEMCrypto_Initialize(void) { + if (!impl_) { + impl_ = Impl::create(); + if (!impl_) { + return OEMCrypto_ERROR_INIT_FAILED; + } + } + return impl_->Initialize(); +} + +OEMCryptoResult OEMCryptoInterface::OEMCrypto_Terminate(void) { + return impl_->Terminate(); +} + +OEMCryptoResult OEMCryptoInterface::OEMCrypto_OpenSession( + OEMCrypto_SESSION* session) const { + return impl_->OpenSession(session); +} + +OEMCryptoResult OEMCryptoInterface::OEMCrypto_CloseSession( + OEMCrypto_SESSION session) const { + return impl_->CloseSession(session); +} + +OEMCrypto_ProvisioningMethod +OEMCryptoInterface::OEMCrypto_GetProvisioningMethod() const { + return impl_->GetProvisioningMethod(); +} + +OEMCryptoResult OEMCryptoInterface::OEMCrypto_GetKeyData( + uint8_t* keyData, size_t* keyDataLength) const { + return impl_->GetKeyData(keyData, keyDataLength); +} + +uint32_t OEMCryptoInterface::OEMCrypto_SupportedCertificates() const { + return impl_->SupportedCertificates(); +} + +OEMCryptoResult OEMCryptoInterface::OEMCrypto_GenerateNonce( + OEMCrypto_SESSION session, uint32_t* nonce) const { + return impl_->GenerateNonce(session, nonce); +} + +OEMCryptoResult OEMCryptoInterface::OEMCrypto_GenerateDerivedKeys( + OEMCrypto_SESSION session, const uint8_t* mac_key_context, + uint32_t mac_key_context_length, const uint8_t* enc_key_context, + uint32_t enc_key_context_length) const { + return impl_->GenerateDerivedKeys(session, mac_key_context, + mac_key_context_length, enc_key_context, + enc_key_context_length); +} + +OEMCryptoResult OEMCryptoInterface::OEMCrypto_PrepAndSignLicenseRequest( + OEMCrypto_SESSION session, uint8_t* message, size_t message_length, + size_t* core_message_size, uint8_t* signature, + size_t* signature_length) const { + return impl_->PrepAndSignLicenseRequest(session, message, message_length, + core_message_size, signature, + signature_length); +} + +OEMCryptoResult OEMCryptoInterface::OEMCrypto_PrepAndSignRenewalRequest( + OEMCrypto_SESSION session, uint8_t* message, size_t message_length, + size_t* core_message_size, uint8_t* signature, + size_t* signature_length) const { + return impl_->PrepAndSignRenewalRequest(session, message, message_length, + core_message_size, signature, + signature_length); +} + +OEMCryptoResult OEMCryptoInterface::OEMCrypto_PrepAndSignProvisioningRequest( + OEMCrypto_SESSION session, uint8_t* message, size_t message_length, + size_t* core_message_size, uint8_t* signature, + size_t* signature_length) const { + return impl_->PrepAndSignProvisioningRequest(session, message, message_length, + core_message_size, signature, + signature_length); +} + +OEMCryptoResult OEMCryptoInterface::OEMCrypto_LoadProvisioning( + OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, + size_t core_message_length, const uint8_t* signature, + size_t signature_length, uint8_t* wrapped_private_key, + size_t* wrapped_private_key_length) const { + return impl_->LoadProvisioning( + session, message, message_length, core_message_length, signature, + signature_length, wrapped_private_key, wrapped_private_key_length); +} + +OEMCryptoResult OEMCryptoInterface::OEMCrypto_GetOEMPublicCertificate( + OEMCrypto_SESSION session, uint8_t* public_cert, + size_t* public_cert_length) const { + return impl_->GetOEMPublicCertificate(session, public_cert, + public_cert_length); +} + +OEMCryptoResult OEMCryptoInterface::OEMCrypto_LoadDRMPrivateKey( + OEMCrypto_SESSION session, OEMCrypto_PrivateKeyType key_type, + const uint8_t* wrapped_rsa_key, size_t wrapped_rsa_key_length) const { + return impl_->LoadDRMPrivateKey(session, key_type, wrapped_rsa_key, + wrapped_rsa_key_length); +} + +OEMCryptoResult OEMCryptoInterface::OEMCrypto_GenerateRSASignature( + OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, + uint8_t* signature, size_t* signature_length, + RSA_Padding_Scheme padding_scheme) const { + return impl_->GenerateRSASignature(session, message, message_length, + signature, signature_length, + padding_scheme); +} + +OEMCryptoResult OEMCryptoInterface::OEMCrypto_DeriveKeysFromSessionKey( + OEMCrypto_SESSION session, const uint8_t* enc_session_key, + size_t enc_session_key_length, const uint8_t* mac_key_context, + size_t mac_key_context_length, const uint8_t* enc_key_context, + size_t enc_key_context_length) const { + return impl_->DeriveKeysFromSessionKey( + session, enc_session_key, enc_session_key_length, mac_key_context, + mac_key_context_length, enc_key_context, enc_key_context_length); +} + +OEMCryptoResult OEMCryptoInterface::OEMCrypto_LoadLicense( + OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, + size_t core_message_length, const uint8_t* signature, + size_t signature_length) const { + return impl_->LoadLicense(session, message, message_length, + core_message_length, signature, signature_length); +} + +OEMCryptoResult OEMCryptoInterface::OEMCrypto_LoadRenewal( + OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, + size_t core_message_length, const uint8_t* signature, + size_t signature_length) const { + return impl_->LoadRenewal(session, message, message_length, + core_message_length, signature, signature_length); +} + +OEMCryptoResult OEMCryptoInterface::OEMCrypto_LoadCasECMKeys( + OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, + const OEMCrypto_EntitledContentKeyObject* even_key, + const OEMCrypto_EntitledContentKeyObject* odd_key) const { + return impl_->LoadCasECMKeys(session, message, message_length, even_key, + odd_key); +} + +OEMCryptoResult OEMCryptoInterface::OEMCrypto_GetHDCPCapability( + OEMCrypto_HDCP_Capability* current, OEMCrypto_HDCP_Capability* max) const { + return impl_->GetHDCPCapability(current, max); +} + +OEMCryptoResult OEMCryptoInterface::OEMCrypto_GetDeviceID( + uint8_t* deviceID, size_t* idLength) const { + return impl_->GetDeviceID(deviceID, idLength); +} + +OEMCryptoResult OEMCryptoInterface::OEMCrypto_LoadTestKeybox( + const uint8_t* buffer, size_t length) const { + // Optional method. Handle missing method. + if (impl_->LoadTestKeybox == nullptr) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; + } + return impl_->LoadTestKeybox(buffer, length); +} + +const char* OEMCryptoInterface::OEMCrypto_SecurityLevel() const { + return impl_->SecurityLevel(); +} + +OEMCryptoResult OEMCryptoInterface::OEMCrypto_CreateEntitledKeySession( + OEMCrypto_SESSION oec_session, OEMCrypto_SESSION* key_session) const { + return impl_->CreateEntitledKeySession(oec_session, key_session); +} + +OEMCryptoResult OEMCryptoInterface::OEMCrypto_RemoveEntitledKeySession( + OEMCrypto_SESSION key_session) const { + return impl_->RemoveEntitledKeySession(key_session); +} + +OEMCryptoResult OEMCryptoInterface::OEMCrypto_ReassociateEntitledKeySession( + OEMCrypto_SESSION key_session, OEMCrypto_SESSION oec_session) const { + return impl_->ReassociateEntitledKeySession(key_session, oec_session); +} + +uint32_t OEMCryptoInterface::OEMCrypto_APIVersion() const { + return impl_->APIVersion(); +} + +OEMCryptoResult OEMCryptoInterface::OEMCrypto_GetOEMKeyToken( + OEMCrypto_SESSION key_session, uint8_t* key_token, + size_t* key_token_length) const { + // Optional method. Handle missing method. + if (impl_->GetOEMKeyToken == nullptr) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; + } + return impl_->GetOEMKeyToken(key_session, key_token, key_token_length); +} + +OEMCryptoResult OEMCryptoInterface::OEMCrypto_GetSignatureHashAlgorithm( + OEMCrypto_SESSION session, + OEMCrypto_SignatureHashAlgorithm* algorithm) const { + // Optional method. Handle missing method. + if (impl_->GetOEMKeyToken == nullptr) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; + } + return impl_->GetSignatureHashAlgorithm(session, algorithm); +} + +} // namespace wvcas diff --git a/plugin/src/policy_engine.cpp b/plugin/src/policy_engine.cpp new file mode 100644 index 0000000..eb885d3 --- /dev/null +++ b/plugin/src/policy_engine.cpp @@ -0,0 +1,368 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#include "policy_engine.h" + +#include + +#include + +#include "log.h" +#include "string_conversions.h" + +// TODO(vtarasov): +//#include "properties.h" +//#include "cas_event_listener.h" + +using video_widevine::License; + +namespace { + +static constexpr int kPolicyTimerDurationSeconds = 1; +static constexpr int kClockSkewDelta = 5; // seconds + +// Use 0 to represent never expired license as specified in EME spec +// (NaN in JS translates to 0 in unix timestamp). +static constexpr int64_t NEVER_EXPIRES = 0; + +} // namespace + +namespace wvcas { + +bool PolicyEngine::CanDecryptContent(const KeyId& key_id) const { + if (license_keys_->IsContentKey(key_id)) { + return license_keys_->CanDecryptContent(key_id); + } else { + LOGE("PolicyEngine::CanDecryptContent Key '%s' not in license.", + wvutil::b2a_hex(key_id).c_str()); + return false; + } +} + +void PolicyEngine::SetLicense(const License& license) { + license_id_.Clear(); + license_id_.CopyFrom(license.id()); + policy_.Clear(); + license_keys_->SetFromLicense(license); + UpdateLicense(license); +} + +void PolicyEngine::UpdateLicense(const License& license) { + if (!license.has_policy()) return; + + if (kLicenseStateExpired == license_state_) { + LOGD("PolicyEngine::UpdateLicense: updating an expired license"); + } + + policy_.MergeFrom(license.policy()); + + // some basic license validation + // license start time needs to be specified in the initial response + if (!license.has_license_start_time()) return; + + // if renewal, discard license if version has not been updated + if (license_state_ != kLicenseStateInitial && policy_.can_play()) { + if (license.id().version() > license_id_.version()) + license_id_.CopyFrom(license.id()); + else + return; + } + + // Update time information + license_start_time_ = license.license_start_time(); + next_renewal_time_ = license_start_time_ + policy_.renewal_delay_seconds(); + + int64_t current_time = GetCurrentTime(); + if ((!policy_.can_play() || + HasLicenseOrPlaybackDurationExpired(current_time)) && + license_state_ != kLicenseStateExpired) { + license_state_ = kLicenseStateExpired; + NotifyLicenseExpired(license_state_); + NotifyKeysChange(kKeyStatusExpired); + return; + } + + // Update state + if (current_time >= license_start_time_) { + license_state_ = kLicenseStateCanPlay; + NotifyKeysChange(kKeyStatusUsable); + } else { + license_state_ = kLicenseStatePending; + NotifyKeysChange(kKeyStatusPending); + } + NotifyExpirationUpdate(current_time); + NotifyRenewalServerUpdate(); +} + +int64_t PolicyEngine::GetCurrentTime() { + int64_t current_time = clock_->GetCurrentTime(); + if (current_time + kClockSkewDelta < last_recorded_current_time_) + current_time = last_recorded_current_time_; + else + last_recorded_current_time_ = current_time; + return current_time; +} + +void PolicyEngine::NotifyRenewalServerUpdate() { + if (policy_.renewal_server_url() != renewal_server_url_) { + renewal_server_url_ = policy_.renewal_server_url(); + if (event_listener_) { + event_listener_->OnNewRenewalServerUrl(renewal_server_url_); + } + } +} + +void PolicyEngine::NotifyLicenseExpired(LicenseState license_state) { + if (event_listener_ && license_state == kLicenseStateExpired) { + event_listener_->OnLicenseExpiration(); + } +} + +// Apply a key status to the current keys. +// If this represents a new key status, perform a notification callback. +// NOTE: if the new status is kKeyStatusUsable, the HDCP check may result in an +// override to kKeyStatusOutputNotAllowed. +void PolicyEngine::NotifyKeysChange(KeyStatus new_status) { + bool keys_changed; + bool has_new_usable_key = false; + if (new_status == kKeyStatusUsable) { + CheckDeviceHdcpStatus(); + } + keys_changed = + license_keys_->ApplyStatusChange(new_status, &has_new_usable_key); + if (event_listener_ && keys_changed) { + KeyStatusMap content_keys; + license_keys_->ExtractKeyStatuses(&content_keys); + event_listener_->OnSessionKeysChange(content_keys, has_new_usable_key); + } +} + +bool PolicyEngine::HasLicenseOrPlaybackDurationExpired(int64_t current_time) { + const int64_t expiry_time = + GetExpiryTime(current_time, + /* ignore_soft_enforce_playback_duration */ false); + return expiry_time != NEVER_EXPIRES && expiry_time <= current_time; +} + +void PolicyEngine::NotifyExpirationUpdate(int64_t current_time) { + const int64_t expiry_time = + GetExpiryTime(current_time, + /* ignore_soft_enforce_playback_duration */ false); + if (!last_expiry_time_set_ || expiry_time != last_expiry_time_) { + last_expiry_time_ = expiry_time; + if (event_listener_) event_listener_->OnExpirationUpdate(expiry_time); + } + last_expiry_time_set_ = true; +} + +int64_t PolicyEngine::GetExpiryTime( + int64_t current_time, bool ignore_soft_enforce_playback_duration) { + if (!HasPlaybackStarted(current_time)) return GetRentalExpiryTime(); + + const int64_t hard_limit = GetHardLicenseExpiryTime(); + if (policy_.playback_duration_seconds() == 0) return hard_limit; + if (!ignore_soft_enforce_playback_duration && !was_expired_on_load_ && + policy_.soft_enforce_playback_duration()) { + return hard_limit; + } + const int64_t expiry_time = + playback_start_time_ + policy_.playback_duration_seconds(); + + if (hard_limit == NEVER_EXPIRES) return expiry_time; + return std::min(hard_limit, expiry_time); +} + +void PolicyEngine::CheckDeviceHdcpStatusOnTimer(int64_t current_time) { + if (current_time >= next_device_check_) { + CheckDeviceHdcpStatus(); + next_device_check_ = current_time + HDCP_DEVICE_CHECK_INTERVAL; + } +} + +int64_t PolicyEngine::GetRentalExpiryTime() { + const int64_t hard_limit = GetHardLicenseExpiryTime(); + if (policy_.rental_duration_seconds() == 0) return hard_limit; + const int64_t expiry_time = + license_start_time_ + policy_.rental_duration_seconds(); + if (hard_limit == NEVER_EXPIRES) return expiry_time; + return std::min(hard_limit, expiry_time); +} + +// For the policy time fields checked in the following methods, a value of 0 +// indicates that there is no limit to the duration. If the fields are zero +// (including the hard limit) then these methods will return NEVER_EXPIRES. + +int64_t PolicyEngine::GetHardLicenseExpiryTime() { + return policy_.license_duration_seconds() > 0 + ? license_start_time_ + policy_.license_duration_seconds() + : NEVER_EXPIRES; +} + +void PolicyEngine::CheckDeviceHdcpStatus() { + if (!license_keys_->Empty()) { + HdcpCapability current_hdcp_level; + HdcpCapability ignored; + if (!crypto_session_->GetHdcpCapabilities(¤t_hdcp_level, &ignored)) { + current_hdcp_level = HDCP_NONE; + } + license_keys_->ApplyConstraints(current_resolution_, current_hdcp_level); + } +} + +void PolicyEngine::BeginDecryption() { + if (playback_start_time_ == 0) { + switch (license_state_) { + case kLicenseStateCanPlay: + case kLicenseStateNeedRenewal: + case kLicenseStateWaitingLicenseUpdate: + playback_start_time_ = GetCurrentTime(); + last_playback_time_ = playback_start_time_; + if (policy_.play_start_grace_period_seconds() == 0) + grace_period_end_time_ = playback_start_time_; + + if (policy_.renew_with_usage()) { + license_state_ = kLicenseStateNeedRenewal; + } + NotifyExpirationUpdate(playback_start_time_); + break; + case kLicenseStateInitial: + case kLicenseStatePending: + case kLicenseStateExpired: + NotifyLicenseExpired(license_state_); + break; + default: + break; + } + } +} + +void PolicyEngine::OnTimerEvent() { + last_recorded_current_time_ += kPolicyTimerDurationSeconds; + int64_t current_time = GetCurrentTime(); + + // If we have passed the grace period, the expiration will update. + if (grace_period_end_time_ == 0 && HasPlaybackStarted(current_time)) { + grace_period_end_time_ = playback_start_time_; + NotifyExpirationUpdate(current_time); + } + + // License expiration trumps all. + if (HasLicenseOrPlaybackDurationExpired(current_time) && + license_state_ != kLicenseStateExpired) { + license_state_ = kLicenseStateExpired; + NotifyLicenseExpired(license_state_); + NotifyKeysChange(kKeyStatusExpired); + return; + } + + // Check device conditions that affect playability (HDCP, resolution) + CheckDeviceHdcpStatusOnTimer(current_time); + + bool renewal_needed = false; + + // Test to determine if renewal should be attempted. + switch (license_state_) { + case kLicenseStateCanPlay: { + if (HasRenewalDelayExpired(current_time)) { + renewal_needed = true; + } + // HDCP may change, so force a check. + NotifyKeysChange(kKeyStatusUsable); + break; + } + + case kLicenseStateNeedRenewal: { + renewal_needed = true; + break; + } + + case kLicenseStateWaitingLicenseUpdate: { + if (HasRenewalRetryIntervalExpired(current_time)) { + renewal_needed = true; + } + break; + } + + case kLicenseStatePending: { + if (current_time >= license_start_time_) { + license_state_ = kLicenseStateCanPlay; + NotifyKeysChange(kKeyStatusUsable); + } + break; + } + + case kLicenseStateInitial: + case kLicenseStateExpired: { + NotifyLicenseExpired(license_state_); + break; + } + + default: { + license_state_ = kLicenseStateExpired; + NotifyLicenseExpired(license_state_); + NotifyKeysChange(kKeyStatusInternalError); + break; + } + } + + if (renewal_needed) { + UpdateRenewalRequest(current_time); + if (event_listener_) { + event_listener_->OnSessionRenewalNeeded(); + } + } +} + +bool PolicyEngine::HasRenewalDelayExpired(int64_t current_time) { + return policy_.can_renew() && (policy_.renewal_delay_seconds() > 0) && + license_start_time_ + policy_.renewal_delay_seconds() <= current_time; +} + +bool PolicyEngine::HasRenewalRetryIntervalExpired(int64_t current_time) { + return policy_.can_renew() && + (policy_.renewal_retry_interval_seconds() > 0) && + next_renewal_time_ <= current_time; +} + +void PolicyEngine::UpdateRenewalRequest(int64_t current_time) { + license_state_ = kLicenseStateWaitingLicenseUpdate; + next_renewal_time_ = current_time + policy_.renewal_retry_interval_seconds(); +} + +void PolicyEngine::SetEntitledLicenseKeys( + const std::vector& entitled_keys) { + license_keys_->SetEntitledKeys(entitled_keys); +} + +std::unique_ptr PolicyEngine::CreateLicenseKeys() { + return make_unique(); +} + +std::unique_ptr PolicyEngine::CreateClock() { + return make_unique(); +} + +void PolicyEngine::RestorePlaybackTimes(int64_t playback_start_time, + int64_t last_playback_time, + int64_t grace_period_end_time) { + playback_start_time_ = (playback_start_time > 0) ? playback_start_time : 0; + last_playback_time_ = (last_playback_time > 0) ? last_playback_time : 0; + grace_period_end_time_ = grace_period_end_time; + if (policy_.play_start_grace_period_seconds() != 0) { + // If we are using grace period, we may need to override some of the values + // given to us by OEMCrypto. |grace_period_end_time| will be 0 if the grace + // period has not expired (effectively playback has not begun). Otherwise, + // |grace_period_end_time| contains the playback start time we should use. + playback_start_time_ = grace_period_end_time; + } + const int64_t current_time = GetCurrentTime(); + const int64_t expiry_time = + GetExpiryTime(current_time, + /* ignore_soft_enforce_playback_duration */ true); + was_expired_on_load_ = + expiry_time != NEVER_EXPIRES && expiry_time < current_time; + NotifyExpirationUpdate(current_time); +} + +} // namespace wvcas diff --git a/plugin/src/widevine_cas_api.cpp b/plugin/src/widevine_cas_api.cpp new file mode 100644 index 0000000..6b89e93 --- /dev/null +++ b/plugin/src/widevine_cas_api.cpp @@ -0,0 +1,931 @@ +#include "widevine_cas_api.h" + +#include +#include + +#include + +#include "cas_events.h" +#include "cas_util.h" +#include "license_protocol.pb.h" +#include "log.h" +#include "media_cas.pb.h" +#include "string_conversions.h" +#include "widevine_cas_session_map.h" + +constexpr char kBasePathPrefix[] = "/data/vendor/mediacas/IDM/widevine/"; +constexpr char kCertFileBase[] = "cert.bin"; +constexpr char kLicenseFileNameSuffix[] = ".lic"; + +namespace { +bool ReadFileFromStorage(wvutil::FileSystem& file_system, + const std::string& filename, std::string* file_data) { + if (nullptr == file_data) { + return false; + } + if (!file_system.Exists(filename)) { + return false; + } + size_t filesize = file_system.FileSize(filename); + if (0 == filesize) { + return false; + } + file_data->resize(filesize); + std::unique_ptr file = + file_system.Open(filename, wvutil::FileSystem::kReadOnly); + if (nullptr == file) { + return false; + } + size_t bytes_read = file->Read(&(*file_data)[0], file_data->size()); + if (bytes_read != filesize) { + return false; + } + return true; +} + +bool RemoveFile(wvutil::FileSystem& file_system, const std::string& filename) { + if (!file_system.Exists(filename)) { + return false; + } + if (!file_system.Remove(filename)) { + return false; + } + return true; +} + +bool StoreFile(wvutil::FileSystem& file_system, const std::string& filename, + const std::string& file_data) { + std::unique_ptr file(file_system.Open( + filename, wvutil::FileSystem::kTruncate | wvutil::FileSystem::kCreate)); + if (nullptr == file) { + return false; + } + size_t bytes_written = file->Write(file_data.data(), file_data.size()); + if (bytes_written != file_data.size()) { + return false; + } + return true; +} + +std::string GenerateLicenseFilename(const std::string& content_id, + const std::string& provider_id) { + std::string data(content_id + provider_id); + std::string hash; + hash.resize(SHA256_DIGEST_LENGTH); + const unsigned char* input = + reinterpret_cast(data.data()); + unsigned char* output = reinterpret_cast(&hash[0]); + SHA256(input, data.size(), output); + return std::string(std::string(kBasePathPrefix) + wvutil::b2a_hex(hash) + + std::string(kLicenseFileNameSuffix)); +} + +std::string GenerateMultiContentLicenseInfo( + const std::string& license_id, + const std::vector& content_list) { + std::string message; + if (license_id.empty() || content_list.empty()) { + return message; + } + message.push_back(MultiContentLicenseFieldType::MULTI_CONTENT_LICENSE_ID); + message.push_back((license_id.size() >> 8) & 0xff); + message.push_back(license_id.size() & 0xff); + message.append(license_id); + for (const auto& content_id : content_list) { + message.push_back( + MultiContentLicenseFieldType::MULTI_CONTENT_LICENSE_CONTENT_ID); + message.push_back((content_id.size() >> 8) & 0xff); + message.push_back(content_id.size() & 0xff); + message.append(content_id); + } + return message; +} + +std::string GenerateGroupLicenseInfo(const std::string& license_id, + const std::string group_id) { + std::string message; + if (license_id.empty() || group_id.empty()) { + return message; + } + message.push_back(GroupLicenseFieldType::GROUP_LICENSE_ID); + message.push_back((license_id.size() >> 8) & 0xff); + message.push_back(license_id.size() & 0xff); + message.append(license_id); + + message.push_back(GroupLicenseFieldType::GROUP_LICENSE_GROUP_ID); + message.push_back((group_id.size() >> 8) & 0xff); + message.push_back(group_id.size() & 0xff); + message.append(group_id); + return message; +} + +// Generates a random number between 1 and |range_to|, all inclusive. +uint32_t GetRandom(uint32_t range_to) { + if (range_to <= 1) { + return 1; + } + constexpr uint32_t max_val = std::numeric_limits::max(); + + // Keep searching for a random value in a range divisible by |range_to|. + // Worst case we have 1/2 chance to end the loop on each roll. + uint32_t generated; + do { + RAND_bytes(reinterpret_cast(&generated), /*len=*/4); + } while (generated >= (max_val - (max_val % range_to))); + + return 1 + (generated % range_to); +} + +} // namespace + +namespace wvcas { + +class MediaContext : public CasMediaId { + public: + MediaContext() : CasMediaId() {} + ~MediaContext() override {} + MediaContext(const MediaContext&) = delete; + MediaContext& operator=(const MediaContext&) = delete; + + const std::string content_id() override { return pssh_.content_id(); } + const std::string provider_id() override { return pssh_.provider(); } + bool is_entitlement_rotation_enabled() override { + return pssh_.has_entitlement_period_index(); + } + uint32_t entitlement_period_index() override { + return pssh_.entitlement_period_index(); + } + std::string get_init_data() override { return pssh_.SerializeAsString(); } + + CasStatus initialize(const std::string& init_data) override { + if (!pssh_.ParseFromString(init_data)) { + return CasStatus(CasStatusCode::kInvalidParameter, "invalid init_data"); + } + return CasStatusCode::kNoError; + } + + private: + video_widevine::WidevinePsshData pssh_; +}; + +std::unique_ptr CasMediaId::create() { + std::unique_ptr ctx = make_unique(); + return std::move(ctx); +} + +std::shared_ptr WidevineCas::getCryptoSession() { + return std::make_shared(); +} + +std::unique_ptr WidevineCas::getCasLicense() { + return make_unique(); +} + +std::unique_ptr WidevineCas::getFileSystem() { + return make_unique(); +} + +std::shared_ptr WidevineCas::newCasSession() { + return std::make_shared(); +} + +std::unique_ptr WidevineCas::getEcmParser(const CasEcm& ecm) const { + return EcmParser::Create(ecm); +} + +void WidevineCas::OnTimerEvent() { + std::unique_lock locker(lock_); + if (cas_license_.get() != nullptr) { + cas_license_->OnTimerEvent(); + + // Delete expired license after firing expired event in policy_engine + if (cas_license_->IsExpired() && (media_id_.get() != nullptr)) { + std::string filename = license_id_ + kLicenseFileNameSuffix; + if (!file_system_->Exists(filename)) { + LOGI("No expired license file stored in disk"); + } else { + if (RemoveFile(*file_system_, filename)) { + LOGI("Remove expired license file from disk successfully."); + } + } + } + } +} + +CasStatus WidevineCas::initialize(CasEventListener* event_listener) { + std::unique_lock locker(lock_); + crypto_session_ = getCryptoSession(); + // For session name generation. + srand(time(nullptr)); + // Setup an oemcrypto session. + CasStatus status = crypto_session_->initialize(); + if (!status.ok()) { + LOGE("WidevineCas initialization failed: %d", status.status_code()); + return status; + } + + file_system_ = getFileSystem(); + cas_license_ = getCasLicense(); + status = cas_license_->initialize(crypto_session_, event_listener); + if (!status.ok()) { + LOGE("WidevineCas initialization failed: %d", status.status_code()); + return status; + } + + std::string cert_filename_path(std::string(kBasePathPrefix) + + std::string(kCertFileBase)); + + // Try to read a certificate if one exists. If any error occurs, just ignore + // it and let new cert file overwrite the existing file. + std::string cert_file; + if (ReadFileFromStorage(*file_system_, cert_filename_path, &cert_file)) { + LOGI("read cert.bin successfully"); + if (!HandleStoredDrmCert(cert_file).ok()) { + return CasStatusCode::kNoError; + } + } + + event_listener_ = event_listener; + return CasStatusCode::kNoError; +} + +// TODO(jfore): Split out the functionality and move the callback out of this +// class. +CasStatus WidevineCas::openSession(WvCasSessionId* sessionId) { + std::unique_lock locker(lock_); + if (nullptr == sessionId) { + return CasStatus(CasStatusCode::kInvalidParameter, + "missing openSession sessionId"); + } + + CasSessionPtr session = newCasSession(); + CasStatus status = + session->initialize(crypto_session_, event_listener_, sessionId); + if (CasStatusCode::kNoError != status.status_code()) { + return status; + } + WidevineCasSessionMap::instance().AddSession(*sessionId, session); + return CasStatusCode::kNoError; +} + +CasStatus WidevineCas::closeSession(const WvCasSessionId& sessionId) { + std::unique_lock locker(lock_); + CasSessionPtr session = + WidevineCasSessionMap::instance().GetSession(sessionId); + // TODO(jfore): Add a log event if the session doesn't exist and perhaps raise + // an error.` + if (session == nullptr) { + return CasStatus(CasStatusCode::kSessionNotFound, "unknown session id"); + } + WidevineCasSessionMap::instance().RemoveSession(sessionId); + return CasStatusCode::kNoError; +} + +CasStatus WidevineCas::processEmm(const CasEmm& emm) { + LOGI("WidevineCas::processEmm."); + std::unique_ptr emm_parser = getEmmParser(emm); + if (emm_parser == nullptr) { + return CasStatus(CasStatusCode::kInvalidParameter, "Unable to parse emm"); + } + if (event_listener_ == nullptr) { + LOGW("processEmm: Event listener is not initialized."); + return CasStatusCode::kNoError; + } + + // TODO(b/): Verify signature. + // TODO(b/): Update EMM timer. + + const video_widevine::EmmPayload& emm_payload = emm_parser->emm_payload(); + // Process fingerprinting info. + std::set current_fingerprinting_events_; + for (int i = 0; i < emm_payload.fingerprinting_size(); ++i) { + CasData message = + GenerateFingerprintingEventMessage(emm_payload.fingerprinting(i)); + if (message.empty()) { + continue; + } + if (last_fingerprinting_events_.find(message) == + last_fingerprinting_events_.end()) { + event_listener_->OnFingerprintingUpdated(message); + } + current_fingerprinting_events_.insert(message); + } + last_fingerprinting_events_.clear(); + last_fingerprinting_events_.insert(current_fingerprinting_events_.begin(), + current_fingerprinting_events_.end()); + + // Process service blocking info. + std::set current_service_blocking_events_; + for (int i = 0; i < emm_payload.service_blocking_size(); ++i) { + CasData message = + GenerateServiceBlockingEventMessage(emm_payload.service_blocking(i)); + if (message.empty()) { + continue; + } + if (last_service_blocking_events_.find(message) == + last_service_blocking_events_.end()) { + event_listener_->OnServiceBlockingUpdated(message); + } + current_service_blocking_events_.insert(message); + } + last_service_blocking_events_.clear(); + last_service_blocking_events_.insert(current_service_blocking_events_.begin(), + current_service_blocking_events_.end()); + + return CasStatusCode::kNoError; +} + +// TODO(jfore): Add unit test to widevine_cas_api_test.cpp that is added in +// another cl. +CasStatus WidevineCas::processEcm(const WvCasSessionId& sessionId, + const CasEcm& ecm) { + LOGD("WidevineCasPlugin::processEcm"); + std::unique_lock locker(lock_); + // If we don't have a license yet, save the ecm and session id. + if (!has_license_) { + // In the case of entitlement key rotation enabled, the caller is expected + // to call processEcm first (before processPrivateData), so we know which + // entitlement period index to request when requesting license. + TryExtractEntitlementPeriodIndex(ecm); + deferred_ecms_.emplace(sessionId, ecm); + return CasStatusCode::kDeferedEcmProcessing; + } + return HandleProcessEcm(sessionId, ecm); +} + +CasStatus WidevineCas::HandleProcessEcm(const WvCasSessionId& sessionId, + const CasEcm& ecm) { + if (cas_license_->IsExpired()) { + return CasStatus(CasStatusCode::kCasLicenseError, + "license is expired, unable to process ecm"); + } + CasSessionPtr session = + WidevineCasSessionMap::instance().GetSession(sessionId); + if (session == nullptr) { + return CasStatus(CasStatusCode::kSessionNotFound, + "unknown session for processEcm"); + } + uint8_t ecm_age_previous = session->GetEcmAgeRestriction(); + + CasStatus status = + session->processEcm(ecm, parental_control_age_, license_group_id_); + uint8_t ecm_age_current = session->GetEcmAgeRestriction(); + if (event_listener_ != nullptr && ecm_age_current != ecm_age_previous) { + event_listener_->OnAgeRestrictionUpdated(sessionId, ecm_age_current); + } + + if (media_id_ != nullptr && media_id_->is_entitlement_rotation_enabled()) { + CheckEntitlementPeriodUpdate(session->GetEntitlementPeriodIndex(), + session->GetEntitlementRotationWindowLeft()); + } + + if (status.ok()) { + cas_license_->BeginDecryption(); + } + return status; +} + +CasStatus WidevineCas::HandleDeferredECMs() { + for (const auto& deferred_ecm : deferred_ecms_) { + CasStatus status = + HandleProcessEcm(deferred_ecm.first, deferred_ecm.second); + if (!status.ok()) { + return status; + } + } + deferred_ecms_.clear(); + return CasStatusCode::kNoError; +} + +CasStatus WidevineCas::generateDeviceProvisioningRequest( + std::string* provisioning_request) { + std::unique_lock locker(lock_); + if (provisioning_request == nullptr) { + return CasStatus(CasStatusCode::kInvalidParameter, + "missing output buffer for provisioning request"); + } + return cas_license_->GenerateDeviceProvisioningRequest(provisioning_request); +} + +CasStatus WidevineCas::handleProvisioningResponse(const std::string& response) { + if (response.empty()) { + return CasStatus(CasStatusCode::kCasLicenseError, + "empty individualization response"); + } + std::string device_file; + std::unique_lock locker(lock_); + CasStatus status = cas_license_->HandleDeviceProvisioningResponse( + response, &device_certificate_, &wrapped_rsa_key_, &device_file); + + if (!status.ok()) { + return status; + } + + if (!device_file.empty()) { + std::string cert_filename(std::string(kBasePathPrefix) + + std::string(kCertFileBase)); + StoreFile(*file_system_, cert_filename, device_file); + } + return CasStatusCode::kNoError; +} + +CasStatus WidevineCas::generateEntitlementRequest( + const std::string& init_data, std::string* entitlement_request, + std::string& license_id) { + media_id_ = CasMediaId::create(); + CasStatus status = media_id_->initialize(init_data); + if (!status.ok()) { + return status; + } + + std::string filename; + // Backward compatible. If the license_filename is unrequested by app, plugin + // will directly use the single_content_license named "content_id + + // provider_id" by default. + if (requested_license_id_.empty()) { + filename = GenerateLicenseFilename(media_id_->content_id(), + media_id_->provider_id()); + } else { + filename = requested_license_id_ + kLicenseFileNameSuffix; + // Clean up the assigned_license_filename for next round use. + requested_license_id_.clear(); + } + + // An offline license file is successfully loaded. + if (TryReuseStoredLicense(filename)) { + // If license file is expired, don't proceed the request. Also + // delete the stored license file. + std::unique_lock locker(lock_); + if (cas_license_->IsExpired()) { + if (!RemoveFile(*file_system_, filename)) { + return CasStatus(CasStatusCode::kInvalidLicenseFile, + "unable to remove expired license file from disk"); + } + LOGI("Remove expired license file from disk successfully."); + return CasStatus(CasStatusCode::kCasLicenseError, + "license is expired, unable to process emm"); + } + license_id = + filename.substr(0, filename.size() - strlen(kLicenseFileNameSuffix)); + if (cas_license_->IsGroupLicense()) { + license_group_id_ = cas_license_->GetGroupId(); + } + + // Save current in use license_id. The purpose is to make the license_id + // available for license removal or license expiration. + license_id_ = license_id; + policy_timer_.Start(this, 1); + has_license_ = true; + return HandleDeferredECMs(); + } + + if (entitlement_request == nullptr) { + return CasStatus(CasStatusCode::kInvalidParameter, + "missing output buffer for entitlement request"); + } + std::unique_lock locker(lock_); + return cas_license_->GenerateEntitlementRequest( + init_data, device_certificate_, wrapped_rsa_key_, license_type_, + entitlement_request); +} + +CasStatus WidevineCas::handleEntitlementResponse( + const std::string& response, std::string& license_id, + std::string& multi_content_license_info, std::string& group_license_info) { + if (response.empty()) { + return CasStatus(CasStatusCode::kCasLicenseError, + "empty entitlement response"); + } + if (media_id_ == nullptr) { + return CasStatus(CasStatusCode::kCasLicenseError, "No media id"); + } + + std::string device_file; + std::unique_lock locker(lock_); + CasStatus status = + cas_license_->HandleEntitlementResponse(response, &device_file); + if (status.ok()) { + // A license has been successfully loaded. Load any ecms that may have been + // deferred waiting for the license. + if (cas_license_->IsGroupLicense()) { + license_group_id_ = cas_license_->GetGroupId(); + } + has_license_ = true; + + status = HandleDeferredECMs(); + if (!status.ok()) { + return status; + } + + policy_timer_.Start(this, 1); + + if (!device_file.empty()) { + const std::string license_group_id = cas_license_->GetGroupId(); + std::string filename = GenerateLicenseFilename( + license_group_id.empty() ? media_id_->content_id() : license_group_id, + media_id_->provider_id()); + StoreFile(*file_system_, filename, device_file); + // license_id will be the filename without ".lic" extension. + license_id = + filename.substr(0, filename.size() - strlen(kLicenseFileNameSuffix)); + // Save the license id. + license_id_ = license_id; + + // License info is only needed if the license is stored. + if (cas_license_->IsMultiContentLicense()) { + multi_content_license_info = GenerateMultiContentLicenseInfo( + license_id, cas_license_->GetContentIdList()); + } + if (cas_license_->IsGroupLicense()) { + group_license_info = + GenerateGroupLicenseInfo(license_id, license_group_id); + } + } + } + return status; +} + +CasStatus WidevineCas::generateEntitlementRenewalRequest( + std::string* entitlement_renewal_request) { + if (entitlement_renewal_request == nullptr) { + return CasStatus(CasStatusCode::kInvalidParameter, + "missing output buffer for entitlement renewal request"); + } + return cas_license_->GenerateEntitlementRenewalRequest( + device_certificate_, entitlement_renewal_request); +} + +CasStatus WidevineCas::handleEntitlementRenewalResponse( + const std::string& response, std::string& license_id) { + if (response.empty()) { + return CasStatus(CasStatusCode::kCasLicenseError, + "empty entitlement renewal response"); + } + std::string device_file; + std::unique_lock locker(lock_); + CasStatus status = + cas_license_->HandleEntitlementRenewalResponse(response, &device_file); + if (!status.ok()) { + return status; + } + if (!device_file.empty() && media_id_ != nullptr) { + const std::string license_group_id = cas_license_->GetGroupId(); + std::string filename = GenerateLicenseFilename( + license_group_id.empty() ? media_id_->content_id() : license_group_id, + media_id_->provider_id()); + StoreFile(*file_system_, filename, device_file); + // TODO(chelu): The license id should not change, right? + // license_id will be the filename without ".lic" extension. + license_id = + filename.substr(0, filename.size() - strlen(kLicenseFileNameSuffix)); + license_id_ = license_id; + } + return CasStatusCode::kNoError; +} + +CasStatus WidevineCas::generateEntitlementPeriodUpdateRequest( + const std::string& init_data) { + std::unique_lock locker(lock_); + next_media_id_ = CasMediaId::create(); + CasStatus status = next_media_id_->initialize(init_data); + if (!status.ok()) { + return status; + } + + // Setup a new OEMCrypto session. + next_crypto_session_ = getCryptoSession(); + status = next_crypto_session_->initialize(); + if (!status.ok()) { + LOGE("WidevineCas new oemcrypto session failed: %d", status.status_code()); + return status; + } + // Setup a new CasLicense. + next_cas_license_ = getCasLicense(); + status = next_cas_license_->initialize(next_crypto_session_, event_listener_); + if (!status.ok()) { + LOGE("WidevineCas new license initialize failed: %d", status.status_code()); + return status; + } + + std::string entitlement_request; + status = next_cas_license_->GenerateEntitlementRequest( + init_data, device_certificate_, wrapped_rsa_key_, license_type_, + &entitlement_request); + if (!status.ok()) { + LOGE("WidevineCas generate entitlement request failed: %d", + status.status_code()); + return status; + } + + if (event_listener_ == nullptr) { + LOGE("No event listener"); + return CasStatus(CasStatusCode::kUnknownError, "No event listener"); + } + event_listener_->OnEntitlementPeriodUpdateNeeded(entitlement_request); + return CasStatusCode::kNoError; +} + +CasStatus WidevineCas::handleEntitlementPeriodUpdateResponse( + const std::string& response, std::string& license_id) { + std::unique_lock locker(lock_); + if (next_media_id_ == nullptr || next_crypto_session_ == nullptr || + next_cas_license_ == nullptr) { + return CasStatus(CasStatusCode::kInvalidParameter, + "Must generate entitlement switch request first."); + } + // Install the new license. + std::string device_file; + CasStatus status = + next_cas_license_->HandleEntitlementResponse(response, &device_file); + if (!status.ok()) { + LOGE("WidevineCas install new license failed: %d", status.status_code()); + return status; + } + // License has been successfully installed. Switch to use it across all + // sessions. + for (const auto& session : + WidevineCasSessionMap::instance().GetAllSessions()) { + status = session->resetCryptoSession(next_crypto_session_); + if (!status.ok()) { + // Some of the sessions may have already been reassociated (unlikely to + // happen). Here we continue process ignoring the errors. Some sessions + // will become unusable. + LOGE("resetCryptoSession failed, error %d: %s", status.status_code(), + status.error_string().c_str()); + } + } + // Close the current OEMCrypto session. + crypto_session_->close(); + // Apply the new crypto session and cas license. + crypto_session_ = std::move(next_crypto_session_); + cas_license_ = std::move(next_cas_license_); + media_id_ = std::move(next_media_id_); + + // Store offline license. + if (!device_file.empty()) { + std::string filename = GenerateLicenseFilename(media_id_->content_id(), + media_id_->provider_id()); + StoreFile(*file_system_, filename, device_file); + // license_id will be the filename without ".lic" extension. + license_id = + filename.substr(0, filename.size() - strlen(kLicenseFileNameSuffix)); + } + return status; +} + +CasStatus WidevineCas::RemoveLicense(const std::string& file_name) { + // Check if the license is in use. If it is, besides removing the license, + // update policy in current license. Else, we just directly remove it. + if (media_id_ == nullptr) { + return CasStatus(CasStatusCode::kCasLicenseError, "No media id"); + } + // Remove the license file given the file_name user provides. + if (!RemoveFile(*file_system_, file_name)) { + return CasStatus(CasStatusCode::kInvalidLicenseFile, + "unable to remove license file from disk"); + } + LOGI("Remove license file from disk successfully."); + + std::string current_license_filename = license_id_ + kLicenseFileNameSuffix; + if (file_name == current_license_filename) { + // Update license policy for the in-used license. Plugin will not allowed to + // play stream, store and renew license unless a new plugin instance is + // created. + std::unique_lock locker(lock_); + cas_license_->UpdateLicenseForLicenseRemove(); + } + + return CasStatusCode::kNoError; +} + +bool WidevineCas::is_provisioned() const { + return (!(device_certificate_.empty() || wrapped_rsa_key_.empty())); +} + +CasStatus WidevineCas::ProcessCAPrivateData(const CasData& private_data, + std::string* init_data) { + if (init_data == nullptr) { + return CasStatus(CasStatusCode::kInvalidParameter, + "missing output buffer for init_data"); + } + // Parse provider and content id from CA descriptor. + video_widevine::CaDescriptorPrivateData descriptor; + descriptor.ParseFromArray(private_data.data(), private_data.size()); + if (!descriptor.has_content_id() || !descriptor.has_provider()) { + return CasStatus(CasStatusCode::kInvalidParameter, + "unable to parse private data"); + } + + // Build PSSH of type ENTITLEMENT. + video_widevine::WidevinePsshData pssh; + pssh.set_provider(descriptor.provider()); + pssh.set_content_id(descriptor.content_id()); + for (int i = 0; i < descriptor.group_ids_size(); ++i) { + pssh.add_group_ids(descriptor.group_ids(i)); + } + pssh.set_type(video_widevine::WidevinePsshData::ENTITLEMENT); + if (is_entitlement_rotation_enabled_) { + pssh.set_entitlement_period_index(entitlement_period_index_); + } + pssh.SerializeToString(init_data); + return CasStatusCode::kNoError; +} + +CasStatus WidevineCas::ProcessSessionCAPrivateData( + const WvCasSessionId& session_id, const CasData& private_data, + std::string* init_data) { + if (!WidevineCasSessionMap::instance().GetSession(session_id)) { + return CasStatus(CasStatusCode::kCasLicenseError, "invalid session id"); + } + return ProcessCAPrivateData(private_data, init_data); +} + +CasStatus WidevineCas::GetUniqueID(std::string* buffer) { + return crypto_session_->GetDeviceID(buffer); +} + +CasStatus WidevineCas::HandleStoredDrmCert(const std::string& certificate) { + if (certificate.empty()) { + return CasStatus(CasStatusCode::kCasLicenseError, "empty certificate data"); + } + CasStatus status = cas_license_->HandleStoredDrmCert( + certificate, &device_certificate_, &wrapped_rsa_key_); + return status; +} + +CasStatus WidevineCas::HandleSetParentalControlAge(const CasData& data) { + if (data.empty()) { + return CasStatus(CasStatusCode::kCasLicenseError, + "missing value of parental control min age"); + } + parental_control_age_ = data[0]; + LOGI("Parental control age set to: %d", parental_control_age_); + return CasStatusCode::kNoError; +} + +CasStatus WidevineCas::RecordLicenseId(const std::string& license_id) { + requested_license_id_ = license_id; + LOGI("License id selected is: %s", requested_license_id_.c_str()); + return CasStatusCode::kNoError; +} + +std::vector WidevineCas::GenerateFingerprintingEventMessage( + const video_widevine::Fingerprinting& fingerprinting) const { + std::vector message; + for (int i = 0; i < fingerprinting.channels_size(); ++i) { + const std::string& channel = fingerprinting.channels(i); + message.push_back( + static_cast(FingerprintingFieldType::FINGERPRINTING_CHANNEL)); + message.push_back((channel.size() >> 8) & 0xff); + message.push_back(channel.size() & 0xff); + message.insert(message.end(), channel.begin(), channel.end()); + } + + if (fingerprinting.has_control()) { + message.push_back( + static_cast(FingerprintingFieldType::FINGERPRINTING_CONTROL)); + const std::string& control = fingerprinting.control(); + message.push_back((control.size() >> 8) & 0xff); + message.push_back(control.size() & 0xff); + message.insert(message.end(), control.begin(), control.end()); + } + + return message; +} + +std::vector WidevineCas::GenerateServiceBlockingEventMessage( + const video_widevine::ServiceBlocking& service_blocking) const { + std::vector message; + // Process service blocking channels. + for (int i = 0; i < service_blocking.channels_size(); ++i) { + const std::string& channel = service_blocking.channels(i); + message.push_back(static_cast( + ServiceBlockingFieldType::SERVICE_BLOCKING_CHANNEL)); + message.push_back((channel.size() >> 8) & 0xff); + message.push_back(channel.size() & 0xff); + message.insert(message.end(), channel.begin(), channel.end()); + } + + // Process service blocking device_groups. + for (int i = 0; i < service_blocking.device_groups_size(); ++i) { + const std::string& device_group = service_blocking.device_groups(i); + message.push_back(static_cast( + ServiceBlockingFieldType::SERVICE_BLOCKING_DEVICE_GROUP)); + message.push_back((device_group.size() >> 8) & 0xff); + message.push_back(device_group.size() & 0xff); + message.insert(message.end(), device_group.begin(), device_group.end()); + } + + // Process service blocking start_time_sec. + if (service_blocking.has_start_time_sec()) { + message.push_back(static_cast( + ServiceBlockingFieldType::SERVICE_BLOCKING_START_TIME_SECONDS)); + // Timestamp is always 8 bytes (64 bits). + message.push_back(0); + message.push_back(8); + for (int i = 0; i < 8; ++i) { + message.push_back((service_blocking.start_time_sec() >> (8 * (7 - i))) & + 0xff); + } + } + + // Process service blocking end_time_sec. + if (service_blocking.has_end_time_sec()) { + message.push_back(static_cast( + ServiceBlockingFieldType::SERVICE_BLOCKING_END_TIME_SECONDS)); + // Timestamp is always 8 bytes (64 bits). + message.push_back(0); + message.push_back(8); + for (int i = 0; i < 8; ++i) { + message.push_back((service_blocking.end_time_sec() >> (8 * (7 - i))) & + 0xff); + } + } + + return message; +} + +std::unique_ptr WidevineCas::getEmmParser( + const CasEmm& emm) const { + return EmmParser::Create(emm); +} + +void WidevineCas::TryExtractEntitlementPeriodIndex(const CasEcm& ecm) { + std::unique_ptr ecm_parser = getEcmParser(ecm); + if (ecm_parser == nullptr) { + LOGE("ECM parser failed for extracting entitlement period index"); + return; + } + if (ecm_parser->is_entitlement_rotation_enabled()) { + is_entitlement_rotation_enabled_ = true; + entitlement_period_index_ = ecm_parser->entitlement_period_index(); + LOGI("Entitlement key rotation enabled. Current index: %d", + entitlement_period_index_); + } +} + +bool WidevineCas::TryReuseStoredLicense(const std::string& filename) { + // Read the file with |filename| from the file system. + std::string license_file; + if (!ReadFileFromStorage(*file_system_, filename, &license_file)) { + return false; + } + + // If entitlement rotation is enabled, check if the entitlement period in the + // license is outdated. + if (media_id_->is_entitlement_rotation_enabled()) { + uint32_t stored_index; + CasStatus status = CasLicense::GetEntitlementPeriodIndexFromStoredLicense( + license_file, stored_index); + if (!status.ok()) { + LOGW( + "Failed to retrieve entitlement period index from stored license. " + "code: %d, message: %s", + status.status_code(), status.error_string().c_str()); + return false; + } + + if (media_id_->entitlement_period_index() != stored_index) { + LOGI("Stored license has mismatch entitlement period index."); + return false; + } + } + + // Load the stored license to the session. + CasStatus status = + cas_license_->HandleStoredLicense(wrapped_rsa_key_, license_file); + if (!status.ok()) { + LOGW("Failed to load stored license. code: %d, message: %s", + status.status_code(), status.error_string().c_str()); + return false; + } + return true; +} + +void WidevineCas::CheckEntitlementPeriodUpdate(uint32_t period_index, + uint32_t window_left) { + if (period_index == media_id_->entitlement_period_index()) { + return; + } + // If the index changed unexpectedly, we request a new license immediately. If + // it is increased by 1, we decide if a new license should be generated based + // on |window_left|. + if (period_index != media_id_->entitlement_period_index() + 1 || + GetRandom(window_left) == 1) { + video_widevine::WidevinePsshData pssh; + if (!pssh.ParseFromString(media_id_->get_init_data())) { + LOGE("Cannot parse init data"); + return; + } + pssh.set_entitlement_period_index(period_index); + generateEntitlementPeriodUpdateRequest(pssh.SerializeAsString()); + } +} + +void WidevineCas::StopTimer() { + if (policy_timer_.IsRunning()) { + policy_timer_.Stop(); + } +} + +} // namespace wvcas diff --git a/plugin/src/widevine_cas_session.cpp b/plugin/src/widevine_cas_session.cpp new file mode 100644 index 0000000..421ae4a --- /dev/null +++ b/plugin/src/widevine_cas_session.cpp @@ -0,0 +1,202 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. +#include "widevine_cas_session.h" + +#include + +#include +#include + +#include "log.h" +#include "media_cas.pb.h" + +namespace wvcas { + +KeySlot& CasKeySlotData::operator[](KeySlotId slot_id) { + return keys_[static_cast(slot_id)]; +} + +const KeySlot& CasKeySlotData::operator[](KeySlotId slot_id) const { + return keys_[static_cast(slot_id)]; +} + +WidevineCasSession::~WidevineCasSession() { + if (crypto_session_ != nullptr) { + crypto_session_->RemoveEntitledKeySession(key_session_id_); + } +} + +CasStatus WidevineCasSession::initialize( + std::shared_ptr crypto_session, + CasEventListener* event_listener, WvCasSessionId* session_id) { + std::unique_lock lock(crypto_lock_); + if (crypto_session == nullptr) { + LOGE( + "WidevineCasSession::initialize: missing input parameter " + "crypto_session"); + return CasStatus(CasStatusCode::kInvalidParameter, + "missing input parameter crypto_session"); + } + crypto_session_ = std::move(crypto_session); + CasStatus status = + crypto_session_->CreateEntitledKeySession(&key_session_id_); + if (!status.ok()) { + return status; + } + + status = crypto_session_->GetOEMKeyToken(key_session_id_, + external_key_session_id_); + if (!status.ok()) { + return status; + } + session_id->assign(external_key_session_id_.begin(), + external_key_session_id_.end()); + + event_listener_ = event_listener; + return CasStatusCode::kNoError; +} + +CasStatus WidevineCasSession::resetCryptoSession( + std::shared_ptr crypto_session) { + std::unique_lock lock(crypto_lock_); + if (crypto_session == nullptr) { + return CasStatus(CasStatusCode::kInvalidParameter, + "Can not reset crypto session to null"); + } + crypto_session_ = std::move(crypto_session); + return crypto_session_->ReassociateEntitledKeySession(key_session_id_); +} + +CasStatus WidevineCasSession::processEcm(const CasEcm& ecm, + uint8_t parental_control_age, + const std::string& license_group_id) { + std::unique_lock lock(crypto_lock_); + if (ecm != current_ecm_) { + LOGD("WidevineCasSession::processEcm: received new ecm"); + std::unique_ptr ecm_parser = getEcmParser(ecm); + if (ecm_parser == nullptr) { + return CasStatus(CasStatusCode::kInvalidParameter, "invalid ecm"); + } + if (!license_group_id.empty() && + !ecm_parser->set_group_id(license_group_id)) { + return CasStatus(CasStatusCode::kInvalidParameter, "invalid group id"); + } + + ecm_age_restriction_ = ecm_parser->age_restriction(); + // Parental control check. + if (parental_control_age > 0 && + parental_control_age < ecm_age_restriction_) { + const std::string message(1, parental_control_age); + return CasStatus(CasStatusCode::kAccessDeniedByParentalControl, message); + } + + std::vector message; + if (!ecm_parser->fingerprinting().control().empty()) { + message.push_back(static_cast( + SessionFingerprintingFieldType::SESSION_FINGERPRINTING_CONTROL)); + const std::string control = ecm_parser->fingerprinting().control(); + message.push_back((control.size() >> 8) & 0xff); + message.push_back(control.size() & 0xff); + message.insert(message.end(), control.begin(), control.end()); + } + if (message != last_fingerprinting_message_) { + last_fingerprinting_message_ = message; + if (event_listener_ == nullptr) { + LOGW("event_listener is null. Fingerprinting info ignored!"); + } else { + event_listener_->OnSessionFingerprintingUpdated( + external_key_session_id_, message); + } + } + + message.clear(); + for (int i = 0; i < ecm_parser->service_blocking().device_groups_size(); + ++i) { + message.push_back( + static_cast(SessionServiceBlockingFieldType:: + SESSION_SERVICE_BLOCKING_DEVICE_GROUP)); + const std::string device_group = + ecm_parser->service_blocking().device_groups(i); + message.push_back((device_group.size() >> 8) & 0xff); + message.push_back(device_group.size() & 0xff); + message.insert(message.end(), device_group.begin(), device_group.end()); + } + if (message != last_service_blocking_message_) { + last_service_blocking_message_ = message; + if (event_listener_ == nullptr) { + LOGW("event_listener is null. Service blocking info ignored!"); + } else { + event_listener_->OnSessionServiceBlockingUpdated( + external_key_session_id_, message); + } + } + + entitlement_period_index_ = ecm_parser->entitlement_period_index(); + entitlement_rotation_window_left_ = + ecm_parser->entitlement_rotation_window_left(); + + bool load_even = false; + bool load_odd = false; + KeySlotId keyslot_id = KeySlotId::kEvenKeySlot; + // Temporary key slots to only have successfully loaded keys in |keys_|. + CasKeySlotData keys; + do { + if (keys_[keyslot_id].wrapped_key != + ecm_parser->wrapped_key_data(keyslot_id)) { + KeySlot& key = keys[keyslot_id]; + key.key_id = ecm_parser->content_key_id(keyslot_id); + key.wrapped_key = ecm_parser->wrapped_key_data(keyslot_id); + key.wrapped_key_iv = ecm_parser->wrapped_key_iv(keyslot_id); + key.entitlement_key_id = ecm_parser->entitlement_key_id(keyslot_id); + key.cipher_mode = ecm_parser->crypto_mode(); + key.content_iv = ecm_parser->content_iv(keyslot_id); + if (keyslot_id == KeySlotId::kEvenKeySlot) { + load_even = true; + } else { + load_odd = true; + } + if (key.content_iv.size() == 8) { + key.content_iv.resize(16, 0); + } + } + if (!ecm_parser->rotation_enabled() || + keyslot_id == KeySlotId::kOddKeySlot) { + break; + } + keyslot_id = KeySlotId::kOddKeySlot; + } while (true); + if (load_even || load_odd) { + CasStatus status = crypto_session_->LoadCasECMKeys( + key_session_id_, + (load_even ? &keys[KeySlotId::kEvenKeySlot] : nullptr), + (load_odd ? &keys[KeySlotId::kOddKeySlot] : nullptr)); + if (status.status_code() != CasStatusCode::kNoError) { + LOGE("WidevineCasSession::processEcm: error %d, msg %s", + status.status_code(), status.error_string().c_str()); + return status; + } + + // Don't update on failure, to not to lose still working key_id. + if (load_even) { + keys_[KeySlotId::kEvenKeySlot] = keys[KeySlotId::kEvenKeySlot]; + } + if (load_odd) { + keys_[KeySlotId::kOddKeySlot] = keys[KeySlotId::kOddKeySlot]; + } + } + current_ecm_ = ecm; + } + return CasStatusCode::kNoError; +} + +std::unique_ptr WidevineCasSession::getEcmParser( + const CasEcm& ecm) const { + return EcmParser::Create(ecm); +} + +const char* WidevineCasSession::securityLevel() { + return crypto_session_->SecurityLevel(); +} + +} // namespace wvcas diff --git a/plugin/src/widevine_cas_session_map.cpp b/plugin/src/widevine_cas_session_map.cpp new file mode 100644 index 0000000..9faf6ee --- /dev/null +++ b/plugin/src/widevine_cas_session_map.cpp @@ -0,0 +1,47 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#include "widevine_cas_session_map.h" + +namespace wvcas { + +WidevineCasSessionMap& WidevineCasSessionMap::instance() { + static WidevineCasSessionMap the_cas_session_map; + return the_cas_session_map; +} + +bool WidevineCasSessionMap::AddSession(const WvCasSessionId& cas_session_id, + CasSessionPtr session) { + std::unique_lock lock(lock_); + std::pair entry = + map_.emplace(cas_session_id, session); + return entry.second; +} + +CasSessionPtr WidevineCasSessionMap::GetSession( + const WvCasSessionId& cas_session_id) const { + std::unique_lock lock(lock_); + CasSessionMap::const_iterator it = map_.find(cas_session_id); + if (it == map_.end()) { + return CasSessionPtr(nullptr); + } + return it->second; +} + +void WidevineCasSessionMap::RemoveSession( + const WvCasSessionId& cas_session_id) { + std::unique_lock lock(lock_); + map_.erase(cas_session_id); +} + +std::vector WidevineCasSessionMap::GetAllSessions() const { + std::unique_lock lock(lock_); + std::vector sessions; + for (const auto& session : map_) { + sessions.push_back(session.second); + } + return sessions; +} + +} // namespace wvcas diff --git a/plugin/src/widevine_media_cas.cpp b/plugin/src/widevine_media_cas.cpp new file mode 100644 index 0000000..5c63339 --- /dev/null +++ b/plugin/src/widevine_media_cas.cpp @@ -0,0 +1,95 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#include "widevine_media_cas.h" + +#include + +#include "cas_util.h" +#include "crypto_session.h" +#include "utils/Errors.h" +#include "widevine_media_cas_plugin.h" + +using android::BAD_VALUE; +using android::OK; + +// Widevine Technologies CA system ID. +static constexpr int32_t kWidevineCAID = 0x4AD4; +// New Widevine CAS IDs 0x56C0 to 0x56C9 (all inclusive). +static constexpr int32_t kWidevineNewCasIdLowerBound = 0x56C0; +static constexpr int32_t kWidevineNewCasIdUpperBound = 0x56C9; +// Total number of supported Widevine CAS ids. +static constexpr size_t kWidevineCasIdCount = 11; +static constexpr char kName[] = "WidevineCas"; + +// Implements extern android::CasFactory *createCasFactory() entry point. +CasFactory* createCasFactory() { + return wvcas::WidevineCasFactory::createCasFactory(); +} + +namespace wvcas { + +/************************ Cas factory implementation *********************/ + +// The widevine cas implementation of the cas plugin factory. +WidevineCasFactory* WidevineCasFactory::createCasFactory() { + return new WidevineCasFactory(); +} + +bool WidevineCasFactory::isSystemIdSupported(int32_t CA_system_id) const { + return (CA_system_id == kWidevineCAID) || + (CA_system_id >= kWidevineNewCasIdLowerBound && + CA_system_id <= kWidevineNewCasIdUpperBound); +} + +status_t WidevineCasFactory::queryPlugins( + std::vector* descriptors) const { + if (nullptr == descriptors) { + return BAD_VALUE; + } + descriptors->clear(); + descriptors->reserve(kWidevineCasIdCount); + descriptors->push_back({kWidevineCAID, String8(kName)}); + for (int32_t new_id = kWidevineNewCasIdLowerBound; + new_id <= kWidevineNewCasIdUpperBound; ++new_id) { + descriptors->push_back({new_id, String8(kName)}); + } + return OK; +} + +status_t WidevineCasFactory::createPlugin(int32_t CA_system_id, void* appData, + CasPluginCallback callback, + CasPlugin** plugin) { + if (nullptr == plugin || !isSystemIdSupported(CA_system_id)) { + return BAD_VALUE; + } + + std::unique_ptr new_plugin = + make_unique(appData, callback); + status_t status = new_plugin->initialize(); + if (status != OK) { + return status; + } + *plugin = new_plugin.release(); + return OK; +} + +status_t WidevineCasFactory::createPlugin(int32_t CA_system_id, void* appData, + CasPluginCallbackExt callback, + CasPlugin** plugin) { + if (nullptr == plugin || !isSystemIdSupported(CA_system_id)) { + return BAD_VALUE; + } + + std::unique_ptr new_plugin = + make_unique(appData, callback); + status_t status = new_plugin->initialize(); + if (status != OK) { + return status; + } + *plugin = new_plugin.release(); + return OK; +} + +} // namespace wvcas diff --git a/plugin/src/widevine_media_cas_plugin.cpp b/plugin/src/widevine_media_cas_plugin.cpp new file mode 100644 index 0000000..863ec5a --- /dev/null +++ b/plugin/src/widevine_media_cas_plugin.cpp @@ -0,0 +1,600 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#include "widevine_media_cas_plugin.h" + +#include +#include +#include + +#include +#include + +#include "cas_events.h" +#include "cas_properties.h" +#include "cas_status.h" +#include "cas_types.h" +#include "cas_util.h" +#include "log.h" +#include "media/stagefright/MediaErrors.h" + +using android::BAD_VALUE; +using android::INVALID_OPERATION; +using android::OK; + +namespace wvcas { + +WidevineCasPlugin::WidevineCasPlugin(void* appData, CasPluginCallback callback) + : app_data_(appData), + callback_(callback), + callback_ext_(nullptr), + widevine_cas_api_(make_unique()) {} + +WidevineCasPlugin::WidevineCasPlugin(void* appData, + CasPluginCallbackExt callback) + : app_data_(appData), + callback_(nullptr), + callback_ext_(callback), + widevine_cas_api_(make_unique()) {} + +WidevineCasPlugin::~WidevineCasPlugin() { widevine_cas_api_->StopTimer(); } + +status_t WidevineCasPlugin::initialize() { + CasStatus status = widevine_cas_api_->initialize(this); + if (!status.ok()) { + return android::ERROR_CAS_UNKNOWN; + } + + std::string version; + if (Properties::GetWvCasPluginVersion(version)) { + LOGI("Widevine CAS plugin version: %s", version.c_str()); + } else { + LOGW("Failed to get Widevine CAS plugin version."); + } + + return OK; +} + +bool WidevineCasPlugin::is_provisioned() const { + return widevine_cas_api_->is_provisioned(); +} + +status_t WidevineCasPlugin::setStatusCallback( + CasPluginStatusCallback /*callback*/) { + // TODO(chelu): support status callback. + return OK; +} + +status_t WidevineCasPlugin::setPrivateData(const CasData& privateData) { + // Can get PSSH from multiple streams and from provision call. + // Only need to request a license once. + if (is_emm_request_sent_ || privateData.empty()) { + return OK; + } + CasStatus status = + widevine_cas_api_->ProcessCAPrivateData(privateData, &init_data_); + if (!status.ok()) { + return android::ERROR_CAS_UNKNOWN; + } + if (widevine_cas_api_->is_provisioned()) { + return requestLicense(init_data_); + } + return OK; +} + +status_t WidevineCasPlugin::openSession(CasSessionId* sessionId) { + if (nullptr == sessionId) { + return BAD_VALUE; + } + if (!is_provisioned()) { + LOGE("Sessions can only be opened after privisioned."); + return android::ERROR_CAS_NOT_PROVISIONED; + } + + WvCasSessionId new_session_id; + CasStatus status = widevine_cas_api_->openSession(&new_session_id); + if (!status.ok()) { + return android::ERROR_CAS_SESSION_NOT_OPENED; + } + sessionId->assign(new_session_id.begin(), new_session_id.end()); + + // This is the backward compatible 4 bytes session_id. + int32_t session_id_int32 = 0; + if (new_session_id.size() == 4) { + // CasSessionId is expected to be in little endian order. + for (int i = new_session_id.size() - 1; i >= 0; --i) { + session_id_int32 = (session_id_int32 << 8) | new_session_id[i]; + } + } + + // Not a session event, so CasSessionId in callback is null. + CallBack(app_data_, CAS_SESSION_ID, session_id_int32, sessionId->data(), + sessionId->size(), nullptr); + return OK; +} + +status_t WidevineCasPlugin::openSession(uint32_t intent, uint32_t mode, + CasSessionId* sessionId) { + // TODO(chelu): return error on unsupported intent/mode. + return openSession(sessionId); +} + +status_t WidevineCasPlugin::closeSession(const CasSessionId& sessionId) { + CasStatus status = widevine_cas_api_->closeSession(sessionId); + if (!status.ok()) { + return android::ERROR_CAS_SESSION_NOT_OPENED; + } + return OK; +} + +status_t WidevineCasPlugin::setSessionPrivateData(const CasSessionId& sessionId, + const CasData& privateData) { + // Can get PSSH from multiple streams and from provision call. + // Only need to request a license once. + if (is_emm_request_sent_ || privateData.empty()) { + return OK; + } + + // Doesn't matter which session, CA descriptor applies to all of them. + CasStatus status = widevine_cas_api_->ProcessSessionCAPrivateData( + sessionId, privateData, &init_data_); + if (!status.ok()) { + return android::ERROR_CAS_SESSION_NOT_OPENED; + } + if (widevine_cas_api_->is_provisioned()) { + return requestLicense(init_data_); + } + return OK; +} + +status_t WidevineCasPlugin::processEcm(const CasSessionId& sessionId, + const CasEcm& ecm) { + LOGI("WidevineCasPlugin::processEcm"); + CasStatus status = widevine_cas_api_->processEcm(sessionId, ecm); + if (!status.ok()) { + CasData error(status.error_string().begin(), status.error_string().end()); + switch (status.status_code()) { + case CasStatusCode::kDeferedEcmProcessing: + return android::ERROR_CAS_DECRYPT_UNIT_NOT_INITIALIZED; + case CasStatusCode::kAccessDeniedByParentalControl: + CallBack(reinterpret_cast(app_data_), + ACCESS_DENIED_BY_PARENTAL_CONTROL, /*arg=*/0, &error[0], + error.size(), &sessionId); + return android::ERROR_CAS_DECRYPT; + default: + CallBack(reinterpret_cast(app_data_), CAS_ERROR, + static_cast(status.status_code()), &error[0], + error.size(), &sessionId); + // TODO(jfore): Can this error value be more specific? Is it because we + // don't have a license? Or the key ids don't match? Or sunspots? + return android::ERROR_CAS_UNKNOWN; + } + } + return OK; +} + +status_t WidevineCasPlugin::processEmm(const CasEmm& emm) { + LOGI("WidevineCasPlugin::processEmm"); + if (!widevine_cas_api_->processEmm(emm).ok()) { + return android::ERROR_CAS_UNKNOWN; + } + return OK; +} + +status_t WidevineCasPlugin::sendEvent(int32_t event, int32_t arg, + const CasData& eventData) { + CasStatus status = processEvent(event, arg, eventData, /*sessionId=*/nullptr); + if (status.status_code() != CasStatusCode::kNoError) { + CasData error(status.error_string().begin(), status.error_string().end()); + CallBack(reinterpret_cast(app_data_), CAS_ERROR, + static_cast(status.status_code()), &error[0], + error.size(), nullptr); + return android::ERROR_CAS_UNKNOWN; + } + return OK; +} + +status_t WidevineCasPlugin::sendSessionEvent(const CasSessionId& sessionId, + int32_t event, int32_t arg, + const CasData& eventData) { + CasStatus status = processEvent(event, arg, eventData, &sessionId); + if (status.status_code() != CasStatusCode::kNoError) { + CasData error(status.error_string().begin(), status.error_string().end()); + CallBack(reinterpret_cast(app_data_), CAS_ERROR, + static_cast(status.status_code()), &error[0], + error.size(), &sessionId); + return android::ERROR_CAS_UNKNOWN; + } + return OK; +} + +status_t WidevineCasPlugin::provision(const String8& provisionString) { + // Store |provisionString| for future use. If |provisionString| is not empty + // the value takes priority over data in CA descriptor. + init_data_ = std::string(provisionString.c_str(), + provisionString.c_str() + provisionString.length()); + + if (is_provisioned()) { + CallBack(reinterpret_cast(app_data_), INDIVIDUALIZATION_COMPLETE, 0, + nullptr, 0, nullptr); + if (!init_data_.empty()) { + return requestLicense(init_data_); + } + return OK; + } + + std::string provisioning_request; + CasStatus status = widevine_cas_api_->generateDeviceProvisioningRequest( + &provisioning_request); + if (!status.ok()) { + return INVALID_OPERATION; + } + + if (!provisioning_request.empty()) { + std::vector callback_data(provisioning_request.begin(), + provisioning_request.end()); + CallBack(reinterpret_cast(app_data_), INDIVIDUALIZATION_REQUEST, 0, + callback_data.data(), callback_data.size(), nullptr); + } + + return OK; +} + +status_t WidevineCasPlugin::requestLicense(const std::string& init_data) { + std::string signed_license_request; + std::string license_id; + CasStatus status = widevine_cas_api_->generateEntitlementRequest( + init_data, &signed_license_request, license_id); + if (!status.ok()) { + LOGE("WidevineCas generate entitlement request failed: %d", + status.status_code()); + return INVALID_OPERATION; + } + + // If populated we need to send |signed_license_request| to the server. + // Otherwise signal license complete. + if (!signed_license_request.empty()) { + CallBack(reinterpret_cast(app_data_), LICENSE_REQUEST, + LICENSE_REQUEST, + reinterpret_cast(&signed_license_request[0]), + signed_license_request.size(), nullptr); + } else { + CallBack(reinterpret_cast(app_data_), LICENSE_CAS_READY, + LICENSE_CAS_READY, reinterpret_cast(&license_id[0]), + license_id.size(), nullptr); + } + is_emm_request_sent_ = true; + return OK; +} + +status_t WidevineCasPlugin::refreshEntitlements( + int32_t /*refreshType*/, const CasData& /*refreshData*/) { + return OK; +} + +CasStatus WidevineCasPlugin::processEvent(int32_t event, int32_t arg, + const CasData& eventData, + const CasSessionId* sessionId) { + switch (event) { + case INDIVIDUALIZATION_RESPONSE: + return HandleIndividualizationResponse(eventData); + case TEST_FOR_ECHO: + CallBack(reinterpret_cast(app_data_), ECHO, 0, nullptr, 0, + sessionId); + break; + case LICENSE_RESPONSE: + return HandleEntitlementResponse(eventData); + case LICENSE_RENEWAL_RESPONSE: + return HandleEntitlementRenewalResponse(eventData); + case QUERY_UNIQUE_ID: + return HandleUniqueIDQuery(); + case SET_PARENTAL_CONTROL_AGE: + return HandleSetParentalControlAge(eventData); + case LICENSE_REMOVAL: + return HandleLicenseRemoval(eventData); + case ASSIGN_LICENSE_ID: + return HandleAssignLicenseID(eventData); + case QUERY_WV_CAS_PLUGIN_VERSION: + return HandlePluginVersionQuery(); + case LICENSE_ENTITLEMENT_PERIOD_UPDATE_RESPONSE: + return HandleEntitlementPeriodUpdateResponse(eventData); + default: + return CasStatusCode::kUnknownEvent; + } + return CasStatusCode::kNoError; +} + +CasStatus WidevineCasPlugin::HandleIndividualizationResponse( + const CasData& response) { + if (response.empty()) { + return CasStatus(CasStatusCode::kCasLicenseError, + "empty individualization response"); + } + std::string resp_string(response.begin(), response.end()); + CasStatus status = widevine_cas_api_->handleProvisioningResponse(resp_string); + if (!status.ok()) { + return status; + } + + CallBack(reinterpret_cast(app_data_), INDIVIDUALIZATION_COMPLETE, 0, + nullptr, 0, nullptr); + + if (!init_data_.empty() && !is_emm_request_sent_) { + LOGD("Making license request with provisioned PSSH"); + if (requestLicense(init_data_) != OK) { + return CasStatus(CasStatusCode::kCasLicenseError, + "failed to generate license request"); + } + } + return CasStatusCode::kNoError; +} + +CasStatus WidevineCasPlugin::HandleEntitlementResponse( + const CasData& response) { + if (response.empty()) { + return CasStatus(CasStatusCode::kCasLicenseError, "empty emm response"); + } + std::string resp_string(response.begin(), response.end()); + std::string license_id; + std::string multi_content_license_info; + std::string group_license_info; + CasStatus status = widevine_cas_api_->handleEntitlementResponse( + resp_string, license_id, multi_content_license_info, group_license_info); + if (!status.ok()) { + return status; + } + CallBack(reinterpret_cast(app_data_), LICENSE_CAS_READY, + LICENSE_CAS_READY, reinterpret_cast(&license_id[0]), + license_id.size(), nullptr); + if (!multi_content_license_info.empty()) { + CallBack(reinterpret_cast(app_data_), MULTI_CONTENT_LICENSE_INFO, + MULTI_CONTENT_LICENSE_INFO, + reinterpret_cast(&multi_content_license_info[0]), + multi_content_license_info.size(), nullptr); + } + if (!group_license_info.empty()) { + CallBack(reinterpret_cast(app_data_), GROUP_LICENSE_INFO, + GROUP_LICENSE_INFO, + reinterpret_cast(&group_license_info[0]), + group_license_info.size(), nullptr); + } + return CasStatusCode::kNoError; +} + +CasStatus WidevineCasPlugin::HandleEntitlementRenewalResponse( + const CasData& response) { + if (response.empty()) { + return CasStatus(CasStatusCode::kCasLicenseError, "empty emm response"); + } + std::string resp_string(response.begin(), response.end()); + std::string license_id; + CasStatus status = widevine_cas_api_->handleEntitlementRenewalResponse( + resp_string, license_id); + if (!status.ok()) { + return status; + } + CallBack(reinterpret_cast(app_data_), LICENSE_CAS_RENEWAL_READY, + LICENSE_CAS_RENEWAL_READY, + reinterpret_cast(&license_id[0]), license_id.size(), + nullptr); + return CasStatusCode::kNoError; +} + +CasStatus WidevineCasPlugin::HandleEntitlementPeriodUpdateResponse( + const CasData& response) { + if (response.empty()) { + return CasStatus(CasStatusCode::kCasLicenseError, "empty license response"); + } + std::string resp_string(response.begin(), response.end()); + std::string license_id; + CasStatus status = widevine_cas_api_->handleEntitlementPeriodUpdateResponse( + resp_string, license_id); + if (!status.ok()) { + return status; + } + CallBack( + reinterpret_cast(app_data_), LICENSE_ENTITLEMENT_PERIOD_UPDATED, + LICENSE_ENTITLEMENT_PERIOD_UPDATED, + reinterpret_cast(&license_id[0]), license_id.size(), nullptr); + return CasStatusCode::kNoError; +} + +CasStatus WidevineCasPlugin::HandleUniqueIDQuery() { + std::string buffer; + CasStatus status = widevine_cas_api_->GetUniqueID(&buffer); + if (!status.ok()) { + CasData error(status.error_string().begin(), status.error_string().end()); + CallBack(reinterpret_cast(app_data_), CAS_ERROR, + static_cast(status.status_code()), &error[0], + error.size(), nullptr); + return status; + } else { + CallBack(reinterpret_cast(app_data_), UNIQUE_ID, + static_cast(0), reinterpret_cast(&buffer[0]), + buffer.size(), nullptr); + } + return status; +} + +CasStatus WidevineCasPlugin::HandleSetParentalControlAge(const CasData& data) { + return widevine_cas_api_->HandleSetParentalControlAge(data); +} + +CasStatus WidevineCasPlugin::HandleLicenseRemoval(const CasData& license_id) { + if (license_id.empty()) { + return CasStatus(CasStatusCode::kInvalidParameter, "empty license id"); + } + std::string license_id_str(license_id.begin(), license_id.end()); + + std::string file_name = license_id_str + ".lic"; + CasStatus status = widevine_cas_api_->RemoveLicense(file_name); + if (!status.ok()) { + CasData error(status.error_string().begin(), status.error_string().end()); + CallBack(reinterpret_cast(app_data_), CAS_ERROR, + static_cast(status.status_code()), &error[0], + error.size(), nullptr); + return status; + } + CallBack(reinterpret_cast(app_data_), LICENSE_REMOVED, LICENSE_REMOVED, + reinterpret_cast(&license_id_str[0]), + license_id_str.size(), nullptr); + return CasStatusCode::kNoError; +} + +CasStatus WidevineCasPlugin::HandleAssignLicenseID( + const wvcas::CasData& license_id) { + if (license_id.empty()) { + return CasStatus(CasStatusCode::kInvalidParameter, "empty license id"); + } + std::string license_id_str(license_id.begin(), license_id.end()); + CasStatus status = widevine_cas_api_->RecordLicenseId(license_id_str); + if (!status.ok()) { + return status; + } + CallBack(reinterpret_cast(app_data_), LICENSE_ID_ASSIGNED, + LICENSE_ID_ASSIGNED, reinterpret_cast(&license_id_str[0]), + license_id_str.size(), nullptr); + return CasStatusCode::kNoError; +} + +CasStatus WidevineCasPlugin::HandlePluginVersionQuery() { + std::string version; + if (!Properties::GetWvCasPluginVersion(version)) { + return CasStatus(CasStatusCode::kUnknownError, + "unable to get plugin version"); + } + CallBack(reinterpret_cast(app_data_), WV_CAS_PLUGIN_VERSION, + static_cast(0), reinterpret_cast(&version[0]), + version.size(), /*sessionId=*/nullptr); + return CasStatusCode::kNoError; +} + +void WidevineCasPlugin::OnSessionRenewalNeeded() { + LOGI("OnSessionRenewalNeeded"); + std::string renewal_request; + CasStatus status = + widevine_cas_api_->generateEntitlementRenewalRequest(&renewal_request); + if (!status.ok()) { + LOGE("unable to generate a license renewal request: %s", + status.error_string().c_str()); + return; + } + CallBack(reinterpret_cast(app_data_), LICENSE_RENEWAL_REQUEST, + LICENSE_RENEWAL_REQUEST, + reinterpret_cast(&renewal_request[0]), + renewal_request.size(), nullptr); +} + +// TODO(jfore): Hook up usage of the key status once the license service +// populates policy information and drop the status logging. b/129482318 +void WidevineCasPlugin::OnSessionKeysChange(const KeyStatusMap& keys_status, + bool has_new_usable_key) { + LOGI("OnSessionKeysChange: Has new usable key : %s", + (has_new_usable_key ? "true" : "false")); + + for (auto& key : keys_status) { + LOGI("%d", key.second); + } +} + +// Send the license expiration timestamp via this existing callback to app. +// Callback will be triggered once license is installed or license expiration +// time getting update. +// TODO(b/163427255): Should we combine with license_id? +void WidevineCasPlugin::OnExpirationUpdate(int64_t new_expiry_time_seconds) { + LOGI("OnExpirationUpdate"); + CallBack(reinterpret_cast(app_data_), LICENSE_NEW_EXPIRY_TIME, + LICENSE_NEW_EXPIRY_TIME, + reinterpret_cast(&new_expiry_time_seconds), 8, nullptr); +} + +void WidevineCasPlugin::OnNewRenewalServerUrl( + const std::string& renewal_server_url) { + CasData url(renewal_server_url.begin(), renewal_server_url.end()); + CallBack(reinterpret_cast(app_data_), LICENSE_RENEWAL_URL, 0, &url[0], + url.size(), nullptr); +} + +void WidevineCasPlugin::OnLicenseExpiration() { + LOGI("OnLicenseExpiration"); + CallBack(reinterpret_cast(app_data_), + android::ERROR_CAS_LICENSE_EXPIRED, + android::ERROR_CAS_LICENSE_EXPIRED, nullptr, 0, nullptr); +} + +void WidevineCasPlugin::OnAgeRestrictionUpdated(const WvCasSessionId& sessionId, + uint8_t ecm_age_restriction) { + LOGI("OnAgeRestrictionUpdated"); + CallBack(reinterpret_cast(app_data_), AGE_RESTRICTION_UPDATED, + /*arg=*/0, &ecm_age_restriction, 1, &sessionId); +} + +void WidevineCasPlugin::OnSessionFingerprintingUpdated( + const WvCasSessionId& sessionId, const CasData& fingerprinting) { + LOGI("OnSessionFingerprintingUpdated"); + CallBack(reinterpret_cast(app_data_), + SESSION_FINGERPRINTING_INFO, /*arg=*/ + 0, + fingerprinting.empty() ? nullptr + : const_cast(&fingerprinting[0]), + fingerprinting.size(), &sessionId); +} + +void WidevineCasPlugin::OnSessionServiceBlockingUpdated( + const WvCasSessionId& sessionId, const CasData& service_blocking) { + LOGI("OnSessionServiceBlockingUpdated"); + CallBack(reinterpret_cast(app_data_), + SESSION_SERVICE_BLOCKING_INFO, /*arg=*/ + 0, + service_blocking.empty() + ? nullptr + : const_cast(&service_blocking[0]), + service_blocking.size(), &sessionId); +} + +void WidevineCasPlugin::OnFingerprintingUpdated(const CasData& fingerprinting) { + if (fingerprinting.empty()) { + return; + } + LOGI("OnFingerprintingUpdated"); + CallBack(reinterpret_cast(app_data_), FINGERPRINTING_INFO, /*arg=*/0, + const_cast(&fingerprinting[0]), fingerprinting.size(), + nullptr); +} + +void WidevineCasPlugin::OnServiceBlockingUpdated( + const CasData& service_blocking) { + if (service_blocking.empty()) { + return; + } + LOGI("OnServiceBlockingUpdated"); + CallBack(reinterpret_cast(app_data_), SERVICE_BLOCKING_INFO, /*arg=*/0, + const_cast(&service_blocking[0]), service_blocking.size(), + nullptr); +} + +void WidevineCasPlugin::OnEntitlementPeriodUpdateNeeded( + const std::string& signed_license_request) { + LOGI("OnEntitlementPeriodUpdateNeeded"); + if (!signed_license_request.empty()) { + CallBack(reinterpret_cast(app_data_), + LICENSE_ENTITLEMENT_PERIOD_UPDATE_REQUEST, + LICENSE_ENTITLEMENT_PERIOD_UPDATE_REQUEST, + const_cast( + reinterpret_cast(&signed_license_request[0])), + signed_license_request.size(), nullptr); + } +} + +void WidevineCasPlugin::CallBack(void* appData, int32_t event, int32_t arg, + uint8_t* data, size_t size, + const CasSessionId* sessionId) const { + if (callback_ext_ != nullptr) { + callback_ext_(appData, event, arg, data, size, sessionId); + } else { + callback_(appData, event, arg, data, size); + } +} + +} // namespace wvcas diff --git a/protos/Android.bp b/protos/Android.bp new file mode 100644 index 0000000..62bfaa7 --- /dev/null +++ b/protos/Android.bp @@ -0,0 +1,21 @@ +// ----------------------------------------------------------------------------- +// Builds libcas_protos.a +// Generates *.a, *.pb.h and *.pb.cc for *.proto files. +// + +cc_library_static { + + name: "libcas_protos", + + proprietary: true, + + srcs: [ + "media_cas.proto", + "device_files.proto", + "license_protocol.proto", + ], + + proto: { + export_proto_headers: true, + }, +} diff --git a/protos/device_files.proto b/protos/device_files.proto new file mode 100644 index 0000000..9ca0b99 --- /dev/null +++ b/protos/device_files.proto @@ -0,0 +1,115 @@ +// ---------------------------------------------------------------------------- +// device_files.proto +// ---------------------------------------------------------------------------- +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. +// +// Description: +// Format of various files stored at the device. +// +syntax = "proto2"; + +package video_widevine_client.sdk; + +// need this if we are using libprotobuf-cpp-2.3.0-lite +option optimize_for = LITE_RUNTIME; + +message NameValue { + optional string name = 1; + optional string value = 2; +} + +message DeviceCertificate { + optional bytes certificate = 1; + optional bytes wrapped_private_key = 2; +} + +message License { + enum LicenseState { + ACTIVE = 1; + RELEASING = 2; + } + + optional LicenseState state = 1; + optional bytes pssh_data = 2; + optional bytes license_request = 3; + optional bytes license = 4; + optional bytes renewal_request = 5; + optional bytes renewal = 6; + optional bytes release_server_url = 7; + optional int64 playback_start_time = 8 [default = 0]; + optional int64 last_playback_time = 9 [default = 0]; + repeated NameValue app_parameters = 10; + // This will be 0/missing if the grace period has not expired; otherwise it + // contains the playback_start_time we should use as an override. This is + // ignored if there is no grace period. + optional int64 grace_period_end_time = 11 [default = 0]; + optional bytes usage_entry = 12; + optional int64 usage_entry_number = 13; +} + +message UsageInfo { + message ProviderSession { + optional bytes token = 1; + optional bytes license_request = 2; + optional bytes license = 3; + optional bytes key_set_id = 4; + optional bytes usage_entry = 5; + optional int64 usage_entry_number = 6; + } + + repeated ProviderSession sessions = 1; +} + +message HlsAttributes { + enum Method { + AES_128 = 1; + SAMPLE_AES = 2; + } + optional Method method = 1; + optional bytes media_segment_iv = 2; +} + +message UsageTableInfo { + message UsageEntryInfo { + enum UsageEntryStorage { + LICENSE = 1; + USAGE_INFO = 2; + UNKNOWN = 3; + } + + optional UsageEntryStorage storage = 1; + optional bytes key_set_id = 2; + optional bytes usage_info_file_name = 3; // hash of the app_id + } + + optional bytes usage_table_header = 1; + repeated UsageEntryInfo usage_entry_info = 2; +} + +message File { + enum FileType { + DEVICE_CERTIFICATE = 1; + LICENSE = 2; + USAGE_INFO = 3; + HLS_ATTRIBUTES = 4; + USAGE_TABLE_INFO = 5; + } + + enum FileVersion { VERSION_1 = 1; } + + optional FileType type = 1; + optional FileVersion version = 2 [default = VERSION_1]; + optional DeviceCertificate device_certificate = 3; + optional License license = 4; + optional UsageInfo usage_info = 5; + optional HlsAttributes hls_attributes = 6; + optional UsageTableInfo usage_table_info = 7; +} + +message HashedFile { + optional bytes file = 1; + // A raw (not hex-encoded) SHA256, taken over the bytes of 'file'. + optional bytes hash = 2; +} diff --git a/protos/license_protocol.proto b/protos/license_protocol.proto new file mode 100644 index 0000000..0ec2482 --- /dev/null +++ b/protos/license_protocol.proto @@ -0,0 +1,1261 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +// Description: +// Definitions of the protocol buffer messages used in the Widevine license +// exchange protocol. + +syntax = "proto2"; + +package video_widevine; + +// need this if we are using libprotobuf-cpp-2.3.0-lite +option optimize_for = LITE_RUNTIME; + +option java_package = "com.google.video.widevine.protos"; + +enum LicenseType { + STREAMING = 1; + OFFLINE = 2; + // License type decision is left to the provider. + AUTOMATIC = 3; +} + +enum PlatformVerificationStatus { + // The platform is not verified. + PLATFORM_UNVERIFIED = 0; + // Tampering detected on the platform. + PLATFORM_TAMPERED = 1; + // The platform has been verified by means of software. + PLATFORM_SOFTWARE_VERIFIED = 2; + // The platform has been verified by means of hardware (e.g. secure boot). + PLATFORM_HARDWARE_VERIFIED = 3; + // Platform verification was not performed. + PLATFORM_NO_VERIFICATION = 4; + // Platform and secure storage capability have been verified by means of + // software. + PLATFORM_SECURE_STORAGE_SOFTWARE_VERIFIED = 5; +} + +// LicenseIdentification is propagated from LicenseRequest to License, +// incrementing version with each iteration. +message LicenseIdentification { + optional bytes request_id = 1; + optional bytes session_id = 2; + optional bytes purchase_id = 3; + optional LicenseType type = 4; + optional int32 version = 5; + optional bytes provider_session_token = 6; +} + +// This message is used to indicate the license cateogry spec for a license as +// a part of initial license issuance. +message LicenseCategorySpec { + // Possible license categories. + enum LicenseCategory { + // By default, License is used for single content. + SINGLE_CONTENT_LICENSE_DEFAULT = 0; + // License is used for multiple contents (could be a combination of + // single contents and groups of contents). + MULTI_CONTENT_LICENSE = 1; + // License is used for contents logically grouped. + GROUP_LICENSE = 2; + } + // Optional. License category indicates if license is used for single + // content, multiple contents (could be a combination of + // single contents and groups of contents) or a group of contents. + optional LicenseCategory license_category = 1; + // Optional. Content or group ID covered by the license. + oneof content_or_group_id { + // Content_id would be present if it is a license for single content. + bytes content_id = 2; + // Group_id would be present if the license is a multi_content_license or + // group_license. Group Id could be the name of a group of contents, + // defined by licensor. + bytes group_id = 3; + } +} + +message License { + message Policy { + // Indicates that playback of the content is allowed. + optional bool can_play = 1 [default = false]; + + // Indicates that the license may be persisted to non-volatile + // storage for offline use. + optional bool can_persist = 2 [default = false]; + + // Indicates that renewal of this license is allowed. + optional bool can_renew = 3 [default = false]; + + // For the |*duration*| fields, playback must halt when + // license_start_time (seconds since the epoch (UTC)) + + // license_duration_seconds is exceeded. A value of 0 + // indicates that there is no limit to the duration. + + // Indicates the rental window. + optional int64 rental_duration_seconds = 4 [default = 0]; + + // Indicates the viewing window, once playback has begun. + optional int64 playback_duration_seconds = 5 [default = 0]; + + // Indicates the time window for this specific license. + optional int64 license_duration_seconds = 6 [default = 0]; + + // The |renewal*| fields only apply if |can_renew| is true. + + // The window of time, in which playback is allowed to continue while + // renewal is attempted, yet unsuccessful due to backend problems with + // the license server. + optional int64 renewal_recovery_duration_seconds = 7 [default = 0]; + + // All renewal requests for this license shall be directed to the + // specified URL. + optional string renewal_server_url = 8; + + // How many seconds after license_start_time, before renewal is first + // attempted. + optional int64 renewal_delay_seconds = 9 [default = 0]; + + // Specifies the delay in seconds between subsequent license + // renewal requests, in case of failure. + optional int64 renewal_retry_interval_seconds = 10 [default = 0]; + + // Indicates that the license shall be sent for renewal when usage is + // started. + optional bool renew_with_usage = 11 [default = false]; + + // Indicates to client that license renewal and release requests ought to + // include ClientIdentification (client_id). + optional bool always_include_client_id = 12 [default = false]; + + // Duration of grace period before playback_duration_seconds (short window) + // goes into effect. Optional. + optional int64 play_start_grace_period_seconds = 13 [default = 0]; + + // Enables "soft enforcement" of playback_duration_seconds, letting the user + // finish playback even if short window expires. Optional. + optional bool soft_enforce_playback_duration = 14 [default = false]; + + // Enables "soft enforcement" of rental_duration_seconds. Initial playback + // must always start before rental duration expires. In order to allow + // subsequent playbacks to start after the rental duration expires, + // soft_enforce_playback_duration must be true. Otherwise, subsequent + // playbacks will not be allowed once rental duration expires. Optional. + optional bool soft_enforce_rental_duration = 15 [default = true]; + } + + message KeyContainer { + enum KeyType { + // Exactly one key of this type must appear. + SIGNING = 1; + CONTENT = 2; + KEY_CONTROL = 3; + OPERATOR_SESSION = 4; + ENTITLEMENT = 5; + } + + // The SecurityLevel enumeration allows the server to communicate the level + // of robustness required by the client, in order to use the key. + enum SecurityLevel { + // Software-based whitebox crypto is required. + SW_SECURE_CRYPTO = 1; + + // Software crypto and an obfuscated decoder is required. + SW_SECURE_DECODE = 2; + + // The key material and crypto operations must be performed within a + // hardware backed trusted execution environment. + HW_SECURE_CRYPTO = 3; + + // The crypto and decoding of content must be performed within a hardware + // backed trusted execution environment. + HW_SECURE_DECODE = 4; + + // The crypto, decoding and all handling of the media (compressed and + // uncompressed) must be handled within a hardware backed trusted + // execution environment. + HW_SECURE_ALL = 5; + } + + message KeyControl { + // |key_control| is documented in: + // Widevine Modular DRM Security Integration Guide for CENC + // If present, the key control must be communicated to the secure + // environment prior to any usage. This message is automatically generated + // by the Widevine License Server SDK. + optional bytes key_control_block = 1; + optional bytes iv = 2; + } + + message OutputProtection { + // Indicates whether HDCP is required on digital outputs, and which + // version should be used. + enum HDCP { + HDCP_NONE = 0; + HDCP_V1 = 1; + HDCP_V2 = 2; + HDCP_V2_1 = 3; + HDCP_V2_2 = 4; + HDCP_V2_3 = 5; + HDCP_NO_DIGITAL_OUTPUT = 0xff; + } + optional HDCP hdcp = 1 [default = HDCP_NONE]; + + // Indicate the CGMS setting to be inserted on analog output. + enum CGMS { + CGMS_NONE = 42; + COPY_FREE = 0; + COPY_ONCE = 2; + COPY_NEVER = 3; + } + optional CGMS cgms_flags = 2 [default = CGMS_NONE]; + + enum HdcpSrmRule { + HDCP_SRM_RULE_NONE = 0; + // In 'required_protection', this means most current SRM is required. + // Update the SRM on the device. If update cannot happen, + // do not allow the key. + // In 'requested_protection', this means most current SRM is requested. + // Update the SRM on the device. If update cannot happen, + // allow use of the key anyway. + CURRENT_SRM = 1; + } + optional HdcpSrmRule hdcp_srm_rule = 3 [default = HDCP_SRM_RULE_NONE]; + // Optional requirement to indicate analog output is not allowed. + optional bool disable_analog_output = 4 [default = false]; + } + + message VideoResolutionConstraint { + // Minimum and maximum video resolutions in the range (height x width). + optional uint32 min_resolution_pixels = 1; + optional uint32 max_resolution_pixels = 2; + // Optional output protection requirements for this range. If not + // specified, the OutputProtection in the KeyContainer applies. + optional OutputProtection required_protection = 3; + } + + message OperatorSessionKeyPermissions { + // Permissions/key usage flags for operator service keys + // (type = OPERATOR_SESSION). + optional bool allow_encrypt = 1 [default = false]; + optional bool allow_decrypt = 2 [default = false]; + optional bool allow_sign = 3 [default = false]; + optional bool allow_signature_verify = 4 [default = false]; + } + + // KeyCategorySpec message is used to identify if current key is generated + // for a single content or a group of contents. + message KeyCategorySpec { + // Represents what kind of content a key is used for. + enum KeyCategory { + // By default, key is created for single content. + SINGLE_CONTENT_KEY_DEFAULT = 0; + // Key is created for a group of contents. + GROUP_KEY = 1; + } + // Indicate if the current key is created for single content or for group + // use. + optional KeyCategory key_category = 1; + // Id for key category. If it is a key for single content, this id + // represents the content_id. Otherwise, it represents a group_id. + oneof content_or_group_id { + bytes content_id = 2; + bytes group_id = 3; + } + } + + optional bytes id = 1; + optional bytes iv = 2; + optional bytes key = 3; + optional KeyType type = 4; + optional SecurityLevel level = 5 [default = SW_SECURE_CRYPTO]; + optional OutputProtection required_protection = 6; + // NOTE: Use of requested_protection is not recommended as it is only + // supported on a small number of platforms. + optional OutputProtection requested_protection = 7; + optional KeyControl key_control = 8; + optional OperatorSessionKeyPermissions operator_session_key_permissions = 9; + // Optional video resolution constraints. If the video resolution of the + // content being decrypted/decoded falls within one of the specified ranges, + // the optional required_protections may be applied. Otherwise an error will + // be reported. + // NOTE: Use of this feature is not recommended, as it is only supported on + // a small number of platforms. + repeated VideoResolutionConstraint video_resolution_constraints = 10; + // Optional flag to indicate the key must only be used if the client + // supports anti rollback of the user table. Content provider can query the + // client capabilities to determine if the client support this feature. + optional bool anti_rollback_usage_table = 11 [default = false]; + // Optional not limited to commonly known track types such as SD, HD. + // It can be some provider defined label to identify the track. + optional string track_label = 12; + // A Key Category Spec is used to identify if current key is generated for a + // single content or a group of contents. + optional KeyCategorySpec key_category_spec = 13; + } + + optional LicenseIdentification id = 1; + optional Policy policy = 2; + repeated KeyContainer key = 3; + // Time of the request in seconds (UTC) as set in + // LicenseRequest.request_time. If this time is not set in the request, + // the local time at the license service is used in this field. + optional int64 license_start_time = 4; + // TODO(b/65054419): Deprecate remote_attestation_verified in favor of + // platform_verification_status, below. + optional bool remote_attestation_verified = 5 [default = false]; + // Client token generated by the content provider. Optional. + optional bytes provider_client_token = 6; + // 4cc code specifying the CENC protection scheme as defined in the CENC 3.0 + // specification. Propagated from Widevine PSSH box. Optional. + optional uint32 protection_scheme = 7; + // 8 byte verification field "HDCPDATA" followed by unsigned 32 bit minimum + // HDCP SRM version (whether the version is for HDCP1 SRM or HDCP2 SRM + // depends on client max_hdcp_version). + // Additional details can be found in Widevine Modular DRM Security + // Integration Guide for CENC. + optional bytes srm_requirement = 8; + // If present this contains a signed SRM file (either HDCP1 SRM or HDCP2 SRM + // depending on client max_hdcp_version) that should be installed on the + // client device. + optional bytes srm_update = 9; + // Indicates the status of any type of platform verification performed by the + // server. + optional PlatformVerificationStatus platform_verification_status = 10 + [default = PLATFORM_NO_VERIFICATION]; + // IDs of the groups for which keys are delivered in this license, if any. + repeated bytes group_ids = 11; + // Optional. LicenseCategorySpec is used to indicate the license category for + // a license. It could be used as a part of initial license issuance or shown + // as a part of license in license response. + optional LicenseCategorySpec license_category_spec = 12; +} + +enum ProtocolVersion { + VERSION_2_0 = 20; + VERSION_2_1 = 21; +} + +message LicenseRequest { + message ContentIdentification { + message CencDeprecated { + repeated bytes pssh = 1; + optional LicenseType license_type = 2; + optional bytes request_id = 3; // Opaque, client-specified. + } + + message WebmDeprecated { + optional bytes header = 1; + optional LicenseType license_type = 2; + optional bytes request_id = 3; // Opaque, client-specified. + } + + message ExistingLicense { + optional LicenseIdentification license_id = 1; + optional int64 seconds_since_started = 2; + optional int64 seconds_since_last_played = 3; + optional bytes session_usage_table_entry = 4; + } + + message InitData { + enum InitDataType { + CENC = 1; + WEBM = 2; + } + + optional InitDataType init_data_type = 1 [default = CENC]; + optional bytes init_data = 2; + optional LicenseType license_type = 3; + optional bytes request_id = 4; + } + + // oneof content_id_variant { + // Exactly one of these must be present. + optional CencDeprecated cenc_id_deprecated = 1; + optional WebmDeprecated webm_id_deprecated = 2; + optional ExistingLicense existing_license = 3; + optional InitData init_data = 4; + //} + } + + message SubSessionData { + // Required. The key ID for the corresponding SUB_SESSION_KEY. The + // value must match the sub_session_key_id field for a + // corresponding SubLicense message from the PSSH. + optional string sub_session_key_id = 1; + // Required. The nonce for the track. + optional uint32 nonce = 2; + // Required for initial license request used for each CONTENT key_container + // to know which nonce to use for building its key control block. + // Not needed for renewal license request. + optional string track_label = 3; + } + + enum RequestType { + NEW = 1; + RENEWAL = 2; + RELEASE = 3; + } + + // The client_id provides information authenticating the calling device. It + // contains the Widevine keybox token that was installed on the device at the + // factory. This field or encrypted_client_id below is required for a valid + // license request, but both should never be present in the same request. + optional ClientIdentification client_id = 1; + optional ContentIdentification content_id = 2; + optional RequestType type = 3; + // Time of the request in seconds (UTC) as set by the client. + optional int64 request_time = 4; + // Old-style decimal-encoded string key control nonce. + optional bytes key_control_nonce_deprecated = 5; + optional ProtocolVersion protocol_version = 6 [default = VERSION_2_0]; + // New-style uint32 key control nonce, please use instead of + // key_control_nonce_deprecated. + optional uint32 key_control_nonce = 7; + // Encrypted ClientIdentification message, used for privacy purposes. + optional EncryptedClientIdentification encrypted_client_id = 8; + // Optional sub session context information. Required for using + // SubLicenses from the PSSH. + repeated SubSessionData sub_session_data = 9; +} + +message LicenseError { + enum Error { + // The device credentials are invalid. The device must re-provision. + INVALID_DRM_DEVICE_CERTIFICATE = 1; + // The device credentials have been revoked. Re-provisioning is not + // possible. + REVOKED_DRM_DEVICE_CERTIFICATE = 2; + // The service is currently unavailable due to the backend being down + // or similar circumstances. + SERVICE_UNAVAILABLE = 3; + } + + optional Error error_code = 1; +} + +message MetricData { + enum MetricType { + // The time spent in the 'stage', specified in microseconds. + LATENCY = 1; + // The UNIX epoch timestamp at which the 'stage' was first accessed in + // microseconds. + TIMESTAMP = 2; + } + + message TypeValue { + optional MetricType type = 1; + // The value associated with 'type'. For example if type == LATENCY, the + // value would be the time in microseconds spent in this 'stage'. + optional int64 value = 2 [default = 0]; + } + + // 'stage' that is currently processing the SignedMessage. Required. + optional string stage_name = 1; + // metric and associated value. + repeated TypeValue metric_data = 2; +} + +message RemoteAttestation { + // Encrypted ClientIdentification message containing the device remote + // attestation certificate. Required. + optional EncryptedClientIdentification certificate = 1; + // Bytes of salt which were added to the remote attestation challenge prior to + // signing it. Required. + optional bytes salt = 2; + // Signed remote attestation challenge + salt. Required. + optional bytes signature = 3; +} + +message VersionInfo { + // License SDK version reported by the Widevine License SDK. This field + // is populated automatically by the SDK. + optional string license_sdk_version = 1; + // Version of the service hosting the license SDK. This field is optional. + // It may be provided by the hosting service. + optional string license_service_version = 2; +} + +enum HashAlgorithmProto { + // Unspecified hash algorithm: SHA_256 shall be used for ECC based algorithms + // and SHA_1 shall be used otherwise. + HASH_ALGORITHM_UNSPECIFIED = 0; + HASH_ALGORITHM_SHA_1 = 1; + HASH_ALGORITHM_SHA_256 = 2; + HASH_ALGORITHM_SHA_384 = 3; +} + +message SignedMessage { + enum MessageType { + LICENSE_REQUEST = 1; + LICENSE = 2; + ERROR_RESPONSE = 3; + SERVICE_CERTIFICATE_REQUEST = 4; + SERVICE_CERTIFICATE = 5; + SUB_LICENSE = 6; + CAS_LICENSE_REQUEST = 7; + CAS_LICENSE = 8; + EXTERNAL_LICENSE_REQUEST = 9; + EXTERNAL_LICENSE = 10; + } + + enum SessionKeyType { + UNDEFINED = 0; + WRAPPED_AES_KEY = 1; + EPHEMERAL_ECC_PUBLIC_KEY = 2; + } + + optional MessageType type = 1; + optional bytes msg = 2; + // Required field that contains the signature of the bytes of msg. + // For license requests, the signing algorithm is determined by the + // certificate contained in the request. + // For license responses, the signing algorithm is HMAC with signing key based + // on |session_key|. + optional bytes signature = 3; + // If populated, the contents of this field will be signaled by the + // |session_key_type| type. If the |session_key_type| is WRAPPED_AES_KEY the + // key is the bytes of an encrypted AES key. If the |session_key_type| is + // EPHEMERAL_ECC_PUBLIC_KEY the field contains the bytes of an RFC5208 ASN1 + // serialized ECC public key. + optional bytes session_key = 4; + // Remote attestation data which will be present in the initial license + // request for ChromeOS client devices operating in verified mode. Remote + // attestation challenge data is |msg| field above. Optional. + optional RemoteAttestation remote_attestation = 5; + + repeated MetricData metric_data = 6; + // Version information from the SDK and license service. This information is + // provided in the license response. + optional VersionInfo service_version_info = 7; + // Optional field that contains the algorithm type used to generate the + // session_key and signature in a LICENSE message. + optional SessionKeyType session_key_type = 8 [default = WRAPPED_AES_KEY]; + // The core message is the simple serialization of fields used by OEMCrypto. + // This field was introduced in OEMCrypto API v16. + optional bytes oemcrypto_core_message = 9; + // Optional field that indicates the hash algorithm used in signature scheme. + optional HashAlgorithmProto hash_algorithm = 10; + // If true it indicates that a LICENSE message session key was based on an + // alternate key provided by the client credentials. + optional bool using_secondary_key = 11; +} + +message GroupKeys { + enum GroupLicenseVersion { + GROUP_LICENSE_VERSION_1 = 0; + GROUP_LICENSE_VERSION_2 = 1; + } + + message GroupKeyData { + // Required track type. This indicates the track type to which this key + // belongs. + optional string track_type = 1; + // A required signed message. The message body contains a serialized group + // msg. + optional bytes key = 2; + } + + // Optional key container array used in group licensing V1. This is not used + // in V2. + repeated License.KeyContainer key = 1 [deprecated = true]; + + // Byte string that identifies the group to which this license material + // belongs. + optional bytes group_id = 2; + + // Required version id beginning with version 2. If not present version 1 + // should be assumed. + optional GroupLicenseVersion version = 3 [default = GROUP_LICENSE_VERSION_1]; + // Optional key container array for group licensing V2. + repeated GroupKeyData key_data = 4; +} + +// ---------------------------------------------------------------------------- +// certificate_provisioning.proto +// ---------------------------------------------------------------------------- +// Description of section: +// Public protocol buffer definitions for Widevine Device Certificate +// Provisioning protocol. + +// ProvisioningOptions specifies the type of certificate to specify and +// in the case of X509 certificates, the certificate authority to use. +message ProvisioningOptions { + enum CertificateType { + WIDEVINE_DRM = 0; // Default. The original certificate type. + X509 = 1; // X.509 certificate. + WIDEVINE_KEYBOX = 2; + } + + optional CertificateType certificate_type = 1 [default = WIDEVINE_DRM]; + + // Contains the application-specific name used to identify the certificate + // authority for signing the generated certificate. This is required if the + // certificate type is X509. + optional string certificate_authority = 2; + // System ID for OTA keybox provisioning. Requires device secure boot. + optional uint32 system_id = 3; +} + +// Provisioning request sent by client devices to provisioning service. +message ProvisioningRequest { + message EncryptedSessionKeys { + message SessionKeys { + // 16 bytes encryption key generated by client, used by the server to: + // (1) AES-128-CBC decrypt encrypted_client_id in + // EncryptedClientIdentification which is in RemoteAttestation + // (2) AES-128-CBC encrypt device_key to be returned in + // ProvisioningResponse. + optional bytes encryption_key = 1; + // 32 bytes mac key generated by client, used by server to sign + // the ProvisioningResponse. + optional bytes mac_key = 2; + } + // Serial number of certificate which was used to encrypt the session keys. + // Required. + optional bytes certificate_serial_number = 1; + // Serialized, encrypted session keys. Required. + optional bytes encrypted_session_keys = 2; + } + oneof clear_or_encrypted_client_id { + // Device root of trust and other client identification. Required. + ClientIdentification client_id = 1; + EncryptedClientIdentification encrypted_client_id = 5; + } + // Nonce value used to prevent replay attacks. Required. + optional bytes nonce = 2; + // Options for type of certificate to generate. Optional. + optional ProvisioningOptions options = 3; + oneof spoid_param { + // Stable identifier, unique for each device + application (or origin). + // To be deprecated. + bytes stable_id = 4; + // Service provider ID from the service certificate's provider_id field. + // Preferred parameter. + bytes provider_id = 6; + // Client-generated stable per-origin identifier to be copied directly + // to the client certificate serial number. + bytes spoid = 7; + } + // SessionKeys encrypted using a service cert public key. + // Required for keybox provisioning. + optional EncryptedSessionKeys encrypted_session_keys = 8; +} + +// Provisioning response sent by the provisioning server to client devices. +// This message is used for both regular Widevine DRM certificates and for +// application-specific X.509 certificates. +message ProvisioningResponse { + message OtaKeybox { + // Iv used along with SessionKeys.encryption_key for encrypting device key. + optional bytes device_key_encryption_iv = 1; + // Device key component of the keybox, encrypted using the + // SessionKeys.encryption_key in the request and |device_key_encryption_iv| + // above. + optional bytes encrypted_device_key = 2; + // Device CA token component of the keybox. + optional bytes device_ca_token = 3; + } + // AES-128 encrypted device private RSA key. PKCS#1 ASN.1 DER-encoded. + // Required. For X.509 certificates, the private RSA key may also include + // a prefix as specified by private_key_prefix in the X509CertificateMetadata + // proto message. + optional bytes device_rsa_key = 1; + // Initialization vector used to encrypt device_rsa_key. Required. + optional bytes device_rsa_key_iv = 2; + // For Widevine DRM certificates, this contains the serialized + // SignedDrmCertificate. For X.509 certificates, this contains the PEM + // encoded X.509 certificate. Required. + optional bytes device_certificate = 3; + // Nonce value matching nonce in ProvisioningRequest. Required. + optional bytes nonce = 4; + // Key used to wrap device_rsa_key when DRM provisioning an OEM factory + // provisioned device. Encrypted with the device OEM public key using + // RSA-OAEP. + optional bytes wrapping_key = 5; + // Only populated in OTA keybox provisioning response. + optional OtaKeybox ota_keybox = 6; +} + +// Protocol-specific context data used to hold the state of the server in +// stateful provisioning protocols. For more information, please refer to +// "Widevine DRM Provisioning using Third-Part and Stateful Protocols". +message ProvisioningContext { + // Serialized ProvisioningContextKeyData. Required. + optional bytes key_data = 1; + // Protocol-dependent context data, encrypted with key and IV in key_data. + // Required. + optional bytes context_data = 2; +} + +message SignedProvisioningContext { + // ProvisioningContext in bytes. + optional bytes provisioning_context = 1; + // RSASSA-PSS signature of provisioning_context. Signed with service private + // key. + optional bytes signature = 2; +} + +// Cryptographic tokens to be used for ProvisioningContext. +message ProvisioningContextKeyData { + // Encryption key, usually 32 bytes used for AES-256-CBC. Required. + optional bytes encryption_key = 1; + // Encryption IV, 16 bytes. Required. + optional bytes encryption_iv = 2; +} + +// Serialized ProvisioningRequest or ProvisioningResponse signed with +// The message authentication key. +message SignedProvisioningMessage { + enum ProtocolVersion { + SERVICE_CERTIFICATE_REQUEST = 1; // Service certificate request. + PROVISIONING_20 = 2; // Keybox factory-provisioned devices. + PROVISIONING_30 = 3; // OEM certificate factory-provisioned devices. + ARCPP_PROVISIONING = 4; // ChromeOS/Arc++ devices. + // Devices use Boot Certificate Chain (BCC) to provision an OEM certificate. + PROVISIONING_40 = 5; + INTEL_SIGMA_101 = 101; // Intel Sigma 1.0.1 protocol. + } + + // Serialized protobuf message for the corresponding protocol and stage of + // the provisioning exchange. ProvisioningRequest or ProvisioningResponse + // in the case of Provisioning 2.0, 3.0 and ARCPP_PROVISIONING. Required. + optional bytes message = 1; + // HMAC-SHA256 (Keybox) or RSASSA-PSS (OEM) signature of message. Required + // for provisioning 2.0 and 3.0. For ARCPP_PROVISIONING, only used in + // response. + optional bytes signature = 2; + // Version number of provisioning protocol. + optional ProtocolVersion protocol_version = 3 [default = PROVISIONING_20]; + // Protocol-specific context / state information for multiple-exchange, + // stateful provisioing protocols. Optional. + optional SignedProvisioningContext signed_provisioning_context = 4; + // Remote attestation data to authenticate that the ChromeOS client device + // is operating in verified mode. Remote attestation challenge data is + // |message| field above. Required for ARCPP_PROVISIONING request. + // It contains signature of |message|. + optional RemoteAttestation remote_attestation = 5; + // The core message is the simple serialization of fields used by OEMCrypto. + // This field was introduced in OEMCrypto API v16. The core message format is + // documented in the "Widevine Core Message Serialization". + optional bytes oemcrypto_core_message = 6; + // Optional field that indicates the hash algorithm used in signature scheme. + optional HashAlgorithmProto hash_algorithm = 7; +} + +// ---------------------------------------------------------------------------- +// client_identification.proto +// ---------------------------------------------------------------------------- +// Description: +// ClientIdentification messages used by provisioning and license protocols. + +// ClientIdentification message used to authenticate the client device. +message ClientIdentification { + enum TokenType { + KEYBOX = 0; + DRM_DEVICE_CERTIFICATE = 1; + REMOTE_ATTESTATION_CERTIFICATE = 2; + OEM_DEVICE_CERTIFICATE = 3; + } + + message NameValue { + optional string name = 1; + optional string value = 2; + } + + // Capabilities which not all clients may support. Used for the license + // exchange protocol only. + message ClientCapabilities { + enum HdcpVersion { + HDCP_NONE = 0; + HDCP_V1 = 1; + HDCP_V2 = 2; + HDCP_V2_1 = 3; + HDCP_V2_2 = 4; + HDCP_V2_3 = 5; + HDCP_NO_DIGITAL_OUTPUT = 0xff; + } + + enum CertificateKeyType { + RSA_2048 = 0; + RSA_3072 = 1; + } + + enum AnalogOutputCapabilities { + ANALOG_OUTPUT_UNKNOWN = 0; + ANALOG_OUTPUT_NONE = 1; + ANALOG_OUTPUT_SUPPORTED = 2; + ANALOG_OUTPUT_SUPPORTS_CGMS_A = 3; + } + + optional bool client_token = 1 [default = false]; + optional bool session_token = 2 [default = false]; + optional bool video_resolution_constraints = 3 [default = false]; + optional HdcpVersion max_hdcp_version = 4 [default = HDCP_NONE]; + optional uint32 oem_crypto_api_version = 5; + // Client has hardware support for protecting the usage table, such as + // storing the generation number in secure memory. For Details, see: + // Widevine Modular DRM Security Integration Guide for CENC + optional bool anti_rollback_usage_table = 6 [default = false]; + // The client shall report |srm_version| if available. + optional uint32 srm_version = 7; + // A device may have SRM data, and report a version, but may not be capable + // of updating SRM data. + optional bool can_update_srm = 8 [default = false]; + repeated CertificateKeyType supported_certificate_key_type = 9; + optional AnalogOutputCapabilities analog_output_capabilities = 10 + [default = ANALOG_OUTPUT_UNKNOWN]; + optional bool can_disable_analog_output = 11 [default = false]; + } + + // Type of factory-provisioned device root of trust. Optional. + optional TokenType type = 1 [default = KEYBOX]; + // Factory-provisioned device root of trust. Required. + optional bytes token = 2; + // Optional client information name/value pairs. + repeated NameValue client_info = 3; + // Client token generated by the content provider. Optional. + optional bytes provider_client_token = 4; + // Number of licenses received by the client to which the token above belongs. + // Only present if client_token is specified. + optional uint32 license_counter = 5; + // List of non-baseline client capabilities. + optional ClientCapabilities client_capabilities = 6; + // Serialized VmpData message. Optional. + optional bytes vmp_data = 7; +} + +// EncryptedClientIdentification message used to hold ClientIdentification +// messages encrypted for privacy purposes. +message EncryptedClientIdentification { + // Provider ID for which the ClientIdentifcation is encrypted (owner of + // service certificate). + optional string provider_id = 1; + // Serial number for the service certificate for which ClientIdentification is + // encrypted. + optional bytes service_certificate_serial_number = 2; + // Serialized ClientIdentification message, encrypted with the privacy key + // using AES-128-CBC with PKCS#5 padding. + optional bytes encrypted_client_id = 3; + // Initialization vector needed to decrypt encrypted_client_id. + optional bytes encrypted_client_id_iv = 4; + // AES-128 privacy key, encrypted with the service public key using RSA-OAEP. + optional bytes encrypted_privacy_key = 5; +} + +// ---------------------------------------------------------------------------- +// device_certificate.proto +// ---------------------------------------------------------------------------- +// Description: +// Device certificate and certificate status list format definitions. + +// DRM certificate definition for user devices, intermediate, service, and root +// certificates. +message DrmDeviceCertificate { + enum CertificateType { + ROOT = 0; + DRM_INTERMEDIATE = 1; + DRM_USER_DEVICE = 2; + SERVICE = 3; + PROVISIONER = 4; + } + + // Type of certificate. Required. + optional CertificateType type = 1; + // 128-bit globally unique serial number of certificate. + // Value is 0 for root certificate. Required. + optional bytes serial_number = 2; + // POSIX time, in seconds, when the certificate was created. Required. + optional uint32 creation_time_seconds = 3; + // Device public key. PKCS#1 ASN.1 DER-encoded. Required. + optional bytes public_key = 4; + // Widevine system ID for the device. Required for intermediate and + // user device certificates. + optional uint32 system_id = 5; + // Deprecated field, which used to indicate whether the device was a test + // (non-production) device. The test_device field in ProvisionedDeviceInfo + // below should be observed instead. + optional bool test_device_deprecated = 6 [deprecated = true]; + // Service identifier (web origin) for the provider which owns the + // certificate. Required for service and provisioner certificates. + optional string provider_id = 7; +} + +// Contains DRM and OEM certificate status and device information for a +// specific system ID. +message DeviceCertificateStatus { + enum Status { + VALID = 0; + REVOKED = 1; + }; + + // Serial number of the intermediate DrmDeviceCertificate to which this + // message refers. Required. + optional bytes drm_serial_number = 1; + // Status of the certificate. Optional. + optional Status status = 2 [default = VALID]; + // Device model information about the device to which the intermediate + // certificate(s) correspond. + optional ProvisionedDeviceInfo device_info = 4; + // Serial number of the OEM X.509 intermediate certificate for this type + // of device. Present only if the device is OEM-provisioned. + optional bytes oem_serial_number = 5; +} + +// List of DeviceCertificateStatus. Used to propagate certificate revocation +// status and device information. +message DeviceCertificateStatusList { + // POSIX time, in seconds, when the list was created. Required. + optional uint32 creation_time_seconds = 1; + // DeviceCertificateStatus for each system ID. + repeated DeviceCertificateStatus certificate_status = 2; +} + +// Signed CertificateStatusList +message SignedCertificateStatusList { + // Serialized DeviceCertificateStatusList. Required. + optional bytes certificate_status_list = 1; + // Signature of certificate_status_list. Signed with root certificate private + // key using RSASSA-PSS. Required. + optional bytes signature = 2; +} + +// ---------------------------------------------------------------------------- +// provisioned_device_info.proto +// ---------------------------------------------------------------------------- +// Description: +// Provisioned device info format definitions. + +// Contains device model information for a provisioned device. +message ProvisionedDeviceInfo { + enum WvSecurityLevel { + // Defined in "Widevine Security Integration Guide for DASH on Android" + LEVEL_UNSPECIFIED = 0; + LEVEL_1 = 1; + LEVEL_2 = 2; + LEVEL_3 = 3; + } + + // Widevine system ID for the device. Mandatory. + optional uint32 system_id = 1; + // Name of system-on-a-chip. Optional. + optional string soc = 2; + // Name of manufacturer. Optional. + optional string manufacturer = 3; + // Manufacturer's model name. Matches "brand" in device metadata. Optional. + optional string model = 4; + // Type of device (Phone, Tablet, TV, etc). + optional string device_type = 5; + // Device model year. Optional. + optional uint32 model_year = 6; + // Widevine-defined security level. Optional. + optional WvSecurityLevel security_level = 7 [default = LEVEL_UNSPECIFIED]; + // True if the certificate corresponds to a test (non production) device. + // Optional. + optional bool test_device = 8 [default = false]; +} + +// ---------------------------------------------------------------------------- +// widevine_pssh.proto +// ---------------------------------------------------------------------------- +// Description: +// Public protocol buffer definitions for Widevine Cenc Header +// protocol. + +// Each SubLicense message represents a single content key. These keys can be +// added to Widevine CENC initialization data to support both content grouping +// and key rotation. +message SubLicense { + // Required. The key_id of a SUB_SESSION_KEY received in the master license. + // SUB_SESSION_KEY is defined in the Widevine License Protocol. + optional string sub_session_key_id = 1; + + // Required. The key_msg contains the bytes of a serialized SignedMessage + // proto. Internally the message field will contain a serialized KeyContainer + // holding a single content key. + optional bytes key_msg = 2; + + // TODO(jfore): There is some uncertainty about including the current group in + // a license. This may change. + // Byte string that identifies the group to which this this content + // belongs. + optional bytes group_id = 13; +} + +message WidevinePsshData { + enum Type { + SINGLE = 0; // Single PSSH to be used to retrieve content keys. + ENTITLEMENT = 1; // Primary PSSH used to retrieve entitlement keys. + ENTITLED_KEY = 2; // Secondary PSSH containing entitled key(s). + } + + message EntitledKey { + // ID of entitlement key used for wrapping |key|. + optional bytes entitlement_key_id = 1; + // ID of the entitled key. + optional bytes key_id = 2; + // Wrapped key. Required. + optional bytes key = 3; + // IV used for wrapping |key|. Required. + optional bytes iv = 4; + } + + // Entitlement or content key IDs. Can only present in SINGLE or ENTITLEMENT + // PSSHs. May be repeated to facilitate delivery of multiple keys in a + // single license. Cannot be used in conjunction with content_id or + // group_ids, which are the preferred mechanism. + repeated bytes key_ids = 2; + + // Content identifier which may map to multiple entitlement or content key + // IDs to facilitate the delivery of multiple keys in a single license. + // Cannot be present in conjunction with key_ids, but if used must be in all + // PSSHs. + optional bytes content_id = 4; + + // Crypto period index, for media using key rotation. Always corresponds to + // The content key period. This means that if using entitlement licensing + // the ENTITLED_KEY PSSHs will have sequential crypto_period_index's, whereas + // the ENTITELEMENT PSSHs will have gaps in the sequence. Required if doing + // key rotation. + optional uint32 crypto_period_index = 7; + + // Protection scheme identifying the encryption algorithm. The protection + // scheme is represented as a uint32 value. The uint32 contains 4 bytes each + // representing a single ascii character in one of the 4CC protection scheme + // values. To be deprecated in favor of signaling from content. + // 'cenc' (AES-CTR) protection_scheme = 0x63656E63, + // 'cbc1' (AES-CBC) protection_scheme = 0x63626331, + // 'cens' (AES-CTR pattern encryption) protection_scheme = 0x63656E73, + // 'cbcs' (AES-CBC pattern encryption) protection_scheme = 0x63626373. + optional uint32 protection_scheme = 9; + + // Optional. For media using key rotation, this represents the duration + // of each crypto period in seconds. + optional uint32 crypto_period_seconds = 10; + + // Type of PSSH. Required if not SINGLE. + optional Type type = 11 [default = SINGLE]; + + // Key sequence for Widevine-managed keys. Optional. + optional uint32 key_sequence = 12; + + // Group identifiers for all groups to which the content belongs. This can + // be used to deliver licenses to unlock multiple titles / channels. + // Optional, and may only be present in ENTITLEMENT and ENTITLED_KEY PSSHs, + // and not in conjunction with key_ids. + repeated bytes group_ids = 13; + + // Copy/copies of the content key used to decrypt the media stream in which + // the PSSH box is embedded, each wrapped with a different entitlement key. + // May also contain sub-licenses to support devices with OEMCrypto 13 or + // older. May be repeated if using group entitlement keys. Present only in + // PSSHs of type ENTITLED_KEY. + repeated EntitledKey entitled_keys = 14; + + // Video feature identifier, which is used in conjunction with |content_id| + // to determine the set of keys to be returned in the license. Cannot be + // present in conjunction with |key_ids|. + // Current values are "HDR". + optional string video_feature = 15; + + // Audiofeature identifier, which is used in conjunction with |content_id| + // to determine the set of keys to be returned in the license. Cannot be + // present in conjunction with |key_ids|. + // Current values are "commentary". + optional string audio_feature = 16; + + // Entitlement period index for media using entitlement key rotation. Can only + // present in ENTITLEMENT PSSHs. It always corresponds to the entitlement key + // period. + optional uint32 entitlement_period_index = 17; + + //////////////////////////// Deprecated Fields //////////////////////////// + enum Algorithm { + UNENCRYPTED = 0; + AESCTR = 1; + }; + optional Algorithm algorithm = 1 [deprecated = true]; + optional string provider = 3 [deprecated = true]; + optional string track_type = 5 [deprecated = true]; + optional string policy = 6 [deprecated = true]; + optional bytes grouped_license = 8 [deprecated = true]; +} + +// Signed device certificate definition. +// DrmDeviceCertificate signed by a higher (CA) DRM certificate. +message SignedDrmDeviceCertificate { + // Serialized certificate. Required. + optional bytes drm_certificate = 1; + // Signature of certificate. Signed with root or intermediate + // certificate specified below. Required. + optional bytes signature = 2; + // SignedDrmDeviceCertificate used to sign this certificate. + optional SignedDrmDeviceCertificate signer = 3; +} + +// This message is used by the server to preserve and restore session state. +message SessionState { + optional LicenseIdentification license_id = 1; + optional bytes signing_key = 2; + optional uint32 keybox_system_id = 3; + // Provider client token sent back in the license. + optional bytes provider_client_token = 4; + // License counter associated with the avove token. + optional uint32 license_counter = 5; +} + +message CASDrmLicenseRequest { + // The request payload. This is usually the HTTP Post body of a request. + // Required. + optional bytes payload = 1; + // The content provider whose proxy is sending this license request onto the + // Widevine license service. Required. + optional string provider_id = 2; + // An identifier supplied by a content provider, used to identify a piece of + // content and derive key IDs and content keys. + optional bytes content_id = 3; + // A ContentKeySpec identifies a content key by track type name. It also + // specifies the policy that should be used for this key. + // TODO(hali): Consolidate this ContentKeySpec with + // ModularDrmLicenseRequest_ContentKeySpec. Both should include a common + // ContentKeySpec. + message ContentKeySpec { + optional License.KeyContainer.SecurityLevel security_level = 1; + optional License.KeyContainer.OutputProtection required_output_protection = + 2; + optional License.KeyContainer.OutputProtection requested_output_protection = + 3; + // Optionally specify even, odd or single slot for key rotation. + repeated CASEncryptionResponse.KeyInfo entitlement_keys = 4; + optional License.KeyContainer.KeyType key_type = 5; + // A track type is used to represent a set of tracks that share the same + // content key and security level. Common values are SD, HD, UHD1, UHD2 + // and AUDIO. Content providers may use arbitrary strings for track type + // as long as they are consistent with the track types used at packaging + // time. + optional string track_type = 6; + // A Key Category Spec is used to identify if current key is generated for a + // single content or a group of contents. + optional License.KeyContainer.KeyCategorySpec key_category_spec = 7; + } + repeated ContentKeySpec content_key_specs = 4; + // Policy for the entire license such as playback duration. + optional License.Policy policy = 5; +} + +message CASEncryptionRequest { + optional bytes content_id = 1; + optional string provider = 2; + // Optional track types such as "AUDIO", SD" or "HD". + repeated string track_types = 3; + // Indicates if the client is using key rotation. If true, the server will + // return one key for EVEN and one key for ODD, otherwise only a single key is + // returned. + optional bool key_rotation = 4; + // Optional value which can be used to indicate a group. + // If present the CasEncryptionResponse will return key based on the group + // id. + optional bytes group_id = 5; +} + +message CASEncryptionResponse { + enum Status { + UNKNOWN = 0; + OK = 1; + SIGNATURE_FAILED = 2; + ACCESS_DENIED = 3; + INTERNAL_ERROR = 4; + INVALID_ARGUMENT = 5; + PROVIDER_ID_MISSING = 6; + CONTENT_ID_MISSING = 7; + TRACK_TYPE_MISSING = 8; + } + message KeyInfo { + enum KeySlot { + UNKNOWN = 0; + SINGLE = 1; + EVEN = 2; + ODD = 3; + }; + optional bytes key_id = 1; + optional bytes key = 2; + // Optional label used for the key. + optional string track_type = 3; + optional KeySlot key_slot = 4; + } + optional Status status = 1; + optional string status_message = 2; + optional bytes content_id = 3; + repeated KeyInfo entitlement_keys = 4; + // If keys shown in the encryption response are for group usage, this is the + // group identifier. + optional bytes group_id = 5; +} + +message SignedCASEncryptionRequest { + optional bytes request = 1; + optional bytes signature = 2; + // Identifies the entity sending / signing the request. + optional string signer = 3; +} + +message SignedCASEncryptionResponse { + // Serialized CASEncryptionResponse message. + optional bytes response = 1; + optional bytes signature = 2; +} + +message CASDrmLicenseResponse { + enum Status { + UNKNOWN = 0; + OK = 1; + SIGNATURE_FAILED = 2; + INVALID_LICENSE_CHALLENGE = 3; + PROVIDER_ID_MISSING = 4; + INVALID_CONTENT_INFO = 5; + EMPTY_CONTENT_INFO = 6; + CONTENT_ID_MISMATCH = 7; + MISSING_CONTENT_ID = 8; + MALFORMED_REQUEST = 9; + } + optional Status status = 1; + optional string status_message = 2; + // Serialzed bytes for a CAS license. + // TODO(hali): Until a CAS license protocol is defined, this field is a + // serialized License message defined in license_protocol.proto. + optional bytes license = 3; + // Actual SDK license status as defined in widevine/server/sdk/error.proto. + optional uint32 internal_status = 4; + // Indicates the type of message in the license response. + optional SignedMessage.MessageType message_type = 5; + // A subset of data from the Widevine PSSH. + message PsshData { + repeated bytes key_id = 1; + optional bytes content_id = 2; + } + optional PsshData pssh_data = 6; + optional SessionState session_state = 7; + optional string content_owner = 8; + optional string content_provider = 9; +} + +message SignedCASDrmRequest { + optional bytes request = 1; + optional bytes signature = 2; + // Identifies the entity sending / signing the request. Required if signature + // is present. + optional string signer = 3; + // The IP Address of the portal that is forwarding the request from the + // original sender. + optional string client_ip_address = 4; + // The client software identifier, as used by HTTP. + optional string user_agent = 5; + optional string provider = 6; +} + +message SignedCASDrmResponse { + optional bytes response = 1; + optional bytes signature = 2; +} diff --git a/protos/media_cas.proto b/protos/media_cas.proto new file mode 100644 index 0000000..4899adf --- /dev/null +++ b/protos/media_cas.proto @@ -0,0 +1,146 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +syntax = "proto2"; + +package video_widevine; + +option optimize_for = LITE_RUNTIME; + +// Widevine private data in the CA descriptor. +message CaDescriptorPrivateData { + // Provider name. + optional string provider = 1; + + // Content ID. + optional bytes content_id = 2; + + // Deprecated. + repeated bytes deprecated_entitlement_key_ids = 3; + + // The groups ids this channel belongs to. + repeated bytes group_ids = 4; +} + +// Widevine fingerprinting. +message Fingerprinting { + // Channels that will be applied with the controls. + repeated bytes channels = 1; + // Fingerprinting controls are opaque to Widevine. + optional bytes control = 2; +} + +// Widevine service blocking. +message ServiceBlocking { + // Channels that will be blocked. + repeated bytes channels = 1; + // Device groups that will be blocked. Group definition is opaque to Widevine. + repeated bytes device_groups = 2; + // Blocking start time in seconds since epoch. Start time is "immediate" if + // this field is not set. + optional int64 start_time_sec = 3; + // Required. Blocking end time in seconds since epoch. + optional int64 end_time_sec = 4; +} + +// The payload field for an EMM. +message EmmPayload { + repeated Fingerprinting fingerprinting = 1; + repeated ServiceBlocking service_blocking = 2; + // Epoch time in seconds. The time when the EMM is generated. + optional int64 timestamp_secs = 3; +} + +message SignedEmmPayload { + // Serialized EmmPayload. + optional bytes serialized_payload = 1; + // ECC (Elliptic Curve Cryptography) signature of |serialized_payload|. + optional bytes signature = 2; +} + +message EcmMetaData { + enum CipherMode { + UNSPECIFIED = 0; + AES_CBC = 1; + AES_CTR = 2; + DVB_CSA2 = 3; + DVB_CSA3 = 4; + AES_OFB = 5; + AES_SCTE52 = 6; + AES_ECB = 7; + } + // Required. The cipher mode used to encrypt/decrypt the content. + optional CipherMode cipher_mode = 1; + // Optional. The minimum age required to watch the content. The value + // represents actual age, with 0 means no restriction. + optional uint32 age_restriction = 2 [default = 0]; + // If specified, it means entitlement key rotation is enabled. The value will + // be included in the license request. The server is expected to return + // entitlement keys accordingly (e.g., keys for |entitlement_period_index| and + // |entitlement_period_index| + 1). + optional uint32 entitlement_period_index = 3; + // Used only if entitlement key rotation is enabled. This parameter controls + // the probability of requesting a new license by clients upon receiving this + // ECM. The purpose is to spread out requests to avoid request storms. A + // client will request a new license with possibility = 1 / + // |entitlement_rotation_window_left|. + optional uint32 entitlement_rotation_window_left = 4 [default = 1]; +} + +message EcmKeyData { + // The wrapped content key data (aka control word). + // Required. + optional bytes wrapped_key_data = 1; + // The ID of the entitlement key used to wrap the content key. The secure key + // data associated with this ID is held by the license server. The client gets + // the key from the license server through a license request. + // Required for the even key data, optional for the odd key data if it is the + // same as the even key data. + optional bytes entitlement_key_id = 2; + // IV for decrypting the wrapped_key_data. + // Required for the even key data, optional for the odd key data if it is the + // same as the even key data. + optional bytes wrapped_key_iv = 3; + // IV for decrypting the content stream. + // Optional. If not specified in the even key data, 8 bytes 0x00 will be used; + // If not specified in the odd key data, the same content iv in the even key + // data will be used. + optional bytes content_iv = 4; +} + +message EcmGroupKeyData { + // Group id of this key data. + optional bytes group_id = 1; + // Required. The key data for the even slot. Fields wrapped_key_iv and + // content_iv may be omitted if it is the same as EcmPayload.even_key_data. + optional EcmKeyData even_key_data = 2; + // Optional. The key data for the odd slot if key rotation is enabled. Fields + // wrapped_key_iv and content_iv may be omitted if it is the same as + // EcmPayload.odd_key_data. + optional EcmKeyData odd_key_data = 3; +} + +message EcmPayload { + // Required. Meta info carried by the ECM. + optional EcmMetaData meta_data = 1; + // Required. The key data for the even slot. + optional EcmKeyData even_key_data = 2; + // Optional. The key data for the odd slot if key rotation is enabled. + optional EcmKeyData odd_key_data = 3; + // Optional. Widevine fingerprinting information. + optional Fingerprinting fingerprinting = 4; + // Optional. Widevine service blocking information. + optional ServiceBlocking service_blocking = 5; + // If a channel belongs to a group, the content keys can additionally be + // encrypted by the group entitlement keys. + repeated EcmGroupKeyData group_key_data = 6; +} + +// The payload field for an ECM with signature. +message SignedEcmPayload { + // Serialized EcmPayload. + optional bytes serialized_payload = 1; + // ECC (Elliptic Curve Cryptography) signature of |serialized_payload|. + optional bytes signature = 2; +} \ No newline at end of file diff --git a/tests/Android.bp b/tests/Android.bp new file mode 100644 index 0000000..fef1369 --- /dev/null +++ b/tests/Android.bp @@ -0,0 +1,42 @@ +cc_binary { + name: "wv_cas_tests", + proprietary: true, + srcs: [ + "src/cas_license_test.cpp", + "src/cas_session_map_test.cpp", + "src/crypto_session_test.cpp", + "src/ecm_parser_test.cpp", + "src/ecm_parser_v2_test.cpp", + "src/ecm_parser_v3_test.cpp", + "src/emm_parser_test.cpp", + "src/license_key_status_test.cpp", + "src/policy_engine_test.cpp", + "src/test_properties.cpp", + "src/timer_test.cpp", + "src/widevine_cas_api_test.cpp", + "src/widevine_cas_session_test.cpp", + "src/widevine_media_cas_plugin_test.cpp", + "src/wv_cas_test_main.cpp", + ], + header_libs: [ + "//vendor/widevine/libwvmediacas/oemcrypto:oemcastroheaders", + "media_plugin_headers", + ], + static_libs: [ + "//vendor/widevine/libwvmediacas/wvutil:libcasutil", + "//vendor/widevine/libwvmediacas/plugin:libwvcasplugins", + "//vendor/widevine/libwvmediacas/protos:libcas_protos", + "libgmock", + "libgtest", + ], + shared_libs: [ + "libcrypto", + "libutils", + "liblog", + "libprotobuf-cpp-lite", + "libhidlbase", + ], + proto: { + type: "lite", + }, +} diff --git a/tests/src/cas_license_test.cpp b/tests/src/cas_license_test.cpp new file mode 100644 index 0000000..aa32ad2 --- /dev/null +++ b/tests/src/cas_license_test.cpp @@ -0,0 +1,777 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#include "cas_license.h" + +#include +#include +#include + +#include + +#include "cas_status.h" +#include "cas_util.h" +#include "device_files.pb.h" +#include "license_protocol.pb.h" +#include "mock_crypto_session.h" +#include "string_conversions.h" + +namespace wvcas { +namespace { + +using ::testing::_; +using ::testing::AllOf; +using ::testing::DoAll; +using ::testing::Eq; +using ::testing::Invoke; +using ::testing::IsNull; +using ::testing::NotNull; +using ::testing::Pointee; +using ::testing::Return; +using ::testing::ReturnRef; +using ::testing::SetArgPointee; +using ::testing::SetArgReferee; +using ::testing::StrictMock; + +using wvutil::Base64Decode; +using wvutil::Base64SafeDecode; +using wvutil::Base64SafeEncodeNoPad; + +using video_widevine_client::sdk::DeviceCertificate; +using video_widevine_client::sdk::File; +using video_widevine_client::sdk::HashedFile; + +static constexpr char kKeyboxToken[] = "KeyBoxToken"; +static constexpr char kExpectedRenewalRequest[] = "ExpectedRenewalRequest"; +static constexpr char kExpectedSignature[] = "ExpectedSignature"; +static constexpr char kDeviceRsaKey[] = "DeviceRSAKeyDeviceRSAKeyDeviceRSAKey"; +static constexpr char kDeviceRsaKeyIV[] = "0123456789abcdef"; +static constexpr char kInitializationData[] = "CasInitializationData"; +static constexpr char kSessionKey[] = "fedcba9876543210"; +static constexpr uint32_t kNonce = 0x5555; +static constexpr size_t kExpectedKeyboxSizeBytes = 72; +static constexpr char kJsonStartSubstr[] = "\"signedResponse\": \""; +static constexpr char kJsonEndSubstr[] = "\""; +static constexpr char kKeyIDVideo[] = "KeyIdVideo"; +static constexpr char kKeyIDAudio[] = "KeyIdAudio"; +static constexpr char kKeyVideoIV[] = "KeyVideoIV"; +static constexpr char kKeyAudioIV[] = "KeyAudioIV"; +static constexpr char kKeyVideo[] = "KeyVideo"; +static constexpr char kKeyAudio[] = "KeyAudio"; +static constexpr char kKeyControlVideo[] = "KeyControlVideo"; +static constexpr char kKeyControlAudio[] = "KeyControlAudio"; +static constexpr char kKeyControlIVVideo[] = "KeyControlIVVideo"; +static constexpr char kKeyControlIVAudio[] = "KeyControlIVAudio"; +static constexpr char kTrackTypeVideo[] = "Video"; +static constexpr char kTrackTypeAudio[] = "Audio"; +static constexpr char kKeyCompanyName[] = "company_name"; +static constexpr char kKeyModelName[] = "model_name"; +static constexpr char kKeyArchitectureName[] = "architecture_name"; +static constexpr char kKeyDeviceName[] = "device_name"; +static constexpr char kKeyProductName[] = "product_name"; +static constexpr char kKeyBuildInfo[] = "build_info"; +static constexpr char kKeyDeviceId[] = "device_id"; +static constexpr char kKeyOemCryptoSecurityPatchLevel[] = + "oem_crypto_security_patch_level"; +static constexpr char kRenewalSereverURL[] = "ExpectedRenewalURL"; +static constexpr char kCoreMessage[] = "CoreMessage"; + +typedef StrictMock StrictMockCryptoSession; + +class MockPolicyEngine : public wvcas::PolicyEngine { + public: + MockPolicyEngine() {} + ~MockPolicyEngine() override {} + MOCK_METHOD2(initialize, + void(std::shared_ptr crypto_session, + wvcas::CasEventListener* event_listener)); + MOCK_METHOD1(SetLicense, void(const video_widevine::License& license)); + MOCK_METHOD1(UpdateLicense, void(const video_widevine::License& license)); + MOCK_CONST_METHOD0(CanRenew, bool()); + MOCK_CONST_METHOD0(renewal_server_url, const std::string&()); + MOCK_CONST_METHOD0(IsExpired, bool()); + MOCK_CONST_METHOD0(CanPersist, bool()); + MOCK_CONST_METHOD0(always_include_client_id, bool()); +}; +typedef StrictMock StrictMockPolicyEngine; + +class TestCasLicense : public wvcas::CasLicense { + public: + explicit TestCasLicense() {} + ~TestCasLicense() override{}; + std::unique_ptr GetPolicyEngine() override { + policy_engine_ = pass_thru_.get(); + return std::move(pass_thru_); + } + std::unique_ptr pass_thru_ = + make_unique(); + StrictMockPolicyEngine* policy_engine_ = pass_thru_.get(); +}; + +class CasLicenseTest : public ::testing::TestWithParam { + public: + CasLicenseTest() {} + virtual ~CasLicenseTest() {} + void SetKeyboxData(uint8_t* keyData, size_t* keyDataLength) { + ASSERT_EQ(kExpectedKeyboxSizeBytes, *keyDataLength); + memset(keyData, 0, *keyDataLength); + memcpy(keyData, kKeyboxToken, 11); + } + + std::string CreateProvisioningResponse(); + + std::shared_ptr strict_mock_; + std::string wrapped_rsa_key_; + std::string device_certificate_; +}; + +std::string CasLicenseTest::CreateProvisioningResponse() { + video_widevine::SignedProvisioningMessage signed_message; + signed_message.set_signature(kExpectedSignature); + signed_message.set_oemcrypto_core_message(kCoreMessage); + + video_widevine::ProvisioningResponse response; + std::string* nonce = response.mutable_nonce(); + nonce->resize(4); + memcpy(&nonce->at(0), &kNonce, 4); + + response.set_device_rsa_key(kDeviceRsaKey); + response.set_device_rsa_key_iv(kDeviceRsaKeyIV); + response.SerializeToString(signed_message.mutable_message()); + + std::vector b64_message(signed_message.ByteSize()); + signed_message.SerializeToArray(&b64_message[0], b64_message.size()); + + return kJsonStartSubstr + Base64SafeEncodeNoPad(b64_message) + kJsonEndSubstr; +} + +bool Hash(const std::string& data, std::string* hash) { + if (!hash) return false; + hash->resize(SHA256_DIGEST_LENGTH); + const unsigned char* input = + reinterpret_cast(data.data()); + unsigned char* output = reinterpret_cast(&(*hash)[0]); + SHA256(input, data.size(), output); + return true; +} + +std::string CreateEntitlementResponse() { + video_widevine::License license; + auto* key = license.add_key(); + key->set_type(video_widevine::License_KeyContainer::ENTITLEMENT); + + video_widevine::SignedMessage signed_message; + license.SerializeToString(signed_message.mutable_msg()); + signed_message.set_type(video_widevine::SignedMessage::CAS_LICENSE); + signed_message.set_signature(kExpectedSignature); + signed_message.set_session_key(kExpectedSignature); + signed_message.set_oemcrypto_core_message(kCoreMessage); + return signed_message.SerializeAsString(); +} + +std::string CreateEntitlementRenewalResponse() { + video_widevine::License license; + video_widevine::SignedMessage signed_message; + license.SerializeToString(signed_message.mutable_msg()); + signed_message.set_type(video_widevine::SignedMessage::CAS_LICENSE); + signed_message.set_signature(kExpectedSignature); + signed_message.set_oemcrypto_core_message(kCoreMessage); + return signed_message.SerializeAsString(); +} + +std::string CreateLicenseFileData() { + File file; + file.set_type(File::LICENSE); + + video_widevine::SignedMessage license_request; + license_request.set_msg("license_request"); + license_request.set_signature("license_request_signature"); + license_request.set_type(video_widevine::SignedMessage::CAS_LICENSE_REQUEST); + + video_widevine::SignedMessage renewal_request; + renewal_request.set_msg("renewal_request"); + renewal_request.set_signature("renewal_request_signature"); + renewal_request.set_type(video_widevine::SignedMessage::CAS_LICENSE_REQUEST); + + video_widevine_client::sdk::License* license = file.mutable_license(); + license_request.SerializeToString(license->mutable_license_request()); + license->set_license(CreateEntitlementResponse()); + renewal_request.SerializeToString(license->mutable_renewal_request()); + license->set_renewal(CreateEntitlementRenewalResponse()); + + HashedFile hashed_file; + file.SerializeToString(hashed_file.mutable_file()); + Hash(hashed_file.file(), hashed_file.mutable_hash()); + + return hashed_file.SerializeAsString(); +} + +TEST_F(CasLicenseTest, GenerateDeviceProvisioningRequest) { + strict_mock_ = std::make_shared(); + TestCasLicense cas_license; + EXPECT_CALL(*cas_license.policy_engine_, initialize(_, _)); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + cas_license.initialize(strict_mock_, nullptr).status_code()); + + std::string serialized_provisioning_request; + std::string expected_signature(kExpectedSignature); + + EXPECT_CALL(*strict_mock_, provisioning_method()) + .WillRepeatedly(Return(wvcas::Keybox)); + EXPECT_CALL(*strict_mock_, GetKeyData(NotNull(), NotNull())) + .WillOnce(DoAll(Invoke(this, &CasLicenseTest::SetKeyboxData), + Return(wvcas::CasStatusCode::kNoError))); + EXPECT_CALL(*strict_mock_, supported_certificates()) + .WillOnce(Return(wvcas::SupportedCertificates(0x13))); + EXPECT_CALL(*strict_mock_, GenerateNonce(NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(kNonce), + Return(wvcas::CasStatusCode::kNoError))); + EXPECT_CALL(*strict_mock_, GenerateDerivedKeys(_, _, _, _)) + .WillOnce(Return(wvcas::CasStatusCode::kNoError)); + EXPECT_CALL(*strict_mock_, + PrepareAndSignProvisioningRequest(_, NotNull(), NotNull(), _, _)) + .WillOnce(DoAll(SetArgPointee<2>(kExpectedSignature), + SetArgReferee<3>(false), + Return(wvcas::CasStatusCode::kNoError))); + EXPECT_CALL(*strict_mock_, GetDeviceID(NotNull())) + .WillOnce(Return(wvcas::CasStatusCode::kNoError)); + + wvcas::CasStatus status = cas_license.GenerateDeviceProvisioningRequest( + &serialized_provisioning_request); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, status.status_code()); + + // Verify the provisioning request. + video_widevine::SignedProvisioningMessage signed_message; + auto data = Base64Decode(serialized_provisioning_request); + EXPECT_TRUE(signed_message.ParseFromArray(&data[0], data.size())); + EXPECT_EQ(kExpectedSignature, signed_message.signature()); + EXPECT_EQ(video_widevine::SignedProvisioningMessage::PROVISIONING_20, + signed_message.protocol_version()); + + video_widevine::ProvisioningRequest provisioning_request; + EXPECT_TRUE(provisioning_request.ParseFromString(signed_message.message())); + + auto& client_id = provisioning_request.client_id(); + EXPECT_EQ(video_widevine::ClientIdentification::KEYBOX, client_id.type()); + std::string token(kKeyboxToken); + token.resize(kExpectedKeyboxSizeBytes, 0); + EXPECT_EQ(token, client_id.token()); + + ASSERT_EQ(sizeof(uint32_t), provisioning_request.nonce().size()); + EXPECT_EQ(kNonce, *reinterpret_cast( + provisioning_request.nonce().data())); +} + +TEST_F(CasLicenseTest, HandleProvisioningResponse) { + const std::string provisioning_response = CreateProvisioningResponse(); + + strict_mock_ = std::make_shared(); + TestCasLicense cas_license; + EXPECT_CALL(*cas_license.policy_engine_, initialize(_, _)); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + cas_license.initialize(strict_mock_, nullptr).status_code()); + + EXPECT_CALL(*strict_mock_, reset()) + .WillOnce(Return(wvcas::CasStatusCode::kNoError)); + EXPECT_CALL(*strict_mock_, LoadProvisioning(_, _, _, _)); + + wvcas::CasStatus status = cas_license.HandleDeviceProvisioningResponse( + provisioning_response, &device_certificate_, &wrapped_rsa_key_, nullptr); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, status.status_code()); +} + +TEST_F(CasLicenseTest, HandleProvisioningResponseWithDeviceFileOutput) { + const std::string provisioning_response = CreateProvisioningResponse(); + + strict_mock_ = std::make_shared(); + TestCasLicense cas_license; + EXPECT_CALL(*cas_license.policy_engine_, initialize(_, _)); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + cas_license.initialize(strict_mock_, nullptr).status_code()); + + EXPECT_CALL(*strict_mock_, reset()) + .WillOnce(Return(wvcas::CasStatusCode::kNoError)); + EXPECT_CALL(*strict_mock_, LoadProvisioning(_, _, _, _)); + + std::string device_cert_file; + wvcas::CasStatus status = cas_license.HandleDeviceProvisioningResponse( + provisioning_response, &device_certificate_, &wrapped_rsa_key_, + &device_cert_file); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, status.status_code()); + EXPECT_FALSE(device_cert_file.empty()); +} + +TEST_F(CasLicenseTest, GenerateEntitlementRequest) { + strict_mock_ = std::make_shared(); + TestCasLicense cas_license; + EXPECT_CALL(*cas_license.policy_engine_, initialize(_, _)); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + cas_license.initialize(strict_mock_, nullptr).status_code()); + + EXPECT_CALL(*strict_mock_, GetOEMPublicCertificate(_, _)) + .WillOnce(Return(wvcas::CasStatusCode::kCryptoSessionError)); + + EXPECT_CALL(*strict_mock_, APIVersion(_)) + .WillOnce(Return(wvcas::CasStatusCode::kNoError)); + + EXPECT_CALL(*strict_mock_, GenerateNonce(_)) + .WillOnce(Return(wvcas::CasStatusCode::kNoError)); + EXPECT_CALL(*strict_mock_, + PrepareAndSignLicenseRequest(_, NotNull(), NotNull(), _, _)) + .WillOnce(DoAll(SetArgPointee<2>(kExpectedSignature), + SetArgReferee<3>(false), + Return(wvcas::CasStatusCode::kNoError))); + EXPECT_CALL(*strict_mock_, LoadDeviceRSAKey(_, _)); + + std::string serialized_entitlement_request; + wvcas::CasStatus status = cas_license.GenerateEntitlementRequest( + kInitializationData, device_certificate_, wrapped_rsa_key_, + wvcas::LicenseType::kStreaming, &serialized_entitlement_request); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, status.status_code()); +} + +TEST_F(CasLicenseTest, HandleEntitlementResponse) { + strict_mock_ = std::make_shared(); + + TestCasLicense cas_license; + EXPECT_CALL(*cas_license.policy_engine_, initialize(_, _)); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + cas_license.initialize(strict_mock_, nullptr).status_code()); + + EXPECT_CALL(*strict_mock_, GetOEMPublicCertificate(_, _)) + .WillOnce(Return(wvcas::CasStatusCode::kCryptoSessionError)); + EXPECT_CALL(*strict_mock_, APIVersion(_)) + .WillOnce(Return(wvcas::CasStatusCode::kNoError)); + + EXPECT_CALL(*strict_mock_, GenerateNonce(_)) + .WillOnce(Return(wvcas::CasStatusCode::kNoError)); + EXPECT_CALL(*strict_mock_, + PrepareAndSignLicenseRequest(_, NotNull(), NotNull(), _, _)) + .WillOnce(DoAll(SetArgPointee<2>(kExpectedSignature), + SetArgReferee<3>(false), + Return(wvcas::CasStatusCode::kNoError))); + EXPECT_CALL(*strict_mock_, LoadDeviceRSAKey(_, _)); + + std::string serialized_entitlement_request; + wvcas::CasStatus status = cas_license.GenerateEntitlementRequest( + kInitializationData, device_certificate_, wrapped_rsa_key_, + wvcas::LicenseType::kStreaming, &serialized_entitlement_request); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, status.status_code()); + + video_widevine::SignedMessage signed_message; + ASSERT_TRUE(signed_message.ParseFromString(serialized_entitlement_request)); + + const std::string entitlement_response = CreateEntitlementResponse(); + + EXPECT_CALL(*strict_mock_, DeriveKeysFromSessionKey(_, _, _, _, _, _)) + .WillRepeatedly(Return(wvcas::CasStatusCode::kNoError)); + EXPECT_CALL(*strict_mock_, LoadLicense(_, _, _)) + .WillRepeatedly(Return(wvcas::CasStatusCode::kNoError)); + + // Valid. + EXPECT_CALL(*cas_license.policy_engine_, SetLicense(_)); + EXPECT_CALL(*cas_license.policy_engine_, CanPersist()) + .WillOnce(Return(false)); + status = cas_license.HandleEntitlementResponse(entitlement_response, + /*device_file=*/nullptr); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, status.status_code()); + // Not a group license. + EXPECT_TRUE(cas_license.GetGroupId().empty()); + EXPECT_TRUE(cas_license.GetContentIdList().empty()); + EXPECT_FALSE(cas_license.IsGroupLicense()); + EXPECT_FALSE(cas_license.IsMultiContentLicense()); + + // Valid with device file. + EXPECT_CALL(*cas_license.policy_engine_, SetLicense(_)); + EXPECT_CALL(*cas_license.policy_engine_, CanPersist()) + .WillOnce(Return(false)); + std::string device_file; + status = + cas_license.HandleEntitlementResponse(entitlement_response, &device_file); + EXPECT_TRUE(device_file.empty()); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, status.status_code()); + + // Valid with device file and can_persist = true. + EXPECT_CALL(*cas_license.policy_engine_, SetLicense(_)); + EXPECT_CALL(*cas_license.policy_engine_, CanPersist()).WillOnce(Return(true)); + status = + cas_license.HandleEntitlementResponse(entitlement_response, &device_file); + EXPECT_FALSE(device_file.empty()); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, status.status_code()); + + HashedFile hashed_file; + ASSERT_TRUE(hashed_file.ParseFromString(device_file)); + std::string hash; + Hash(hashed_file.file(), &hash); + EXPECT_EQ(std::vector(hash.begin(), hash.end()), + std::vector(hashed_file.hash().begin(), + hashed_file.hash().end())); + + File file; + ASSERT_TRUE(file.ParseFromString(hashed_file.file())); + + const auto& license_file = file.license(); + EXPECT_FALSE(license_file.license_request().empty()); + EXPECT_FALSE(license_file.license().empty()); + EXPECT_TRUE(license_file.renewal_request().empty()); + EXPECT_TRUE(license_file.renewal().empty()); + + std::string emm_request = *signed_message.mutable_msg(); + EXPECT_EQ(std::vector(emm_request.begin(), emm_request.end()), + std::vector(license_file.license_request().begin(), + license_file.license_request().end())); + EXPECT_EQ(std::vector(entitlement_response.begin(), + entitlement_response.end()), + std::vector(license_file.license().begin(), + license_file.license().end())); +} + +TEST_P(CasLicenseTest, GenerateRenewalRequest) { + strict_mock_ = std::make_shared(); + TestCasLicense cas_license; + EXPECT_CALL(*cas_license.policy_engine_, initialize(_, _)); + EXPECT_CALL(*cas_license.policy_engine_, always_include_client_id()) + .WillOnce(Return(GetParam())); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + cas_license.initialize(strict_mock_, nullptr).status_code()); + + std::string serialized_renewal_request; + + // Policy can_renew is false. + EXPECT_CALL(*cas_license.policy_engine_, CanRenew()).WillOnce(Return(false)); + EXPECT_NE(wvcas::CasStatusCode::kNoError, + cas_license + .GenerateEntitlementRenewalRequest(device_certificate_, + &serialized_renewal_request) + .status_code()); + + // Policy can_renew is true. + EXPECT_CALL(*cas_license.policy_engine_, CanRenew()).WillOnce(Return(true)); + EXPECT_CALL(*strict_mock_, + PrepareAndSignRenewalRequest(_, NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<2>(kExpectedSignature), + Return(wvcas::CasStatusCode::kNoError))); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + cas_license + .GenerateEntitlementRenewalRequest(device_certificate_, + &serialized_renewal_request) + .status_code()); + video_widevine::SignedMessage signed_message; + ASSERT_TRUE(signed_message.ParseFromString(serialized_renewal_request)); + video_widevine::LicenseRequest license_request; + ASSERT_TRUE(license_request.ParseFromString(signed_message.msg())); + if (GetParam()) { + // Always include client id == true. + ASSERT_TRUE(license_request.has_client_id()); + EXPECT_EQ(device_certificate_, license_request.client_id().token()); + } else { + ASSERT_FALSE(license_request.has_client_id()); + } +} + +// Test renewal request generation with always_include_client_id == true and +// false; +// Suppress warning "INSTANTIATE_TEST_CASE_P is deprecated". +// TODO(b/142954362): Remove the suppression once gtest on pi-tv-dev branch +// updates to the latest version. +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +INSTANTIATE_TEST_CASE_P(GenerateRenewalRequest, CasLicenseTest, + ::testing::Bool()); +#pragma GCC diagnostic pop + +TEST_F(CasLicenseTest, HandleRenewalResponse) { + strict_mock_ = std::make_shared(); + TestCasLicense cas_license; + EXPECT_CALL(*cas_license.policy_engine_, initialize(_, _)); + EXPECT_CALL(*cas_license.policy_engine_, always_include_client_id()) + .WillOnce(Return(false)); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + cas_license.initialize(strict_mock_, nullptr).status_code()); + + std::string serialized_renewal_request; + + // Policy can_renew is false. + EXPECT_CALL(*cas_license.policy_engine_, CanRenew()).WillOnce(Return(false)); + EXPECT_NE(wvcas::CasStatusCode::kNoError, + cas_license + .GenerateEntitlementRenewalRequest(device_certificate_, + &serialized_renewal_request) + .status_code()); + + const std::string renewal_response = CreateEntitlementRenewalResponse(); + + // Policy can_renew is true. Set expectations to build a renewal request. + EXPECT_CALL(*cas_license.policy_engine_, CanRenew()).WillOnce(Return(true)); + EXPECT_CALL(*strict_mock_, + PrepareAndSignRenewalRequest(_, NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<2>(kExpectedSignature), + Return(wvcas::CasStatusCode::kNoError))); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + cas_license + .GenerateEntitlementRenewalRequest(device_certificate_, + &serialized_renewal_request) + .status_code()); + + video_widevine::SignedMessage signed_message; + ASSERT_TRUE(signed_message.ParseFromString(serialized_renewal_request)); + + std::string device_file; + EXPECT_CALL(*strict_mock_, LoadRenewal(_, _, _)) + .WillRepeatedly(Return(wvcas::CasStatusCode::kNoError)); + // Policy can_persist is false. no file information is generated. + EXPECT_CALL(*cas_license.policy_engine_, UpdateLicense(_)); + EXPECT_CALL(*cas_license.policy_engine_, CanPersist()) + .WillOnce(Return(false)); + EXPECT_EQ( + wvcas::CasStatusCode::kNoError, + cas_license + .HandleEntitlementRenewalResponse(renewal_response, &device_file) + .status_code()); + EXPECT_TRUE(device_file.empty()); + + // Policy can_persist is true. Validate that device_file is populated with a + // valid license file. + EXPECT_CALL(*cas_license.policy_engine_, CanPersist()).WillOnce(Return(true)); + EXPECT_CALL(*cas_license.policy_engine_, UpdateLicense(_)); + ASSERT_EQ( + wvcas::CasStatusCode::kNoError, + cas_license + .HandleEntitlementRenewalResponse(renewal_response, &device_file) + .status_code()); + ASSERT_FALSE(device_file.empty()); + + HashedFile hashed_file; + ASSERT_TRUE(hashed_file.ParseFromString(device_file)); + std::string hash; + Hash(hashed_file.file(), &hash); + EXPECT_EQ(std::vector(hash.begin(), hash.end()), + std::vector(hashed_file.hash().begin(), + hashed_file.hash().end())); + + File file; + ASSERT_TRUE(file.ParseFromString(hashed_file.file())); + + const auto& license_file = file.license(); + EXPECT_TRUE(license_file.license_request().empty()); + EXPECT_TRUE(license_file.license().empty()); + EXPECT_FALSE(license_file.renewal_request().empty()); + EXPECT_FALSE(license_file.renewal().empty()); + + std::string renewal_request = *signed_message.mutable_msg(); + EXPECT_EQ( + std::vector(renewal_request.begin(), renewal_request.end()), + std::vector(license_file.renewal_request().begin(), + license_file.renewal_request().end())); + EXPECT_EQ( + std::vector(renewal_response.begin(), renewal_response.end()), + std::vector(license_file.renewal().begin(), + license_file.renewal().end())); +} + +TEST_F(CasLicenseTest, IsExpired) { + TestCasLicense cas_license; + strict_mock_ = std::make_shared(); + EXPECT_CALL(*cas_license.policy_engine_, initialize(_, _)); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + cas_license.initialize(strict_mock_, nullptr).status_code()); + EXPECT_CALL(*cas_license.policy_engine_, IsExpired()) + .WillOnce(Return(false)) + .WillOnce(Return(true)); + + EXPECT_FALSE(cas_license.IsExpired()); + EXPECT_TRUE(cas_license.IsExpired()); +} + +TEST_F(CasLicenseTest, RestoreLicense) { + TestCasLicense cas_license; + strict_mock_ = std::make_shared(); + EXPECT_CALL(*cas_license.policy_engine_, initialize(_, _)); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + cas_license.initialize(strict_mock_, nullptr).status_code()); + + std::string license_file_data = CreateLicenseFileData(); + EXPECT_CALL(*strict_mock_, LoadDeviceRSAKey(_, _)); + EXPECT_CALL(*strict_mock_, DeriveKeysFromSessionKey(_, _, _, _, _, _)) + .WillRepeatedly(Return(wvcas::CasStatusCode::kNoError)); + EXPECT_CALL(*strict_mock_, LoadLicense(_, _, _)) + .WillRepeatedly(Return(wvcas::CasStatusCode::kNoError)); + EXPECT_CALL(*strict_mock_, LoadRenewal(_, _, _)) + .WillRepeatedly(Return(wvcas::CasStatusCode::kNoError)); + EXPECT_CALL(*cas_license.policy_engine_, SetLicense(_)); + EXPECT_CALL(*cas_license.policy_engine_, UpdateLicense(_)); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + cas_license.HandleStoredLicense(wrapped_rsa_key_, license_file_data) + .status_code()); +} + +TEST_F(CasLicenseTest, HandleMultiContentEntitlementResponse) { + strict_mock_ = std::make_shared(); + TestCasLicense cas_license; + EXPECT_CALL(*cas_license.policy_engine_, initialize(_, _)); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + cas_license.initialize(strict_mock_, nullptr).status_code()); + EXPECT_CALL(*strict_mock_, GetOEMPublicCertificate(_, _)) + .WillOnce(Return(wvcas::CasStatusCode::kCryptoSessionError)); + EXPECT_CALL(*strict_mock_, APIVersion(_)) + .WillOnce(Return(wvcas::CasStatusCode::kNoError)); + EXPECT_CALL(*strict_mock_, GenerateNonce(_)) + .WillOnce(Return(wvcas::CasStatusCode::kNoError)); + EXPECT_CALL(*strict_mock_, + PrepareAndSignLicenseRequest(_, NotNull(), NotNull(), _, _)) + .WillOnce(DoAll(SetArgPointee<2>(kExpectedSignature), + SetArgReferee<3>(false), + Return(wvcas::CasStatusCode::kNoError))); + EXPECT_CALL(*strict_mock_, LoadDeviceRSAKey(_, _)); + + std::string serialized_entitlement_request; + wvcas::CasStatus status = cas_license.GenerateEntitlementRequest( + kInitializationData, device_certificate_, wrapped_rsa_key_, + wvcas::LicenseType::kStreaming, &serialized_entitlement_request); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, status.status_code()); + + // Create multi content entitlement response. + video_widevine::LicenseCategorySpec license_category_spec; + license_category_spec.set_license_category( + video_widevine::LicenseCategorySpec::MULTI_CONTENT_LICENSE); + license_category_spec.set_group_id("group_id"); + video_widevine::License license; + *license.mutable_license_category_spec() = license_category_spec; + video_widevine::License::KeyContainer::KeyCategorySpec key_category_spec; + key_category_spec.set_content_id("content_id_1"); + auto* key = license.add_key(); + key->set_type(video_widevine::License_KeyContainer::ENTITLEMENT); + *key->mutable_key_category_spec() = key_category_spec; + key = license.add_key(); + key->set_type(video_widevine::License_KeyContainer::ENTITLEMENT); + *key->mutable_key_category_spec() = key_category_spec; + key = license.add_key(); + key->set_type(video_widevine::License_KeyContainer::ENTITLEMENT); + key_category_spec.set_content_id("content_id_2"); + *key->mutable_key_category_spec() = key_category_spec; + + video_widevine::SignedMessage signed_message; + license.SerializeToString(signed_message.mutable_msg()); + signed_message.set_type(video_widevine::SignedMessage::CAS_LICENSE); + signed_message.set_signature(kExpectedSignature); + signed_message.set_session_key(kExpectedSignature); + signed_message.set_oemcrypto_core_message(kCoreMessage); + std::string entitlement_response = signed_message.SerializeAsString(); + + EXPECT_CALL(*strict_mock_, DeriveKeysFromSessionKey(_, _, _, _, _, _)) + .WillRepeatedly(Return(wvcas::CasStatusCode::kNoError)); + EXPECT_CALL(*strict_mock_, LoadLicense(_, _, _)) + .WillRepeatedly(Return(wvcas::CasStatusCode::kNoError)); + EXPECT_CALL(*cas_license.policy_engine_, SetLicense(_)); + EXPECT_CALL(*cas_license.policy_engine_, CanPersist()).WillOnce(Return(true)); + + status = cas_license.HandleEntitlementResponse(entitlement_response, + /*device_file=*/nullptr); + EXPECT_EQ(status.status_code(), wvcas::CasStatusCode::kNoError); + // Not a group license. + EXPECT_EQ(cas_license.GetGroupId(), "group_id"); + EXPECT_THAT(cas_license.GetContentIdList(), + testing::ElementsAre("content_id_1", "content_id_2")); + EXPECT_FALSE(cas_license.IsGroupLicense()); + EXPECT_TRUE(cas_license.IsMultiContentLicense()); +} + +TEST(GetEntitlementPeriodIndexTest, ValidIndexSuccess) { + uint32_t expected_index = 123; + // Create a valid pssh with entitlement key period index. + video_widevine::WidevinePsshData pssh; + pssh.set_entitlement_period_index(expected_index); + // Create a license request containing the pssh. + video_widevine::LicenseRequest license_request; + license_request.mutable_content_id()->mutable_cenc_id_deprecated()->add_pssh( + pssh.SerializeAsString()); + // Create a license file. + File file; + file.set_type(File::LICENSE); + license_request.SerializeToString( + file.mutable_license()->mutable_license_request()); + // Hash the created file + HashedFile hashed_file; + file.SerializeToString(hashed_file.mutable_file()); + Hash(hashed_file.file(), hashed_file.mutable_hash()); + uint32_t actual_index; + + EXPECT_TRUE(CasLicense::GetEntitlementPeriodIndexFromStoredLicense( + hashed_file.SerializeAsString(), actual_index) + .ok()); + + EXPECT_EQ(actual_index, expected_index); +} + +TEST(GetEntitlementPeriodIndexTest, PsshHasNoIndexFail) { + // Create a valid pssh without entitlement key period index. + video_widevine::WidevinePsshData pssh; + pssh.set_content_id("content_id"); + // Create a license request containing the pssh. + video_widevine::LicenseRequest license_request; + license_request.mutable_content_id()->mutable_cenc_id_deprecated()->add_pssh( + pssh.SerializeAsString()); + // Create a license file. + File file; + file.set_type(File::LICENSE); + license_request.SerializeToString( + file.mutable_license()->mutable_license_request()); + // Hash the created file + HashedFile hashed_file; + file.SerializeToString(hashed_file.mutable_file()); + Hash(hashed_file.file(), hashed_file.mutable_hash()); + uint32_t actual_index; + + EXPECT_FALSE(CasLicense::GetEntitlementPeriodIndexFromStoredLicense( + hashed_file.SerializeAsString(), actual_index) + .ok()); +} + +TEST(GetEntitlementPeriodIndexTest, NoLicenseDataFail) { + File file; + file.set_type(File::LICENSE); + // Hash the created file + HashedFile hashed_file; + file.SerializeToString(hashed_file.mutable_file()); + Hash(hashed_file.file(), hashed_file.mutable_hash()); + uint32_t actual_index; + + EXPECT_FALSE(CasLicense::GetEntitlementPeriodIndexFromStoredLicense( + hashed_file.SerializeAsString(), actual_index) + .ok()); +} + +TEST(GetEntitlementPeriodIndexTest, InvalidHashFail) { + // Create a valid pssh with entitlement key period index. + video_widevine::WidevinePsshData pssh; + pssh.set_entitlement_period_index(123); + // Create a license request containing the pssh. + video_widevine::LicenseRequest license_request; + license_request.mutable_content_id()->mutable_cenc_id_deprecated()->add_pssh( + pssh.SerializeAsString()); + // Create a license file. + File file; + file.set_type(File::LICENSE); + license_request.SerializeToString( + file.mutable_license()->mutable_license_request()); + // Hash the created file + HashedFile hashed_file; + file.SerializeToString(hashed_file.mutable_file()); + hashed_file.set_hash("invalid_hash"); + uint32_t actual_index; + + EXPECT_FALSE(CasLicense::GetEntitlementPeriodIndexFromStoredLicense( + hashed_file.SerializeAsString(), actual_index) + .ok()); +} + +TEST(GetEntitlementPeriodIndexTest, InvalidFileFail) { + uint32_t actual_index; + EXPECT_FALSE(CasLicense::GetEntitlementPeriodIndexFromStoredLicense( + "invalid file", actual_index) + .ok()); +} + +} // namespace +} // namespace wvcas \ No newline at end of file diff --git a/tests/src/cas_session_map_test.cpp b/tests/src/cas_session_map_test.cpp new file mode 100644 index 0000000..8e31f88 --- /dev/null +++ b/tests/src/cas_session_map_test.cpp @@ -0,0 +1,25 @@ +#include +#include + +#include + +#include "widevine_cas_session.h" +#include "widevine_cas_session_map.h" + +using wvcas::CasSessionPtr; +using wvcas::WidevineCasSession; +using wvcas::WidevineCasSessionMap; + +TEST(WidevineCasSessionMap, HappyPath) { + std::vector base_key = {0x01, 0x02, 0x03}; + WidevineCasSessionMap& map = WidevineCasSessionMap::instance(); + CasSessionPtr session = std::make_shared(); + + EXPECT_TRUE(map.AddSession(base_key, session)); + EXPECT_FALSE(map.AddSession(base_key, session)); + + EXPECT_EQ(map.GetSession(base_key).get(), session.get()); + + map.RemoveSession(base_key); + EXPECT_EQ(map.GetSession(base_key).get(), nullptr); +} diff --git a/tests/src/crypto_session_test.cpp b/tests/src/crypto_session_test.cpp new file mode 100644 index 0000000..c444d2b --- /dev/null +++ b/tests/src/crypto_session_test.cpp @@ -0,0 +1,1057 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#include "crypto_session.h" + +#include +#include + +#include + +#include "cas_util.h" +#include "license_protocol.pb.h" +#include "string_conversions.h" + +namespace wvcas { +namespace { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::Eq; +using ::testing::InSequence; +using ::testing::IsNull; +using ::testing::NiceMock; +using ::testing::NotNull; +using ::testing::Pointee; +using ::testing::Return; +using ::testing::SetArgPointee; +using ::testing::StrictMock; + +using wvutil::a2b_hex; + +static const OEMCrypto_SESSION kOemcSessionId = 0x1234; +static const OEMCrypto_SESSION kOemcSessionId2 = 0x1235; +static const OEMCrypto_SESSION kEntitledKeySessionId = (0x1 << 28) | 0x111; +static const std::string kTestKeyIDData("test_key_id"); +static const std::vector kTestKeyID(kTestKeyIDData.begin(), + kTestKeyIDData.end()); +static const char kExpectedSignature[] = "ExpectedSignature"; +static const size_t kExpectedSignatureSizeBytes = 17; +static const char kExpectedCoreMessage[] = "ExpectedCoreMessage"; +static const size_t kExpectedCoreMessageSizeBytes = 19; + +static const std::string kEvenEntitlementKeyId("even_entitlement_key_id"); +static const std::string kOddEntitlementKeyId("odd_entitlement_key_id"); +static const std::string kEvenKeyId("even_key_id"); +static const std::string kOddKeyId("odd_key_id"); +static const std::string kEvenWrappedKey("even_wrapped_content_key"); +static const std::string kOddWrappedKey("odd_wrapped_content_key"); +static const std::string kEvenWrappedKeyIv("even_wrapped_content_key_iv"); +static const std::string kOddWrappedKeyIv("odd_wrapped_content_key_iv"); +static const std::string kEvenContentIv("even_content_iv"); +static const std::string kOddContentIv("odd_content_iv"); +constexpr uint32_t kCurrentOEMCryptoVersion = 18; + +// TODO(jfore): Add validation of arg->buffer based on type. Type is assumed to +// be clear. +MATCHER_P2(IsValidOutputBuffer, type, dest, "") { + if (nullptr == arg) { + return false; + } + if (type != arg->type) { + *result_listener << "buffer type is incorrect"; + return false; + } + if (arg->buffer.clear.address != &(*dest)[0] || + arg->buffer.clear.address_length != dest->size()) { + *result_listener << "invalid destination buffer"; + return false; + } + return true; +} + +MATCHER_P(IsValidInputStreamParams, expected, "") { + return arg.data_addr == expected.data_addr && + arg.data_length == expected.data_length && + arg.is_encrypted == expected.is_encrypted; +} + +MATCHER_P(IsValidInputStreamParamsWithAnyDataAddr, expected, "") { + return arg.data_length == expected.data_length && + arg.is_encrypted == expected.is_encrypted; +} + +MATCHER_P2(IsValidInputStreamParamsWithCollectSizeT, container, expected, "") { + container->push_back(arg.data_length); + return arg.is_encrypted == expected.is_encrypted; +} + +MATCHER_P(IsValidOutBytes, expected, "") { + if (nullptr == arg) { + return false; + } + if (*arg != *expected) { + return false; + } + return true; +} + +// TODO(jfore): Validate pattern descriptor. +MATCHER(IsValidPatternDescriptor, "") { + if (nullptr == arg) { + return false; + } + return true; +} + +MATCHER_P2(ValidateSubSampleFlags, first, last, "") { + bool is_set = (arg & OEMCrypto_FirstSubsample) > 0; + if (is_set ^ first) { + return false; + } + is_set = (arg & OEMCrypto_LastSubsample) > 0; + if (is_set ^ last) { + return false; + } + return true; +} + +MATCHER_P(IsValidIV, expected, "") { + if (nullptr == arg) { + *result_listener << "iv value cannot be null"; + return false; + } + std::vector actual(arg, arg + 16); + if (actual != expected) { + *result_listener << "actual = " << ::testing::PrintToString(actual); + return false; + } + return actual == expected; +} + +// Collect call arguments to analyze them together. +MATCHER_P(CollectSizeT, container, "") { + container->push_back(arg); + return true; +} + +// TODO(chelu): Rewrite the tests to compare data is expected. +MATCHER(IsValidEvenKeyData, "") { + if (arg == nullptr) { + *result_listener << "even key data is null"; + return false; + } + if (arg->entitlement_key_id.length != kEvenEntitlementKeyId.size()) { + *result_listener << "invalid even entitlement key id"; + return false; + } + if (arg->content_key_id.length != kEvenKeyId.size()) { + *result_listener << "invalid even content key id"; + return false; + } + if (arg->content_key_data.length != kEvenWrappedKey.size()) { + *result_listener << "invalid even content key"; + return false; + } + return true; +} + +MATCHER(IsValidOddKeyData, "") { + if (arg == nullptr) { + *result_listener << "odd key data is null"; + return false; + } + if (arg->entitlement_key_id.length != kOddEntitlementKeyId.size()) { + *result_listener << "invalid odd entitlement key id"; + return false; + } + if (arg->content_key_id.length != kOddKeyId.size()) { + *result_listener << "invalid odd content key id"; + return false; + } + if (arg->content_key_data.length != kOddWrappedKey.size()) { + *result_listener << "invalid odd content key"; + return false; + } + return true; +} + +class MockedOEMCrypto : public wvcas::OEMCryptoInterface { + public: + MOCK_METHOD0(OEMCrypto_Initialize, OEMCryptoResult(void)); + MOCK_METHOD0(OEMCrypto_Terminate, OEMCryptoResult(void)); + MOCK_CONST_METHOD1(OEMCrypto_OpenSession, + OEMCryptoResult(OEMCrypto_SESSION*)); + MOCK_CONST_METHOD1(OEMCrypto_CloseSession, + OEMCryptoResult(OEMCrypto_SESSION)); + MOCK_CONST_METHOD0(OEMCrypto_GetProvisioningMethod, + OEMCrypto_ProvisioningMethod()); + MOCK_CONST_METHOD0(OEMCrypto_APIVersion, uint32_t()); + MOCK_CONST_METHOD2(OEMCrypto_GetKeyData, + OEMCryptoResult(uint8_t* keyData, size_t* keyDataLength)); + MOCK_CONST_METHOD0(OEMCrypto_SupportedCertificates, uint32_t()); + MOCK_CONST_METHOD2(OEMCrypto_GenerateNonce, + OEMCryptoResult(OEMCrypto_SESSION session, + uint32_t* nonce)); + MOCK_CONST_METHOD5(OEMCrypto_GenerateDerivedKeys, + OEMCryptoResult(OEMCrypto_SESSION session, + const uint8_t* mac_key_context, + uint32_t mac_key_context_length, + const uint8_t* enc_key_context, + uint32_t enc_key_context_length)); + MOCK_CONST_METHOD6(OEMCrypto_PrepAndSignLicenseRequest, + OEMCryptoResult(OEMCrypto_SESSION session, + uint8_t* message, size_t message_length, + size_t* core_message_size, + uint8_t* signature, + size_t* signature_length)); + MOCK_CONST_METHOD6(OEMCrypto_PrepAndSignRenewalRequest, + OEMCryptoResult(OEMCrypto_SESSION session, + uint8_t* message, size_t message_length, + size_t* core_message_size, + uint8_t* signature, + size_t* signature_length)); + MOCK_CONST_METHOD6(OEMCrypto_PrepAndSignProvisioningRequest, + OEMCryptoResult(OEMCrypto_SESSION session, + uint8_t* message, size_t message_length, + size_t* core_message_size, + uint8_t* signature, + size_t* signature_length)); + MOCK_CONST_METHOD8( + OEMCrypto_LoadProvisioning, + OEMCryptoResult(OEMCrypto_SESSION session, const uint8_t* message, + size_t message_length, size_t core_message_length, + const uint8_t* signature, size_t signature_length, + uint8_t* wrapped_private_key, + size_t* wrapped_private_key_length)); + MOCK_CONST_METHOD6( + OEMCrypto_LoadLicense, + OEMCryptoResult(OEMCrypto_SESSION session, const uint8_t* message, + size_t message_length, size_t core_message_length, + const uint8_t* signature, size_t signature_length)); + MOCK_CONST_METHOD6( + OEMCrypto_LoadRenewal, + OEMCryptoResult(OEMCrypto_SESSION session, const uint8_t* message, + size_t message_length, size_t core_message_length, + const uint8_t* signature, size_t signature_length)); + MOCK_CONST_METHOD3(OEMCrypto_GetOEMPublicCertificate, + OEMCryptoResult(OEMCrypto_SESSION session, + uint8_t* public_cert, + size_t* public_cert_length)); + MOCK_CONST_METHOD4(OEMCrypto_LoadDRMPrivateKey, + OEMCryptoResult(OEMCrypto_SESSION session, + OEMCrypto_PrivateKeyType key_type, + const uint8_t* wrapped_rsa_key, + size_t wrapped_rsa_key_length)); + MOCK_CONST_METHOD6(OEMCrypto_GenerateRSASignature, + OEMCryptoResult(OEMCrypto_SESSION session, + const uint8_t* message, + size_t message_length, uint8_t* signature, + size_t* signature_length, + RSA_Padding_Scheme padding_scheme)); + MOCK_CONST_METHOD7(OEMCrypto_DeriveKeysFromSessionKey, + OEMCryptoResult(OEMCrypto_SESSION session, + const uint8_t* enc_session_key, + size_t enc_session_key_length, + const uint8_t* mac_key_context, + size_t mac_key_context_length, + const uint8_t* enc_key_context, + size_t enc_key_context_length)); + MOCK_CONST_METHOD5( + OEMCrypto_LoadCasECMKeys, + OEMCryptoResult(OEMCrypto_SESSION session, const uint8_t* message, + size_t message_length, + const OEMCrypto_EntitledContentKeyObject* even_key, + const OEMCrypto_EntitledContentKeyObject* odd_key)); + MOCK_CONST_METHOD2(OEMCrypto_GetDeviceID, + OEMCryptoResult(uint8_t* deviceID, size_t* idLength)); + MOCK_CONST_METHOD2( + OEMCrypto_CreateEntitledKeySession, + OEMCryptoResult(OEMCrypto_SESSION session, + OEMCrypto_SESSION* entitled_key_session_id)); + MOCK_CONST_METHOD1( + OEMCrypto_RemoveEntitledKeySession, + OEMCryptoResult(OEMCrypto_SESSION entitled_key_session_id)); + MOCK_METHOD(OEMCryptoResult, OEMCrypto_GetOEMKeyToken, + (OEMCrypto_SESSION key_session, uint8_t* key_token, + size_t* key_token_length), + (const override)); + MOCK_METHOD(OEMCryptoResult, OEMCrypto_GetSignatureHashAlgorithm, + (OEMCrypto_SESSION session, + OEMCrypto_SignatureHashAlgorithm* algorithm), + (const override)); +}; + +template +class TestCryptoSession : public wvcas::CryptoSession { + public: + explicit TestCryptoSession(MOCK_TYPE& mock) : mock_(mock) {} + ~TestCryptoSession() override {} + OEMCryptoResult getCryptoInterface( + std::unique_ptr* interface) override { + return wvcas::CryptoInterface::create(interface, &mock_); + } + MOCK_TYPE& mock_; +}; + +class CryptoSessionTest : public ::testing::Test { + public: + void SetUp() {} + + void SetOutputCoreMessageAndSignature(OEMCrypto_SESSION session, + uint8_t* message, size_t message_length, + size_t* core_message_size, + uint8_t* signature, + size_t* signature_length) { + ASSERT_EQ(kExpectedCoreMessageSizeBytes, *core_message_size); + memcpy(message, kExpectedCoreMessage, kExpectedCoreMessageSizeBytes); + + ASSERT_EQ(kExpectedSignatureSizeBytes, *signature_length); + memcpy(signature, kExpectedSignature, kExpectedSignatureSizeBytes); + } + + NiceMock nice_oemcrypto_interface_; + StrictMock strict_oemcrypto_interface_; +}; + +TEST_F(CryptoSessionTest, CryptoInterfaceInitialization) { + EXPECT_CALL(strict_oemcrypto_interface_, OEMCrypto_Initialize()).Times(1); + + std::unique_ptr initialize_one; + std::unique_ptr initialize_two; + std::unique_ptr initialize_three; + std::unique_ptr initialize_four; + + EXPECT_EQ(OEMCrypto_SUCCESS, + wvcas::CryptoInterface::create(&initialize_one, + &strict_oemcrypto_interface_)); + EXPECT_EQ(OEMCrypto_SUCCESS, + wvcas::CryptoInterface::create(&initialize_two, + &strict_oemcrypto_interface_)); + EXPECT_EQ(OEMCrypto_SUCCESS, + wvcas::CryptoInterface::create(&initialize_three, + &strict_oemcrypto_interface_)); + EXPECT_EQ(OEMCrypto_SUCCESS, + wvcas::CryptoInterface::create(&initialize_four, + &strict_oemcrypto_interface_)); + + initialize_one.reset(); + initialize_two.reset(); + initialize_three.reset(); + + // Terminate should only be called when initialize_four is destroyed. + // OEMCrypto_Terminate should only be called upon the destruction of the last + // open crypto interface. In this case it is initialize_four due to the + // ordering above. + EXPECT_CALL(strict_oemcrypto_interface_, OEMCrypto_Terminate()).Times(1); + initialize_four.reset(); +} + +TEST_F(CryptoSessionTest, MultipleCryptoSession) { + EXPECT_CALL(strict_oemcrypto_interface_, OEMCrypto_Initialize()).Times(1); + + std::unique_ptr initialize; + EXPECT_EQ(OEMCrypto_SUCCESS, wvcas::CryptoInterface::create( + &initialize, &strict_oemcrypto_interface_)); + + std::unique_ptr one = + make_unique(); + std::unique_ptr two = + make_unique(); + std::unique_ptr three = + make_unique(); + std::unique_ptr four = + make_unique(); + + one.reset(); + four.reset(); + three.reset(); + + // Terminate should only be called when the last session is destroyed. + EXPECT_CALL(strict_oemcrypto_interface_, OEMCrypto_Terminate()).Times(1); + two.reset(); +} + +TEST_F(CryptoSessionTest, CryptoInterfaceInitiallizationBadInput) { + std::unique_ptr initialize; + // Oemcrypto init fails. + EXPECT_CALL(strict_oemcrypto_interface_, OEMCrypto_Initialize()) + .Times(1) + .WillOnce(Return(OEMCrypto_ERROR_INIT_FAILED)); + EXPECT_EQ(OEMCrypto_ERROR_INIT_FAILED, + wvcas::CryptoInterface::create(&initialize, + &strict_oemcrypto_interface_)); + initialize.reset(); + + // Input params are null. + EXPECT_EQ( + OEMCrypto_ERROR_INIT_FAILED, + wvcas::CryptoInterface::create(nullptr, &strict_oemcrypto_interface_)); +} + +TEST_F(CryptoSessionTest, OpenSessions) { + TestCryptoSession > crypto_session_one( + nice_oemcrypto_interface_); + TestCryptoSession > crypto_session_two( + nice_oemcrypto_interface_); + + EXPECT_CALL(nice_oemcrypto_interface_, OEMCrypto_OpenSession(_)) + .WillOnce( + DoAll(SetArgPointee<0>(kOemcSessionId), Return(OEMCrypto_SUCCESS))) + .WillOnce( + DoAll(SetArgPointee<0>(kOemcSessionId2), Return(OEMCrypto_SUCCESS))); + + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + crypto_session_one.initialize().status_code()); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + crypto_session_two.initialize().status_code()); + + // close() will be called in the destructor. + EXPECT_CALL(nice_oemcrypto_interface_, OEMCrypto_CloseSession(kOemcSessionId)) + .WillOnce(Return(OEMCrypto_SUCCESS)); + EXPECT_CALL(nice_oemcrypto_interface_, + OEMCrypto_CloseSession(kOemcSessionId2)) + .WillOnce(Return(OEMCrypto_SUCCESS)); +} + +TEST_F(CryptoSessionTest, GetProvisioningMethodWithKeybox) { + EXPECT_CALL(strict_oemcrypto_interface_, OEMCrypto_Initialize()); + EXPECT_CALL(strict_oemcrypto_interface_, OEMCrypto_Terminate()); + + EXPECT_CALL(strict_oemcrypto_interface_, OEMCrypto_OpenSession(_)) + .WillOnce( + DoAll(SetArgPointee<0>(kOemcSessionId), Return(OEMCrypto_SUCCESS))); + EXPECT_CALL(strict_oemcrypto_interface_, OEMCrypto_GetProvisioningMethod()) + .WillRepeatedly(Return(OEMCrypto_Keybox)); + + EXPECT_CALL(strict_oemcrypto_interface_, + OEMCrypto_CloseSession(kOemcSessionId)) + .WillOnce(Return(OEMCrypto_SUCCESS)); + + TestCryptoSession > crypto_session( + strict_oemcrypto_interface_); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + crypto_session.initialize().status_code()); + + EXPECT_EQ(wvcas::Keybox, crypto_session.provisioning_method()); +} + +TEST_F(CryptoSessionTest, GetKeyData) { + EXPECT_CALL(nice_oemcrypto_interface_, OEMCrypto_OpenSession(_)) + .WillOnce( + DoAll(SetArgPointee<0>(kOemcSessionId), Return(OEMCrypto_SUCCESS))); + + TestCryptoSession > crypto_session( + nice_oemcrypto_interface_); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + crypto_session.initialize().status_code()); + + uint8_t data = 0; + size_t size = 0; + EXPECT_CALL(nice_oemcrypto_interface_, OEMCrypto_GetKeyData(&data, &size)) + .WillOnce(DoAll(SetArgPointee<0>(0x12), SetArgPointee<1>(0x4321), + Return(OEMCrypto_ERROR_SHORT_BUFFER))); + EXPECT_NE(wvcas::CasStatusCode::kNoError, + crypto_session.GetKeyData(&data, &size).status_code()); + EXPECT_EQ(0x12, data); + EXPECT_EQ(size_t(0x4321), size); +} + +TEST_F(CryptoSessionTest, GetApiVersion) { + TestCryptoSession > crypto_session( + nice_oemcrypto_interface_); + EXPECT_TRUE(crypto_session.initialize().ok()); + EXPECT_CALL(nice_oemcrypto_interface_, OEMCrypto_APIVersion()) + .WillOnce(Return(kCurrentOEMCryptoVersion)); + uint32_t api_version = 0; + + EXPECT_EQ(crypto_session.APIVersion(&api_version).status_code(), + wvcas::CasStatusCode::kNoError); + EXPECT_EQ(api_version, kCurrentOEMCryptoVersion); +} + +class CryptoSessionApiVersionTest + : public CryptoSessionTest, + public ::testing::WithParamInterface {}; + +TEST_P(CryptoSessionApiVersionTest, InvalidApiVersion) { + TestCryptoSession > crypto_session( + nice_oemcrypto_interface_); + EXPECT_TRUE(crypto_session.initialize().ok()); + EXPECT_CALL(nice_oemcrypto_interface_, OEMCrypto_APIVersion()) + .WillOnce(Return(GetParam())); + uint32_t api_version = 0; + + EXPECT_EQ(crypto_session.APIVersion(&api_version).status_code(), + wvcas::CasStatusCode::kOEMCryptoVersionMismatch); + EXPECT_EQ(api_version, 0); +} + +INSTANTIATE_TEST_SUITE_P(CryptoSessionApiVersion, CryptoSessionApiVersionTest, + ::testing::Range(13u, kCurrentOEMCryptoVersion)); + +TEST_F(CryptoSessionTest, GetSupportedCertificates) { + EXPECT_CALL(nice_oemcrypto_interface_, OEMCrypto_OpenSession(_)) + .WillOnce( + DoAll(SetArgPointee<0>(kOemcSessionId), Return(OEMCrypto_SUCCESS))); + + TestCryptoSession > crypto_session( + nice_oemcrypto_interface_); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + crypto_session.initialize().status_code()); + + EXPECT_CALL(nice_oemcrypto_interface_, OEMCrypto_SupportedCertificates()) + .WillOnce(Return(0x13)) + .WillOnce(Return(0x1)) + .WillOnce(Return(0x2)) + .WillOnce(Return(0x10)) + .WillOnce(Return(~0x13)); + + wvcas::SupportedCertificates supported_certs = + crypto_session.supported_certificates(); + EXPECT_TRUE(supported_certs.rsa_2048bit()); + EXPECT_TRUE(supported_certs.rsa_3072bit()); + EXPECT_TRUE(supported_certs.rsa_CASTbit()); + + supported_certs = crypto_session.supported_certificates(); + EXPECT_TRUE(supported_certs.rsa_2048bit()); + EXPECT_FALSE(supported_certs.rsa_3072bit()); + EXPECT_FALSE(supported_certs.rsa_CASTbit()); + + supported_certs = crypto_session.supported_certificates(); + EXPECT_FALSE(supported_certs.rsa_2048bit()); + EXPECT_TRUE(supported_certs.rsa_3072bit()); + EXPECT_FALSE(supported_certs.rsa_CASTbit()); + + supported_certs = crypto_session.supported_certificates(); + EXPECT_FALSE(supported_certs.rsa_2048bit()); + EXPECT_FALSE(supported_certs.rsa_3072bit()); + EXPECT_TRUE(supported_certs.rsa_CASTbit()); + + supported_certs = crypto_session.supported_certificates(); + EXPECT_FALSE(supported_certs.rsa_2048bit()); + EXPECT_FALSE(supported_certs.rsa_3072bit()); + EXPECT_FALSE(supported_certs.rsa_CASTbit()); +} + +TEST_F(CryptoSessionTest, GenerateNonce) { + EXPECT_CALL(nice_oemcrypto_interface_, OEMCrypto_OpenSession(_)) + .WillOnce( + DoAll(SetArgPointee<0>(kOemcSessionId), Return(OEMCrypto_SUCCESS))); + + TestCryptoSession > crypto_session( + nice_oemcrypto_interface_); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + crypto_session.initialize().status_code()); + + uint32_t nonce = 0; + EXPECT_CALL(nice_oemcrypto_interface_, + OEMCrypto_GenerateNonce(kOemcSessionId, &nonce)) + .WillOnce(DoAll(SetArgPointee<1>(0x5555), Return(OEMCrypto_SUCCESS))); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + crypto_session.GenerateNonce(&nonce).status_code()); + EXPECT_EQ(uint32_t(0x5555), nonce); +} + +TEST_F(CryptoSessionTest, GenerateDerivedKeys) { + TestCryptoSession > crypto_session( + nice_oemcrypto_interface_); + EXPECT_CALL(nice_oemcrypto_interface_, OEMCrypto_OpenSession(_)) + .WillOnce( + DoAll(SetArgPointee<0>(kOemcSessionId), Return(OEMCrypto_SUCCESS))); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + crypto_session.initialize().status_code()); + + uint8_t mac_key_context = 0; + uint8_t enc_key_context = 0; + EXPECT_CALL(nice_oemcrypto_interface_, + OEMCrypto_GenerateDerivedKeys(kOemcSessionId, &mac_key_context, + 6789, &enc_key_context, 9876)); + EXPECT_EQ( + wvcas::CasStatusCode::kNoError, + crypto_session + .GenerateDerivedKeys(&mac_key_context, 6789, &enc_key_context, 9876) + .status_code()); +} + +TEST_F(CryptoSessionTest, PrepareAndSignLicenseRequest) { + TestCryptoSession > crypto_session( + nice_oemcrypto_interface_); + EXPECT_CALL(nice_oemcrypto_interface_, OEMCrypto_OpenSession(_)) + .WillOnce( + DoAll(SetArgPointee<0>(kOemcSessionId), Return(OEMCrypto_SUCCESS))); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + crypto_session.initialize().status_code()); + + const std::string message = "message"; + std::string core_message; + std::string signature; + bool should_specify_algorithm = false; + OEMCrypto_SignatureHashAlgorithm oec_algorithm = OEMCrypto_SHA1; + EXPECT_CALL(nice_oemcrypto_interface_, + OEMCrypto_GetSignatureHashAlgorithm(kOemcSessionId, NotNull())) + .WillOnce(DoAll(SetArgPointee<1>(OEMCrypto_SHA2_256), + Return(OEMCrypto_SUCCESS))); + EXPECT_CALL(nice_oemcrypto_interface_, + OEMCrypto_PrepAndSignLicenseRequest(kOemcSessionId, NotNull(), + message.size(), Pointee(0), + IsNull(), Pointee(0))) + .WillOnce(DoAll(SetArgPointee<3>(kExpectedCoreMessageSizeBytes), + SetArgPointee<5>(kExpectedSignatureSizeBytes), + Return(OEMCrypto_ERROR_SHORT_BUFFER))); + EXPECT_CALL(nice_oemcrypto_interface_, + OEMCrypto_PrepAndSignLicenseRequest( + kOemcSessionId, NotNull(), + message.size() + kExpectedCoreMessageSizeBytes, + Pointee(kExpectedCoreMessageSizeBytes), NotNull(), + Pointee(kExpectedSignatureSizeBytes))) + .WillOnce(DoAll( + Invoke(this, &CryptoSessionTest::SetOutputCoreMessageAndSignature), + Return(OEMCrypto_SUCCESS))); + EXPECT_EQ( + wvcas::CasStatusCode::kNoError, + crypto_session + .PrepareAndSignLicenseRequest(message, &core_message, &signature, + should_specify_algorithm, oec_algorithm) + .status_code()); + EXPECT_EQ(core_message, std::string(kExpectedCoreMessage)); + EXPECT_EQ(signature, std::string(kExpectedSignature)); + EXPECT_TRUE(should_specify_algorithm); + EXPECT_EQ(oec_algorithm, OEMCrypto_SHA2_256); +} + +TEST_F(CryptoSessionTest, PrepareAndSignRenewalRequest) { + TestCryptoSession > crypto_session( + nice_oemcrypto_interface_); + EXPECT_CALL(nice_oemcrypto_interface_, OEMCrypto_OpenSession(_)) + .WillOnce( + DoAll(SetArgPointee<0>(kOemcSessionId), Return(OEMCrypto_SUCCESS))); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + crypto_session.initialize().status_code()); + + const std::string message = "message"; + std::string core_message; + std::string signature; + EXPECT_CALL(nice_oemcrypto_interface_, + OEMCrypto_PrepAndSignRenewalRequest(kOemcSessionId, NotNull(), + message.size(), Pointee(0), + IsNull(), Pointee(0))) + .WillOnce(DoAll(SetArgPointee<3>(kExpectedCoreMessageSizeBytes), + SetArgPointee<5>(kExpectedSignatureSizeBytes), + Return(OEMCrypto_ERROR_SHORT_BUFFER))); + EXPECT_CALL(nice_oemcrypto_interface_, + OEMCrypto_PrepAndSignRenewalRequest( + kOemcSessionId, NotNull(), + message.size() + kExpectedCoreMessageSizeBytes, + Pointee(kExpectedCoreMessageSizeBytes), NotNull(), + Pointee(kExpectedSignatureSizeBytes))) + .WillOnce(DoAll( + Invoke(this, &CryptoSessionTest::SetOutputCoreMessageAndSignature), + Return(OEMCrypto_SUCCESS))); + EXPECT_EQ( + wvcas::CasStatusCode::kNoError, + crypto_session + .PrepareAndSignRenewalRequest(message, &core_message, &signature) + .status_code()); + EXPECT_EQ(core_message, std::string(kExpectedCoreMessage)); + EXPECT_EQ(signature, std::string(kExpectedSignature)); +} + +TEST_F(CryptoSessionTest, PrepareAndSignProvisioningRequest) { + TestCryptoSession > crypto_session( + nice_oemcrypto_interface_); + EXPECT_CALL(nice_oemcrypto_interface_, OEMCrypto_OpenSession(_)) + .WillOnce( + DoAll(SetArgPointee<0>(kOemcSessionId), Return(OEMCrypto_SUCCESS))); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + crypto_session.initialize().status_code()); + + const std::string message = "message"; + std::string core_message; + std::string signature; + bool should_specify_algorithm = false; + OEMCrypto_SignatureHashAlgorithm oec_algorithm = OEMCrypto_SHA1; + EXPECT_CALL(nice_oemcrypto_interface_, + OEMCrypto_PrepAndSignProvisioningRequest( + kOemcSessionId, NotNull(), message.size(), Pointee(0), + IsNull(), Pointee(0))) + .WillOnce(DoAll(SetArgPointee<3>(kExpectedCoreMessageSizeBytes), + SetArgPointee<5>(kExpectedSignatureSizeBytes), + Return(OEMCrypto_ERROR_SHORT_BUFFER))); + EXPECT_CALL(nice_oemcrypto_interface_, + OEMCrypto_PrepAndSignProvisioningRequest( + kOemcSessionId, NotNull(), + message.size() + kExpectedCoreMessageSizeBytes, + Pointee(kExpectedCoreMessageSizeBytes), NotNull(), + Pointee(kExpectedSignatureSizeBytes))) + .WillOnce(DoAll( + Invoke(this, &CryptoSessionTest::SetOutputCoreMessageAndSignature), + Return(OEMCrypto_SUCCESS))); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + crypto_session + .PrepareAndSignProvisioningRequest( + message, &core_message, &signature, + should_specify_algorithm, oec_algorithm) + .status_code()); + EXPECT_EQ(core_message, std::string(kExpectedCoreMessage)); + EXPECT_EQ(signature, std::string(kExpectedSignature)); + EXPECT_FALSE(should_specify_algorithm); +} + +TEST_F(CryptoSessionTest, LoadProvisioning) { + TestCryptoSession > crypto_session( + nice_oemcrypto_interface_); + EXPECT_CALL(nice_oemcrypto_interface_, OEMCrypto_OpenSession(_)) + .WillOnce( + DoAll(SetArgPointee<0>(kOemcSessionId), Return(OEMCrypto_SUCCESS))); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + crypto_session.initialize().status_code()); + + const std::string signed_message("signed_message"); + const std::string core_message("core_message"); + const std::string signature("signature"); + std::string wrapped_private_key; + + // Test data for use in handling the expected calls. + const size_t wrapped_rsa_key_size = 75; + const uint8_t first_wrapped_rsa_key_byte = 0x55; + + EXPECT_CALL(nice_oemcrypto_interface_, + OEMCrypto_LoadProvisioning(_, _, _, _, _, _, IsNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<7>(wrapped_rsa_key_size), + Return(OEMCrypto_ERROR_SHORT_BUFFER))); + EXPECT_CALL(nice_oemcrypto_interface_, + OEMCrypto_LoadProvisioning(_, _, _, _, _, _, NotNull(), + Pointee(wrapped_rsa_key_size))) + .WillOnce(DoAll(SetArgPointee<6>(first_wrapped_rsa_key_byte), + Return(OEMCrypto_SUCCESS))); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + crypto_session + .LoadProvisioning(signed_message, core_message, signature, + &wrapped_private_key) + .status_code()); + ASSERT_EQ(wrapped_rsa_key_size, wrapped_private_key.size()); + EXPECT_EQ(first_wrapped_rsa_key_byte, wrapped_private_key[0]); +} + +TEST_F(CryptoSessionTest, LoadLicense) { + TestCryptoSession > crypto_session( + nice_oemcrypto_interface_); + EXPECT_CALL(nice_oemcrypto_interface_, OEMCrypto_OpenSession(_)) + .WillOnce( + DoAll(SetArgPointee<0>(kOemcSessionId), Return(OEMCrypto_SUCCESS))); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + crypto_session.initialize().status_code()); + + const std::string signed_message("signed_message"); + const std::string core_message("core_message"); + const std::string signature("signature"); + EXPECT_CALL(nice_oemcrypto_interface_, + OEMCrypto_LoadLicense(kOemcSessionId, _, + signed_message.size() + core_message.size(), + core_message.size(), _, signature.size())) + .WillOnce(Return(OEMCrypto_SUCCESS)); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + crypto_session.LoadLicense(signed_message, core_message, signature) + .status_code()); +} + +TEST_F(CryptoSessionTest, GetOEMPublicCertificate) { + TestCryptoSession > crypto_session( + nice_oemcrypto_interface_); + EXPECT_CALL(nice_oemcrypto_interface_, OEMCrypto_OpenSession(_)) + .WillOnce( + DoAll(SetArgPointee<0>(kOemcSessionId), Return(OEMCrypto_SUCCESS))); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + crypto_session.initialize().status_code()); + + uint8_t public_cert; + size_t public_cert_length; + + // Test data for use in handling the expected calls. + const uint8_t cert_data = 0xaa; + const size_t cert_size = 5000; + + EXPECT_CALL(nice_oemcrypto_interface_, + OEMCrypto_GetOEMPublicCertificate(_, &public_cert, _)) + .WillOnce(DoAll(SetArgPointee<1>(cert_data), SetArgPointee<2>(cert_size), + Return(OEMCrypto_SUCCESS))); + EXPECT_EQ( + wvcas::CasStatusCode::kNoError, + crypto_session.GetOEMPublicCertificate(&public_cert, &public_cert_length) + .status_code()); + EXPECT_EQ(cert_data, public_cert); + EXPECT_EQ(cert_size, public_cert_length); +} + +TEST_F(CryptoSessionTest, LoadDeviceRSAKey) { + TestCryptoSession > crypto_session( + nice_oemcrypto_interface_); + EXPECT_CALL(nice_oemcrypto_interface_, OEMCrypto_OpenSession(_)) + .WillOnce( + DoAll(SetArgPointee<0>(kOemcSessionId), Return(OEMCrypto_SUCCESS))); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + crypto_session.initialize().status_code()); + + // Test data for use in handling the expected calls. + uint8_t wrapped_rsa_key = 0; + size_t wrapped_rsa_key_length = 75; + + EXPECT_CALL( + nice_oemcrypto_interface_, + OEMCrypto_LoadDRMPrivateKey(kOemcSessionId, OEMCrypto_RSA_Private_Key, + &wrapped_rsa_key, wrapped_rsa_key_length)) + .WillOnce(Return(OEMCrypto_SUCCESS)); + EXPECT_EQ( + wvcas::CasStatusCode::kNoError, + crypto_session.LoadDeviceRSAKey(&wrapped_rsa_key, wrapped_rsa_key_length) + .status_code()); +} + +TEST_F(CryptoSessionTest, GenerateRSASignature) { + TestCryptoSession > crypto_session( + nice_oemcrypto_interface_); + EXPECT_CALL(nice_oemcrypto_interface_, OEMCrypto_OpenSession(_)) + .WillOnce( + DoAll(SetArgPointee<0>(kOemcSessionId), Return(OEMCrypto_SUCCESS))); + + // Test data for use in handling the expected calls. + const uint8_t message = 0; + const size_t message_length = 75; + const size_t output_signature_length = 800; + const uint8_t first_signature_byte = 0xFF; + uint8_t signature = 0; + size_t signature_length = 0; + + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + crypto_session.initialize().status_code()); + EXPECT_CALL(nice_oemcrypto_interface_, + OEMCrypto_GenerateRSASignature( + kOemcSessionId, &message, message_length, &signature, + &signature_length, kSign_RSASSA_PSS)) + .WillOnce(DoAll(SetArgPointee<3>(first_signature_byte), + SetArgPointee<4>(output_signature_length), + Return(OEMCrypto_SUCCESS))); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + crypto_session + .GenerateRSASignature(&message, message_length, &signature, + &signature_length, kSign_RSASSA_PSS) + .status_code()); + EXPECT_EQ(first_signature_byte, signature); + EXPECT_EQ(output_signature_length, signature_length); +} + +TEST_F(CryptoSessionTest, LoadCasECMKeys) { + TestCryptoSession > crypto_session( + nice_oemcrypto_interface_); + EXPECT_CALL(nice_oemcrypto_interface_, OEMCrypto_OpenSession(_)) + .WillOnce( + DoAll(SetArgPointee<0>(kOemcSessionId), Return(OEMCrypto_SUCCESS))); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + crypto_session.initialize().status_code()); + + wvcas::KeySlot even; + even.entitlement_key_id.assign(kEvenEntitlementKeyId.begin(), + kEvenEntitlementKeyId.end()); + even.key_id.assign(kEvenKeyId.begin(), kEvenKeyId.end()); + even.wrapped_key.assign(kEvenWrappedKey.begin(), kEvenWrappedKey.end()); + even.wrapped_key_iv.assign(kEvenWrappedKeyIv.begin(), + kEvenWrappedKeyIv.end()); + even.content_iv.assign(kEvenContentIv.begin(), kEvenContentIv.end()); + + wvcas::KeySlot odd; + odd.entitlement_key_id.assign(kOddEntitlementKeyId.begin(), + kOddEntitlementKeyId.end()); + odd.key_id.assign(kOddKeyId.begin(), kOddKeyId.end()); + odd.wrapped_key.assign(kOddWrappedKey.begin(), kOddWrappedKey.end()); + odd.wrapped_key_iv.assign(kOddWrappedKeyIv.begin(), kOddWrappedKeyIv.end()); + odd.content_iv.assign(kOddContentIv.begin(), kOddContentIv.end()); + + EXPECT_CALL(nice_oemcrypto_interface_, + OEMCrypto_LoadCasECMKeys(_, _, _, IsValidEvenKeyData(), + IsValidOddKeyData())) + .WillOnce(Return(OEMCrypto_SUCCESS)); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + crypto_session.LoadCasECMKeys(kEntitledKeySessionId, &even, &odd) + .status_code()); +} + +TEST_F(CryptoSessionTest, LoadRenewal) { + TestCryptoSession > crypto_session( + nice_oemcrypto_interface_); + EXPECT_CALL(nice_oemcrypto_interface_, OEMCrypto_OpenSession(_)) + .WillOnce( + DoAll(SetArgPointee<0>(kOemcSessionId), Return(OEMCrypto_SUCCESS))); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + crypto_session.initialize().status_code()); + + const std::string signed_message("signed_message"); + const std::string core_message("core_message"); + const std::string signature("signature"); + EXPECT_CALL( + nice_oemcrypto_interface_, + OEMCrypto_LoadRenewal(kOemcSessionId, NotNull(), + signed_message.size() + core_message.size(), + core_message.size(), NotNull(), signature.size())) + .WillOnce(Return(OEMCrypto_ERROR_SIGNATURE_FAILURE)) + .WillOnce(Return(OEMCrypto_SUCCESS)); + + // OEMCrypto error. + ASSERT_EQ(wvcas::CasStatusCode::kCryptoSessionError, + crypto_session.LoadRenewal(signed_message, core_message, signature) + .status_code()); + + // Valid. + ASSERT_EQ(wvcas::CasStatusCode::kNoError, + crypto_session.LoadRenewal(signed_message, core_message, signature) + .status_code()); +} + +TEST_F(CryptoSessionTest, ReadUniqueId) { + TestCryptoSession > crypto_session( + strict_oemcrypto_interface_); + std::string unique_id; + + // Crypto session is not initialized. + ASSERT_NE(wvcas::CasStatusCode::kNoError, + crypto_session.GetDeviceID(&unique_id).status_code()); + EXPECT_CALL(strict_oemcrypto_interface_, OEMCrypto_Initialize()); + EXPECT_CALL(strict_oemcrypto_interface_, OEMCrypto_Terminate()); + EXPECT_CALL(strict_oemcrypto_interface_, OEMCrypto_OpenSession(_)) + .WillOnce( + DoAll(SetArgPointee<0>(kOemcSessionId), Return(OEMCrypto_SUCCESS))); + EXPECT_CALL(strict_oemcrypto_interface_, + OEMCrypto_CloseSession(kOemcSessionId)) + .WillOnce(Return(OEMCrypto_SUCCESS)); + + ASSERT_EQ(wvcas::CasStatusCode::kNoError, + crypto_session.initialize().status_code()); + + // Invalid output buffer. + ASSERT_EQ(wvcas::CasStatusCode::kCryptoSessionError, + crypto_session.GetDeviceID(nullptr).status_code()); + + // OEMCrypto fails. + EXPECT_CALL(strict_oemcrypto_interface_, OEMCrypto_GetDeviceID(NotNull(), _)) + .WillOnce(Return(OEMCrypto_ERROR_NO_DEVICEID)); + ASSERT_EQ(wvcas::CasStatusCode::kCryptoSessionError, + crypto_session.GetDeviceID(&unique_id).status_code()); + + // Valid. + EXPECT_CALL(strict_oemcrypto_interface_, OEMCrypto_GetDeviceID(NotNull(), _)) + .WillOnce(Return(OEMCrypto_SUCCESS)); + ASSERT_EQ(wvcas::CasStatusCode::kNoError, + crypto_session.GetDeviceID(&unique_id).status_code()); + ASSERT_EQ(unique_id.size(), 32); +} + +TEST_F(CryptoSessionTest, EntitledKeySession) { + TestCryptoSession > crypto_session( + strict_oemcrypto_interface_); + uint32_t key_session_id; + + // Crypto session is not initialized. + ASSERT_NE( + wvcas::CasStatusCode::kNoError, + crypto_session.CreateEntitledKeySession(&key_session_id).status_code()); + ASSERT_NE( + wvcas::CasStatusCode::kNoError, + crypto_session.RemoveEntitledKeySession(key_session_id).status_code()); + + EXPECT_CALL(strict_oemcrypto_interface_, OEMCrypto_Initialize()); + EXPECT_CALL(strict_oemcrypto_interface_, OEMCrypto_Terminate()); + EXPECT_CALL(strict_oemcrypto_interface_, OEMCrypto_OpenSession(_)) + .WillOnce( + DoAll(SetArgPointee<0>(kOemcSessionId), Return(OEMCrypto_SUCCESS))); + EXPECT_CALL(strict_oemcrypto_interface_, + OEMCrypto_CloseSession(kOemcSessionId)) + .WillOnce(Return(OEMCrypto_SUCCESS)); + + ASSERT_EQ(wvcas::CasStatusCode::kNoError, + crypto_session.initialize().status_code()); + + // Invalid output buffer. + ASSERT_EQ(wvcas::CasStatusCode::kCryptoSessionError, + crypto_session.CreateEntitledKeySession(nullptr).status_code()); + + // Valid. + EXPECT_CALL(strict_oemcrypto_interface_, + OEMCrypto_CreateEntitledKeySession(kOemcSessionId, NotNull())) + .WillOnce(DoAll(SetArgPointee<1>(kEntitledKeySessionId), + Return(OEMCrypto_SUCCESS))); + ASSERT_EQ( + wvcas::CasStatusCode::kNoError, + crypto_session.CreateEntitledKeySession(&key_session_id).status_code()); + ASSERT_EQ(key_session_id, kEntitledKeySessionId); + + EXPECT_CALL(strict_oemcrypto_interface_, + OEMCrypto_RemoveEntitledKeySession(kEntitledKeySessionId)) + .WillOnce(Return(OEMCrypto_SUCCESS)); + ASSERT_EQ( + wvcas::CasStatusCode::kNoError, + crypto_session.RemoveEntitledKeySession(key_session_id).status_code()); +} + +TEST_F(CryptoSessionTest, GetOEMKeyTokenTest) { + TestCryptoSession > crypto_session( + strict_oemcrypto_interface_); + EXPECT_CALL(strict_oemcrypto_interface_, OEMCrypto_Initialize()); + EXPECT_CALL(strict_oemcrypto_interface_, OEMCrypto_Terminate()); + EXPECT_CALL(strict_oemcrypto_interface_, OEMCrypto_OpenSession(_)) + .WillOnce( + DoAll(SetArgPointee<0>(kOemcSessionId), Return(OEMCrypto_SUCCESS))); + EXPECT_CALL(strict_oemcrypto_interface_, + OEMCrypto_CloseSession(kOemcSessionId)) + .WillOnce(Return(OEMCrypto_SUCCESS)); + ASSERT_TRUE(crypto_session.initialize().ok()); + uint32_t key_session_id = kEntitledKeySessionId; + std::vector expected_token = {0x01, 0x02, 0x03}; + EXPECT_CALL(strict_oemcrypto_interface_, + OEMCrypto_GetOEMKeyToken(key_session_id, _, NotNull())) + .WillRepeatedly([&expected_token](OEMCrypto_SESSION key_session, + uint8_t* key_token, + size_t* key_token_length) { + if (*key_token_length < expected_token.size()) { + *key_token_length = expected_token.size(); + return OEMCrypto_ERROR_SHORT_BUFFER; + } + memcpy(key_token, expected_token.data(), expected_token.size()); + *key_token_length = expected_token.size(); + return OEMCrypto_SUCCESS; + }); + std::vector actual_token; + + ASSERT_EQ( + crypto_session.GetOEMKeyToken(key_session_id, actual_token).status_code(), + wvcas::CasStatusCode::kNoError); + ASSERT_EQ(actual_token, expected_token); +} + +TEST_F(CryptoSessionTest, GetOEMKeyTokenNotImplementedTest) { + TestCryptoSession > crypto_session( + strict_oemcrypto_interface_); + EXPECT_CALL(strict_oemcrypto_interface_, OEMCrypto_Initialize()); + EXPECT_CALL(strict_oemcrypto_interface_, OEMCrypto_Terminate()); + EXPECT_CALL(strict_oemcrypto_interface_, OEMCrypto_OpenSession(_)) + .WillOnce( + DoAll(SetArgPointee<0>(kOemcSessionId), Return(OEMCrypto_SUCCESS))); + EXPECT_CALL(strict_oemcrypto_interface_, + OEMCrypto_CloseSession(kOemcSessionId)) + .WillOnce(Return(OEMCrypto_SUCCESS)); + ASSERT_TRUE(crypto_session.initialize().ok()); + uint32_t key_session_id = 0x12345678; + std::vector expected_token = {0x78, 0x56, 0x34, 0x12}; + EXPECT_CALL(strict_oemcrypto_interface_, + OEMCrypto_GetOEMKeyToken(key_session_id, _, NotNull())) + .WillRepeatedly(Return(OEMCrypto_ERROR_NOT_IMPLEMENTED)); + std::vector actual_token; + + ASSERT_EQ( + crypto_session.GetOEMKeyToken(key_session_id, actual_token).status_code(), + wvcas::CasStatusCode::kNoError); + ASSERT_EQ(actual_token, expected_token); +} + +} // namespace +} // namespace wvcas \ No newline at end of file diff --git a/tests/src/ecm_parser_test.cpp b/tests/src/ecm_parser_test.cpp new file mode 100644 index 0000000..079ebc5 --- /dev/null +++ b/tests/src/ecm_parser_test.cpp @@ -0,0 +1,130 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#include "ecm_parser.h" + +#include +#include + +#include + +#include "media_cas.pb.h" + +namespace wvcas { +namespace { + +constexpr int kCasIdSizeBytes = 2; +constexpr int kVersionSizeBytes = 1; +constexpr int kEcmHeaderSizeBytes = kCasIdSizeBytes + kVersionSizeBytes; +constexpr int kEcmVersion2 = 2; +constexpr int kEcmVersion3 = 3; + +constexpr size_t kValidEcmV2SizeBytes = 165; + +constexpr uint16_t kSectionHeader1 = 0x80; +constexpr uint16_t kSectionHeader2 = 0x81; +constexpr uint8_t kPointerFieldZero = 0x00; + +constexpr uint16_t kWidevineCasId = 0x4AD4; +// New Widevine CAS IDs 0x56C0 to 0x56C9 (all inclusive). +constexpr uint16_t kWidevineNewCasIdLowerBound = 0x56C0; +constexpr uint16_t kWidevineNewCasIdUpperBound = 0x56C9; + +std::vector BuildEcm(uint16_t cas_id, uint8_t version) { + std::vector ecm_data; + ecm_data.resize(kEcmHeaderSizeBytes); + ecm_data[0] = cas_id >> 8; + ecm_data[1] = cas_id & 0xff; + ecm_data[2] = version; + + // Put some dummy data to make the ECM a valid one. + if (version <= 2) { + ecm_data.resize(kValidEcmV2SizeBytes); + } else { + video_widevine::EcmPayload ecm_payload; + ecm_payload.mutable_even_key_data()->set_entitlement_key_id("123"); + video_widevine::SignedEcmPayload signed_ecm_payload; + signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString()); + ecm_data.resize(ecm_data.size() + signed_ecm_payload.ByteSize()); + signed_ecm_payload.SerializeToArray(ecm_data.data() + kEcmHeaderSizeBytes, + signed_ecm_payload.ByteSize()); + } + return ecm_data; +} + +// Verifies ECM parser can be created with different version. +class EcmParserVersionTest : public testing::Test, + public ::testing::WithParamInterface {}; + +TEST_P(EcmParserVersionTest, CreateSuccess) { + std::vector ecm_data = BuildEcm(kWidevineCasId, GetParam()); + ASSERT_TRUE(wvcas::EcmParser::Create(ecm_data) != nullptr); +} + +INSTANTIATE_TEST_SUITE_P(EcmParserVersionTest, EcmParserVersionTest, + ::testing::Values(kEcmVersion2, kEcmVersion3)); + +// Verifies CAS ID returned by the parser must be expected ones. +class EcmParserCasIdTest + : public testing::Test, + public ::testing::WithParamInterface<::testing::tuple> {}; + +TEST_P(EcmParserCasIdTest, ValidateCasIds) { + const uint16_t cas_id = ::testing::get<0>(GetParam()); + std::vector ecm_data = BuildEcm(cas_id, kEcmVersion2); + + const bool is_valid_id = ::testing::get<1>(GetParam()); + if (is_valid_id) { + ASSERT_TRUE(wvcas::EcmParser::Create(ecm_data) != nullptr); + } else { + ASSERT_TRUE(wvcas::EcmParser::Create(ecm_data) == nullptr); + } +} + +INSTANTIATE_TEST_SUITE_P(EcmWithLegacyCasId, EcmParserCasIdTest, + ::testing::Values(std::make_tuple(kWidevineCasId, + true))); +INSTANTIATE_TEST_SUITE_P( + EcmWithNewCasId, EcmParserCasIdTest, + ::testing::Combine( + ::testing::Range(static_cast(kWidevineNewCasIdLowerBound), + static_cast(kWidevineNewCasIdUpperBound + + 1)), + ::testing::Values(true))); + +INSTANTIATE_TEST_SUITE_P( + EcmWithInvalidCasId, EcmParserCasIdTest, + ::testing::Combine(::testing::Values(0, kWidevineCasId - 1, + kWidevineCasId + 1, + kWidevineNewCasIdLowerBound - 1, + kWidevineNewCasIdUpperBound + 1), + ::testing::Values(false))); + +// Verifies Section header and pointer field may be prepended to ECM. +class EcmParserSectionHeaderTest + : public testing::Test, + public ::testing::WithParamInterface {}; + +TEST_P(EcmParserSectionHeaderTest, EcmWithSectionHeaderOnly) { + std::vector ecm_data = BuildEcm(kWidevineCasId, kEcmVersion2); + const std::vector section_header = {GetParam(), 0, 0}; + ecm_data.insert(ecm_data.begin(), section_header.begin(), + section_header.end()); + ASSERT_TRUE(wvcas::EcmParser::Create(ecm_data) != nullptr); +} + +TEST_P(EcmParserSectionHeaderTest, EcmWithSectionHeaderAndPointerField) { + std::vector ecm_data = BuildEcm(kWidevineCasId, kEcmVersion2); + const std::vector section_header = {kPointerFieldZero, GetParam(), 0, + 0}; + ecm_data.insert(ecm_data.begin(), section_header.begin(), + section_header.end()); + ASSERT_TRUE(wvcas::EcmParser::Create(ecm_data) != nullptr); +} + +INSTANTIATE_TEST_SUITE_P(EcmWithSectionHeader, EcmParserSectionHeaderTest, + ::testing::Values(kSectionHeader1, kSectionHeader2)); + +} // namespace +} // namespace wvcas \ No newline at end of file diff --git a/tests/src/ecm_parser_v2_test.cpp b/tests/src/ecm_parser_v2_test.cpp new file mode 100644 index 0000000..9b6d899 --- /dev/null +++ b/tests/src/ecm_parser_v2_test.cpp @@ -0,0 +1,270 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#include "ecm_parser_v2.h" + +#include +#include + +namespace { + +constexpr int kCasIdSizeBytes = 2; +constexpr int kModeSizeBytes = 1; +constexpr int kVersionSizeBytes = 1; +constexpr int kIVFlagsSizeBytes = 1; +constexpr int kEntitlementKeyIDSizeBytes = 16; +constexpr int kContentKeyIDSizeBytes = 16; +constexpr int kContentKeyDataSize = 16; +constexpr int kWrappedKeyIVSizeBytes = 16; + +constexpr int kEcmDescriptorSizeBytes = + kCasIdSizeBytes + kModeSizeBytes + kVersionSizeBytes + kIVFlagsSizeBytes; + +constexpr int kECMVersion = 2; +// The cipher mode flags field in the ECM V2 is 4 bits. +constexpr uint8_t kAESCBCCryptoModeFlagsVal = (0x0 << 1); +constexpr uint8_t kAESCTRCryptoModeFlagsVal = (0x1 << 1); +constexpr uint8_t kDvbCsa2CryptoModeFlagsVal = (0x2 << 1); +constexpr uint8_t kDvbCsa3CryptoModeFlagsVal = (0x3 << 1); +constexpr uint8_t kDvbOFBCryptoModeFlagsVal = (0x4 << 1); +constexpr uint8_t kDvbSCTECryptoModeFlagsVal = (0x5 << 1); +constexpr uint8_t kRotationFlag = (0x1 << 0); +constexpr uint8_t kContentIVSizeFlag = (0x1 << 6); + +constexpr uint8_t kEntitlementKeyIDFill = '1'; +constexpr uint8_t kEvenContentKeyIDFill = '2'; +constexpr uint8_t kEvenContentKeyDataFill = '3'; +constexpr uint8_t kEvenWrappedKeyIVFill = '4'; +constexpr uint8_t kEvenContentKeyIVFill = '5'; +constexpr uint8_t kOddContentKeyIDFill = '6'; +constexpr uint8_t kOddContentKeyDataFill = '7'; +constexpr uint8_t kOddWrappedKeyIVFill = '8'; +constexpr uint8_t kOddContentKeyIVFill = '9'; + +constexpr size_t kMaxEcmSizeBytes = 184; +constexpr uint16_t kWidevineCasId = 0x4AD4; + +} // namespace + +class EcmParserV2Test : public testing::Test { + protected: + void SetUp() { BuildEcm(/*with_rotation=*/true, /*content_iv_flag=*/false); } + size_t ContentKeyIVSize(bool content_iv_flag); + size_t CalculateEcmSize(bool with_rotation, bool content_iv_flag = false); + void BuildEcm(bool with_rotation, bool content_iv_flag); + + std::vector ecm_data_; + std::unique_ptr parser_; +}; + +size_t EcmParserV2Test::ContentKeyIVSize(bool content_iv_flag) { + // Content key iv is 8 bytes if Content_IV flag is zero, and 16 bytes + // othersize. + return content_iv_flag ? 16 : 8; +} + +size_t EcmParserV2Test::CalculateEcmSize(bool with_rotation, + bool content_iv_flag) { + size_t ecm_key_data_size = + kContentKeyIDSizeBytes + kContentKeyDataSize + kWrappedKeyIVSizeBytes + + kEntitlementKeyIDSizeBytes + ContentKeyIVSize(content_iv_flag); + return kEcmDescriptorSizeBytes + ecm_key_data_size * (with_rotation ? 2 : 1); +} + +void EcmParserV2Test::BuildEcm(bool with_rotation, bool content_iv_flag) { + ecm_data_.clear(); + ecm_data_.reserve(CalculateEcmSize(with_rotation, content_iv_flag)); + ecm_data_.resize(kCasIdSizeBytes, 0); + ecm_data_[0] = kWidevineCasId >> 8; + ecm_data_[1] = kWidevineCasId & 0xff; + ecm_data_.resize(ecm_data_.size() + kVersionSizeBytes, kECMVersion); + ecm_data_.resize(ecm_data_.size() + kModeSizeBytes, + kAESCBCCryptoModeFlagsVal); + uint8_t iv_flag_value = content_iv_flag ? kContentIVSizeFlag : 0; + ecm_data_.resize(ecm_data_.size() + kIVFlagsSizeBytes, iv_flag_value); + ASSERT_EQ(kEcmDescriptorSizeBytes, ecm_data_.size()); + + // Even key fields. + ecm_data_.resize(ecm_data_.size() + kEntitlementKeyIDSizeBytes, + kEntitlementKeyIDFill); + ecm_data_.resize(ecm_data_.size() + kContentKeyIDSizeBytes, + kEvenContentKeyIDFill); + ecm_data_.resize(ecm_data_.size() + kContentKeyDataSize, + kEvenContentKeyDataFill); + ecm_data_.resize(ecm_data_.size() + kWrappedKeyIVSizeBytes, + kEvenWrappedKeyIVFill); + ecm_data_.resize(ecm_data_.size() + ContentKeyIVSize(content_iv_flag), + kEvenContentKeyIVFill); + ASSERT_EQ(CalculateEcmSize(false, content_iv_flag), ecm_data_.size()); + + if (with_rotation) { + // Entitlement key id field for odd key. + ecm_data_.resize(ecm_data_.size() + kEntitlementKeyIDSizeBytes, + kEntitlementKeyIDFill); + ecm_data_.resize(ecm_data_.size() + kContentKeyIDSizeBytes, + kOddContentKeyIDFill); + ecm_data_.resize(ecm_data_.size() + kContentKeyDataSize, + kOddContentKeyDataFill); + ecm_data_.resize(ecm_data_.size() + kWrappedKeyIVSizeBytes, + kOddWrappedKeyIVFill); + ecm_data_.resize(ecm_data_.size() + ContentKeyIVSize(content_iv_flag), + kOddContentKeyIVFill); + ASSERT_EQ(CalculateEcmSize(true, content_iv_flag), ecm_data_.size()); + } +} + +TEST_F(EcmParserV2Test, FieldsWithoutKeyRotation) { + bool content_key_iv_16b = false; + ecm_data_.resize(CalculateEcmSize(false, content_key_iv_16b)); + ASSERT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_)); + ASSERT_FALSE(parser_->rotation_enabled()); + + std::vector test_data; + test_data.resize(kEntitlementKeyIDSizeBytes, kEntitlementKeyIDFill); + EXPECT_EQ(test_data, + parser_->entitlement_key_id(wvcas::KeySlotId::kEvenKeySlot)); + + test_data.clear(); + test_data.resize(kContentKeyIDSizeBytes, kEvenContentKeyIDFill); + EXPECT_EQ(test_data, parser_->content_key_id(wvcas::KeySlotId::kEvenKeySlot)); + + test_data.clear(); + test_data.resize(kContentKeyIDSizeBytes, kEvenContentKeyDataFill); + EXPECT_EQ(test_data, + parser_->wrapped_key_data(wvcas::KeySlotId::kEvenKeySlot)); + + test_data.clear(); + test_data.resize(kWrappedKeyIVSizeBytes, kEvenWrappedKeyIVFill); + EXPECT_EQ(test_data, parser_->wrapped_key_iv(wvcas::KeySlotId::kEvenKeySlot)); + + test_data.clear(); + test_data.resize(ContentKeyIVSize(content_key_iv_16b), kEvenContentKeyIVFill); + EXPECT_EQ(test_data, parser_->content_iv(wvcas::KeySlotId::kEvenKeySlot)); + + EXPECT_TRUE(parser_->content_key_id(wvcas::KeySlotId::kOddKeySlot).empty()); + EXPECT_TRUE(parser_->wrapped_key_data(wvcas::KeySlotId::kOddKeySlot).empty()); + EXPECT_TRUE(parser_->wrapped_key_iv(wvcas::KeySlotId::kOddKeySlot).empty()); + EXPECT_TRUE(parser_->content_iv(wvcas::KeySlotId::kOddKeySlot).empty()); +} + +TEST_F(EcmParserV2Test, FieldsWithKeyRotation) { + ecm_data_[3] |= kRotationFlag; + ASSERT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_)); + ASSERT_TRUE(parser_->rotation_enabled()); + + std::vector test_data; + test_data.resize(kEntitlementKeyIDSizeBytes, kEntitlementKeyIDFill); + EXPECT_EQ(test_data, + parser_->entitlement_key_id(wvcas::KeySlotId::kEvenKeySlot)); + + test_data.clear(); + test_data.resize(kContentKeyIDSizeBytes, kEvenContentKeyIDFill); + EXPECT_EQ(test_data, parser_->content_key_id(wvcas::KeySlotId::kEvenKeySlot)); + + test_data.clear(); + test_data.resize(kContentKeyIDSizeBytes, kEvenContentKeyDataFill); + EXPECT_EQ(test_data, + parser_->wrapped_key_data(wvcas::KeySlotId::kEvenKeySlot)); + + test_data.clear(); + test_data.resize(kWrappedKeyIVSizeBytes, kEvenWrappedKeyIVFill); + EXPECT_EQ(test_data, parser_->wrapped_key_iv(wvcas::KeySlotId::kEvenKeySlot)); + + test_data.clear(); + bool content_key_iv_16b = false; + test_data.resize(ContentKeyIVSize(content_key_iv_16b), kEvenContentKeyIVFill); + EXPECT_EQ(test_data, parser_->content_iv(wvcas::KeySlotId::kEvenKeySlot)); + + test_data.clear(); + test_data.resize(kContentKeyIDSizeBytes, kOddContentKeyIDFill); + EXPECT_EQ(test_data, parser_->content_key_id(wvcas::KeySlotId::kOddKeySlot)); + + test_data.clear(); + test_data.resize(kContentKeyIDSizeBytes, kOddContentKeyDataFill); + EXPECT_EQ(test_data, + parser_->wrapped_key_data(wvcas::KeySlotId::kOddKeySlot)); + + test_data.clear(); + test_data.resize(kWrappedKeyIVSizeBytes, kOddWrappedKeyIVFill); + EXPECT_EQ(test_data, parser_->wrapped_key_iv(wvcas::KeySlotId::kOddKeySlot)); + + test_data.clear(); + test_data.resize(ContentKeyIVSize(content_key_iv_16b), kOddContentKeyIVFill); + EXPECT_EQ(test_data, parser_->content_iv(wvcas::KeySlotId::kOddKeySlot)); +} + +TEST_F(EcmParserV2Test, create) { + EXPECT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_)); + + ecm_data_.resize(4); + EXPECT_FALSE(wvcas::EcmParserV2::create(ecm_data_, &parser_)); + + ecm_data_.resize(4 + CalculateEcmSize(false)); + EXPECT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_)); + + ecm_data_.resize(kMaxEcmSizeBytes); + EXPECT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_)); + + ecm_data_.resize(CalculateEcmSize(true)); + EXPECT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_)); + EXPECT_FALSE(wvcas::EcmParserV2::create(ecm_data_, nullptr)); + + ecm_data_.resize(CalculateEcmSize(true)); + EXPECT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_)); + EXPECT_FALSE(wvcas::EcmParserV2::create(ecm_data_, nullptr)); +} + +TEST_F(EcmParserV2Test, crypto_mode) { + ASSERT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_)); + EXPECT_EQ(parser_->crypto_mode(), wvcas::CryptoMode::kAesCBC); + + ecm_data_[3] = kAESCTRCryptoModeFlagsVal; + ASSERT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_)); + EXPECT_EQ(parser_->crypto_mode(), wvcas::CryptoMode::kAesCTR); + + ecm_data_[3] = kDvbCsa2CryptoModeFlagsVal; + ASSERT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_)); + EXPECT_EQ(parser_->crypto_mode(), wvcas::CryptoMode::kDvbCsa2); + + ecm_data_[3] = kDvbCsa3CryptoModeFlagsVal; + ASSERT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_)); + EXPECT_EQ(parser_->crypto_mode(), wvcas::CryptoMode::kDvbCsa3); + + ecm_data_[3] = kDvbOFBCryptoModeFlagsVal; + ASSERT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_)); + EXPECT_EQ(parser_->crypto_mode(), wvcas::CryptoMode::kAesOFB); + + ecm_data_[3] = kDvbSCTECryptoModeFlagsVal; + ASSERT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_)); + EXPECT_EQ(parser_->crypto_mode(), wvcas::CryptoMode::kAesSCTE); +} + +TEST_F(EcmParserV2Test, ContentKeyIVSizes) { + bool with_rotation = true; + bool iv_flag = false; + ecm_data_.resize(CalculateEcmSize(with_rotation, iv_flag)); + ASSERT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_)); + EXPECT_EQ(parser_->content_iv_size(), ContentKeyIVSize(iv_flag)); + + iv_flag = true; + ecm_data_[4] = kContentIVSizeFlag; + ecm_data_.resize(CalculateEcmSize(with_rotation, iv_flag)); + ASSERT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_)); + EXPECT_EQ(parser_->content_iv_size(), ContentKeyIVSize(iv_flag)); +} + +TEST_F(EcmParserV2Test, AgeRestriction) { + ASSERT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_)); + EXPECT_EQ(0, parser_->age_restriction()); + + uint8_t age_restriction = 16; + ecm_data_[4] |= age_restriction << 1; + ASSERT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_)); + EXPECT_EQ(age_restriction, parser_->age_restriction()); +} + +TEST_F(EcmParserV2Test, Version) { + EXPECT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_)); + EXPECT_EQ(parser_->version(), kECMVersion); +} diff --git a/tests/src/ecm_parser_v3_test.cpp b/tests/src/ecm_parser_v3_test.cpp new file mode 100644 index 0000000..f0d4f33 --- /dev/null +++ b/tests/src/ecm_parser_v3_test.cpp @@ -0,0 +1,443 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#include "ecm_parser_v3.h" + +#include +#include + +#include + +#include "media_cas.pb.h" + +namespace wvcas { +namespace { + +using video_widevine::EcmGroupKeyData; +using video_widevine::EcmMetaData; +using video_widevine::EcmPayload; +using video_widevine::SignedEcmPayload; + +constexpr int kEcmVersion = 3; +constexpr uint16_t kWidevineCasId = 0x4AD4; +constexpr int kEcmHeaderSizeByte = 3; + +constexpr char kWrappedKeyIv[] = "wrapped_key_iv.."; +constexpr char kWrappedKeyIv2[] = "wrapped_key_iv.2"; +constexpr char kEntitlementId[] = "entitlement_id.."; +constexpr char kEntitlementId2[] = "entitlement_id.2"; +constexpr char kContentIv[] = "c_iv....c_iv...."; +constexpr char kContentIv2[] = "c_iv....c_iv...2"; +constexpr char kWrappedContentKey[] = "wrapped_key....."; +constexpr char kWrappedContentKey2[] = "wrapped_key....2"; + +void WriteEcmHeader(std::vector* ecm) { + ecm->push_back(kWidevineCasId >> 8); + ecm->push_back(kWidevineCasId & 0xff); + ecm->push_back(kEcmVersion); +} + +std::vector GenerateEcm(const SignedEcmPayload& signed_ecm_payload) { + std::vector ecm; + WriteEcmHeader(&ecm); + ecm.resize(kEcmHeaderSizeByte + signed_ecm_payload.ByteSize()); + signed_ecm_payload.SerializeToArray(ecm.data() + kEcmHeaderSizeByte, + signed_ecm_payload.ByteSize()); + return ecm; +} + +TEST(EcmParserV3Test, CreateWithEmptyEcmFail) { + std::vector ecm; + EXPECT_TRUE(EcmParserV3::Create(ecm) == nullptr); +} + +TEST(EcmParserV3Test, CreateWithOnlyEcmHeaderFail) { + std::vector ecm; + WriteEcmHeader(&ecm); + EXPECT_TRUE(EcmParserV3::Create(ecm) == nullptr); +} + +TEST(EcmParserV3Test, CreateWithInvalidSignedEcmPayloadFail) { + std::vector ecm; + WriteEcmHeader(&ecm); + // appends some chars as payload + ecm.resize(100, 'x'); + + EXPECT_TRUE(EcmParserV3::Create(ecm) == nullptr); +} + +TEST(EcmParserV3Test, CreateWithInvalidSerializedEcmFail) { + SignedEcmPayload signed_ecm_payload; + signed_ecm_payload.set_serialized_payload("invalid"); + std::vector ecm = GenerateEcm(signed_ecm_payload); + + EXPECT_TRUE(EcmParserV3::Create(ecm) == nullptr); +} + +TEST(EcmParserV3Test, CreateWithEvenKeySuccess) { + EcmPayload ecm_payload; + ecm_payload.mutable_even_key_data()->set_entitlement_key_id(kEntitlementId); + ecm_payload.mutable_even_key_data()->set_wrapped_key_data(kWrappedContentKey); + ecm_payload.mutable_even_key_data()->set_content_iv(kContentIv); + ecm_payload.mutable_even_key_data()->set_wrapped_key_iv(kWrappedKeyIv); + SignedEcmPayload signed_ecm_payload; + signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString()); + std::vector ecm = GenerateEcm(signed_ecm_payload); + + std::unique_ptr parser = EcmParserV3::Create(ecm); + + ASSERT_TRUE(parser != nullptr); + EXPECT_EQ(parser->version(), kEcmVersion); + EXPECT_EQ(parser->age_restriction(), 0); + EXPECT_EQ(parser->crypto_mode(), CryptoMode::kInvalid); + EXPECT_FALSE(parser->has_fingerprinting()); + EXPECT_FALSE(parser->has_service_blocking()); + EXPECT_EQ(parser->ecm_serialized_payload(), ecm_payload.SerializeAsString()); + EXPECT_TRUE(parser->signature().empty()); + EXPECT_FALSE(parser->rotation_enabled()); + EXPECT_EQ(parser->content_iv_size(), 16); + std::vector result = + parser->entitlement_key_id(KeySlotId::kEvenKeySlot); + EXPECT_EQ(std::string(result.begin(), result.end()), kEntitlementId); + result = parser->wrapped_key_data(KeySlotId::kEvenKeySlot); + EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedContentKey); + result = parser->content_iv(KeySlotId::kEvenKeySlot); + EXPECT_EQ(std::string(result.begin(), result.end()), kContentIv); + result = parser->wrapped_key_iv(KeySlotId::kEvenKeySlot); + EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedKeyIv); +} + +TEST(EcmParserV3Test, CreateWithEvenOddKeysSuccess) { + EcmPayload ecm_payload; + ecm_payload.mutable_even_key_data()->set_entitlement_key_id(kEntitlementId); + ecm_payload.mutable_even_key_data()->set_wrapped_key_data(kWrappedContentKey); + ecm_payload.mutable_even_key_data()->set_content_iv(kContentIv); + ecm_payload.mutable_even_key_data()->set_wrapped_key_iv(kWrappedKeyIv); + ecm_payload.mutable_odd_key_data()->set_entitlement_key_id(kEntitlementId2); + ecm_payload.mutable_odd_key_data()->set_wrapped_key_data(kWrappedContentKey2); + ecm_payload.mutable_odd_key_data()->set_content_iv(kContentIv2); + ecm_payload.mutable_odd_key_data()->set_wrapped_key_iv(kWrappedKeyIv2); + SignedEcmPayload signed_ecm_payload; + signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString()); + std::vector ecm = GenerateEcm(signed_ecm_payload); + + std::unique_ptr parser = EcmParserV3::Create(ecm); + + ASSERT_TRUE(parser != nullptr); + EXPECT_TRUE(parser->rotation_enabled()); + std::vector result = + parser->entitlement_key_id(KeySlotId::kEvenKeySlot); + EXPECT_EQ(std::string(result.begin(), result.end()), kEntitlementId); + result = parser->wrapped_key_data(KeySlotId::kEvenKeySlot); + EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedContentKey); + result = parser->content_iv(KeySlotId::kEvenKeySlot); + EXPECT_EQ(std::string(result.begin(), result.end()), kContentIv); + result = parser->wrapped_key_iv(KeySlotId::kEvenKeySlot); + EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedKeyIv); + result = parser->entitlement_key_id(KeySlotId::kOddKeySlot); + EXPECT_EQ(std::string(result.begin(), result.end()), kEntitlementId2); + result = parser->wrapped_key_data(KeySlotId::kOddKeySlot); + EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedContentKey2); + result = parser->content_iv(KeySlotId::kOddKeySlot); + EXPECT_EQ(std::string(result.begin(), result.end()), kContentIv2); + result = parser->wrapped_key_iv(KeySlotId::kOddKeySlot); + EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedKeyIv2); +} + +TEST(EcmParserV3Test, CreateWithOmittedOddKeyFieldsSuccess) { + EcmPayload ecm_payload; + ecm_payload.mutable_even_key_data()->set_entitlement_key_id(kEntitlementId); + ecm_payload.mutable_even_key_data()->set_wrapped_key_data(kWrappedContentKey); + ecm_payload.mutable_even_key_data()->set_content_iv(kContentIv); + ecm_payload.mutable_even_key_data()->set_wrapped_key_iv(kWrappedKeyIv); + ecm_payload.mutable_odd_key_data()->set_wrapped_key_data(kWrappedContentKey2); + SignedEcmPayload signed_ecm_payload; + signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString()); + std::vector ecm = GenerateEcm(signed_ecm_payload); + + std::unique_ptr parser = EcmParserV3::Create(ecm); + + ASSERT_TRUE(parser != nullptr); + EXPECT_TRUE(parser->rotation_enabled()); + std::vector result = + parser->entitlement_key_id(KeySlotId::kEvenKeySlot); + EXPECT_EQ(std::string(result.begin(), result.end()), kEntitlementId); + result = parser->wrapped_key_data(KeySlotId::kEvenKeySlot); + EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedContentKey); + result = parser->content_iv(KeySlotId::kEvenKeySlot); + EXPECT_EQ(std::string(result.begin(), result.end()), kContentIv); + result = parser->wrapped_key_iv(KeySlotId::kEvenKeySlot); + EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedKeyIv); + result = parser->entitlement_key_id(KeySlotId::kOddKeySlot); + EXPECT_EQ(std::string(result.begin(), result.end()), kEntitlementId); + result = parser->wrapped_key_data(KeySlotId::kOddKeySlot); + EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedContentKey2); + result = parser->content_iv(KeySlotId::kOddKeySlot); + EXPECT_EQ(std::string(result.begin(), result.end()), kContentIv); + result = parser->wrapped_key_iv(KeySlotId::kOddKeySlot); + EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedKeyIv); +} + +TEST(EcmParserV3Test, AgeRestrictionSuccess) { + const int expected_age_restriction = 3; + EcmPayload ecm_payload; + ecm_payload.mutable_meta_data()->set_age_restriction( + expected_age_restriction); + SignedEcmPayload signed_ecm_payload; + signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString()); + std::vector ecm = GenerateEcm(signed_ecm_payload); + + std::unique_ptr parser = EcmParserV3::Create(ecm); + + ASSERT_TRUE(parser != nullptr); + EXPECT_EQ(parser->age_restriction(), expected_age_restriction); +} + +class EcmParserV3AgeRestrictionTest + : public testing::Test, + public testing::WithParamInterface {}; + +TEST_P(EcmParserV3AgeRestrictionTest, ExpectedAgeRestriction) { + const uint8_t expected_age_restriction = GetParam(); + EcmPayload ecm_payload; + ecm_payload.mutable_meta_data()->set_age_restriction( + expected_age_restriction); + SignedEcmPayload signed_ecm_payload; + signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString()); + std::vector ecm = GenerateEcm(signed_ecm_payload); + + std::unique_ptr parser = EcmParserV3::Create(ecm); + + ASSERT_TRUE(parser != nullptr); + EXPECT_EQ(parser->age_restriction(), expected_age_restriction); +} + +INSTANTIATE_TEST_SUITE_P(EcmParserV3AgeRestrictionTest, + EcmParserV3AgeRestrictionTest, + testing::Values(0, 3, 18)); + +class EcmParserV3CipherModeTest + : public testing::Test, + public testing::WithParamInterface< + testing::tuple> {}; + +TEST_P(EcmParserV3CipherModeTest, ExpectedCipherMode) { + const CryptoMode expected = std::get<0>(GetParam()); + EcmPayload ecm_payload; + ecm_payload.mutable_meta_data()->set_cipher_mode(std::get<1>(GetParam())); + SignedEcmPayload signed_ecm_payload; + signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString()); + std::vector ecm = GenerateEcm(signed_ecm_payload); + + std::unique_ptr parser = EcmParserV3::Create(ecm); + + ASSERT_TRUE(parser != nullptr); + EXPECT_EQ(parser->crypto_mode(), expected); +} + +INSTANTIATE_TEST_SUITE_P( + EcmParserV3CipherModeTest, EcmParserV3CipherModeTest, + testing::Values( + std::make_tuple(CryptoMode::kAesCBC, EcmMetaData::AES_CBC), + std::make_tuple(CryptoMode::kAesCTR, EcmMetaData::AES_CTR), + std::make_tuple(CryptoMode::kDvbCsa2, EcmMetaData::DVB_CSA2), + std::make_tuple(CryptoMode::kDvbCsa3, EcmMetaData::DVB_CSA3), + std::make_tuple(CryptoMode::kAesOFB, EcmMetaData::AES_OFB), + std::make_tuple(CryptoMode::kAesSCTE, EcmMetaData::AES_SCTE52), + std::make_tuple(CryptoMode::kAesECB, EcmMetaData::AES_ECB))); + +TEST(EcmParserV3Test, FingerprintingSuccess) { + EcmPayload ecm_payload; + ecm_payload.mutable_fingerprinting()->set_control("control"); + SignedEcmPayload signed_ecm_payload; + signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString()); + std::vector ecm = GenerateEcm(signed_ecm_payload); + + std::unique_ptr parser = EcmParserV3::Create(ecm); + + ASSERT_TRUE(parser != nullptr); + EXPECT_TRUE(parser->has_fingerprinting()); + EXPECT_EQ(parser->fingerprinting().SerializeAsString(), + ecm_payload.fingerprinting().SerializeAsString()); +} + +TEST(EcmParserV3Test, ServiceBlockingSuccess) { + EcmPayload ecm_payload; + ecm_payload.mutable_service_blocking()->add_device_groups("group"); + SignedEcmPayload signed_ecm_payload; + signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString()); + std::vector ecm = GenerateEcm(signed_ecm_payload); + + std::unique_ptr parser = EcmParserV3::Create(ecm); + + ASSERT_TRUE(parser != nullptr); + EXPECT_TRUE(parser->has_service_blocking()); + EXPECT_EQ(parser->service_blocking().SerializeAsString(), + ecm_payload.service_blocking().SerializeAsString()); +} + +TEST(EcmParserV3Test, SignatureSuccess) { + const std::string expected_signature = "signature"; + SignedEcmPayload signed_ecm_payload; + signed_ecm_payload.set_signature(expected_signature); + std::vector ecm = GenerateEcm(signed_ecm_payload); + + std::unique_ptr parser = EcmParserV3::Create(ecm); + + ASSERT_TRUE(parser != nullptr); + EXPECT_EQ(parser->signature(), expected_signature); +} + +TEST(EcmParserV3Test, SetGroupIdSuccess) { + const std::string group_id = "group_id"; + const std::string group_id2 = "another_group"; + EcmPayload ecm_payload; + EcmGroupKeyData* group_key_data = ecm_payload.add_group_key_data(); + group_key_data->set_group_id(group_id); + group_key_data = ecm_payload.add_group_key_data(); + group_key_data->set_group_id(group_id2); + SignedEcmPayload signed_ecm_payload; + signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString()); + std::vector ecm = GenerateEcm(signed_ecm_payload); + + std::unique_ptr parser = EcmParserV3::Create(ecm); + + ASSERT_TRUE(parser != nullptr); + EXPECT_TRUE(parser->set_group_id(group_id)); + EXPECT_TRUE(parser->set_group_id(group_id2)); +} + +TEST(EcmParserV3Test, SetUnknownGroupIdFail) { + EcmPayload ecm_payload; + EcmGroupKeyData* group_key_data = ecm_payload.add_group_key_data(); + group_key_data->set_group_id("group_id"); + SignedEcmPayload signed_ecm_payload; + signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString()); + std::vector ecm = GenerateEcm(signed_ecm_payload); + + std::unique_ptr parser = EcmParserV3::Create(ecm); + + ASSERT_TRUE(parser != nullptr); + EXPECT_FALSE(parser->set_group_id("unknown")); +} + +TEST(EcmParserV3Test, ParserWithGroupIdSuccess) { + const std::string group_id = "group_id"; + EcmPayload ecm_payload; + EcmGroupKeyData* group_key_data = ecm_payload.add_group_key_data(); + group_key_data->set_group_id(group_id); + group_key_data->mutable_even_key_data()->set_entitlement_key_id( + kEntitlementId); + group_key_data->mutable_even_key_data()->set_wrapped_key_data( + kWrappedContentKey); + group_key_data->mutable_even_key_data()->set_content_iv(kContentIv); + group_key_data->mutable_even_key_data()->set_wrapped_key_iv(kWrappedKeyIv); + ecm_payload.mutable_even_key_data()->set_entitlement_key_id(kEntitlementId2); + ecm_payload.mutable_even_key_data()->set_wrapped_key_data( + kWrappedContentKey2); + ecm_payload.mutable_even_key_data()->set_content_iv(kContentIv2); + ecm_payload.mutable_even_key_data()->set_wrapped_key_iv(kWrappedKeyIv2); + + SignedEcmPayload signed_ecm_payload; + signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString()); + std::vector ecm = GenerateEcm(signed_ecm_payload); + + std::unique_ptr parser = EcmParserV3::Create(ecm); + + ASSERT_TRUE(parser != nullptr); + // If group Id is not set, the normal keys will be returned. + std::vector result = + parser->entitlement_key_id(KeySlotId::kEvenKeySlot); + EXPECT_EQ(std::string(result.begin(), result.end()), kEntitlementId2); + result = parser->wrapped_key_data(KeySlotId::kEvenKeySlot); + EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedContentKey2); + result = parser->content_iv(KeySlotId::kEvenKeySlot); + EXPECT_EQ(std::string(result.begin(), result.end()), kContentIv2); + result = parser->wrapped_key_iv(KeySlotId::kEvenKeySlot); + EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedKeyIv2); + + // Now set the group id. + EXPECT_TRUE(parser->set_group_id(group_id)); + result = parser->entitlement_key_id(KeySlotId::kEvenKeySlot); + EXPECT_EQ(std::string(result.begin(), result.end()), kEntitlementId); + result = parser->wrapped_key_data(KeySlotId::kEvenKeySlot); + EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedContentKey); + result = parser->content_iv(KeySlotId::kEvenKeySlot); + EXPECT_EQ(std::string(result.begin(), result.end()), kContentIv); + result = parser->wrapped_key_iv(KeySlotId::kEvenKeySlot); + EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedKeyIv); +} + +TEST(EcmParserV3Test, ParserGroupKeysWithOmittedFieldsSuccess) { + const std::string group_id = "group_id"; + EcmPayload ecm_payload; + EcmGroupKeyData* group_key_data = ecm_payload.add_group_key_data(); + group_key_data->set_group_id(group_id); + group_key_data->mutable_even_key_data()->set_entitlement_key_id( + kEntitlementId); + group_key_data->mutable_even_key_data()->set_wrapped_key_data( + kWrappedContentKey); + // Content IV and wrapped key iv is omitted in |group_key_data|/ + ecm_payload.mutable_even_key_data()->set_entitlement_key_id(kEntitlementId2); + ecm_payload.mutable_even_key_data()->set_wrapped_key_data( + kWrappedContentKey2); + ecm_payload.mutable_even_key_data()->set_content_iv(kContentIv2); + ecm_payload.mutable_even_key_data()->set_wrapped_key_iv(kWrappedKeyIv2); + SignedEcmPayload signed_ecm_payload; + signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString()); + std::vector ecm = GenerateEcm(signed_ecm_payload); + + std::unique_ptr parser = EcmParserV3::Create(ecm); + ASSERT_TRUE(parser != nullptr); + EXPECT_TRUE(parser->set_group_id(group_id)); + + std::vector result = + parser->entitlement_key_id(KeySlotId::kEvenKeySlot); + EXPECT_EQ(std::string(result.begin(), result.end()), kEntitlementId); + result = parser->wrapped_key_data(KeySlotId::kEvenKeySlot); + EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedContentKey); + // Content IV and wrapped key iv are from normal non-group key. + result = parser->content_iv(KeySlotId::kEvenKeySlot); + EXPECT_EQ(std::string(result.begin(), result.end()), kContentIv2); + result = parser->wrapped_key_iv(KeySlotId::kEvenKeySlot); + EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedKeyIv2); +} + +TEST(EcmParserV3Test, EntitlementRotationEnabledSuccess) { + const uint32_t entitlement_period_index = 10; + const uint32_t entitlement_rotation_window_left = 100; + EcmPayload ecm_payload; + ecm_payload.mutable_meta_data()->set_entitlement_period_index( + entitlement_period_index); + ecm_payload.mutable_meta_data()->set_entitlement_rotation_window_left( + entitlement_rotation_window_left); + SignedEcmPayload signed_ecm_payload; + signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString()); + std::vector ecm = GenerateEcm(signed_ecm_payload); + + std::unique_ptr parser = EcmParserV3::Create(ecm); + + ASSERT_TRUE(parser != nullptr); + EXPECT_TRUE(parser->is_entitlement_rotation_enabled()); + EXPECT_EQ(parser->entitlement_period_index(), entitlement_period_index); + EXPECT_EQ(parser->entitlement_rotation_window_left(), + entitlement_rotation_window_left); +} + +TEST(EcmParserV3Test, EntitlementRotationDefaultDisabledSuccess) { + EcmPayload ecm_payload; + // Put something in the payload just to make the ECM valid. + ecm_payload.mutable_even_key_data()->set_entitlement_key_id(kEntitlementId); + SignedEcmPayload signed_ecm_payload; + signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString()); + std::vector ecm = GenerateEcm(signed_ecm_payload); + + std::unique_ptr parser = EcmParserV3::Create(ecm); + + ASSERT_TRUE(parser != nullptr); + EXPECT_FALSE(parser->is_entitlement_rotation_enabled()); +} + +} // namespace +} // namespace wvcas diff --git a/tests/src/emm_parser_test.cpp b/tests/src/emm_parser_test.cpp new file mode 100644 index 0000000..89304b7 --- /dev/null +++ b/tests/src/emm_parser_test.cpp @@ -0,0 +1,133 @@ +// Copyright 2020 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#include "emm_parser.h" + +#include +#include + +#include +#include + +#include "cas_types.h" + +namespace wvcas { +namespace { + +using video_widevine::EmmPayload; +using video_widevine::SignedEmmPayload; + +constexpr uint8_t kSectionHeader = 0x82; +constexpr int64_t kDefaultTimestamp = 1598905921; +constexpr char kDefaultSignature[] = "signature"; + +class EmmParserTest : public testing::Test { + protected: + EmmParserTest() + : timestamp_(kDefaultTimestamp), signature_(kDefaultSignature) {} + void SetSectionHeader(const std::vector section_header) { + section_header_.assign(section_header.begin(), section_header.end()); + } + void SetTimestamp(uint64_t timestamp) { timestamp_ = timestamp; } + void SetSignedEmm(const std::string& signed_emm) { + serialized_signed_emm_ = signed_emm; + } + void SetEmmPayload(const std::string& serialized_payload) { + serialized_emm_payload_ = serialized_payload; + } + void SetSignature(const std::string& signature) { signature_ = signature; } + + std::vector BuildEmm() const { + std::vector emm_data(section_header_.begin(), + section_header_.end()); + if (!serialized_signed_emm_.empty()) { + emm_data.insert(emm_data.end(), serialized_signed_emm_.begin(), + serialized_signed_emm_.end()); + return emm_data; + } + + SignedEmmPayload signed_emm; + if (serialized_emm_payload_.empty()) { + EmmPayload emm_payload; + emm_payload.set_timestamp_secs(timestamp_); + emm_payload.SerializeToString(signed_emm.mutable_serialized_payload()); + } else { + signed_emm.set_serialized_payload(serialized_emm_payload_); + } + signed_emm.set_signature(signature_); + + emm_data.resize(emm_data.size() + signed_emm.ByteSizeLong()); + signed_emm.SerializeToArray(&emm_data[section_header_.size()], + emm_data.size()); + return emm_data; + } + + void ValidateParserAgainstDefault(const EmmParser* const parser) { + ASSERT_NE(parser, nullptr); + EXPECT_EQ(parser->timestamp(), kDefaultTimestamp); + EmmPayload expected_emm_payload; + expected_emm_payload.set_timestamp_secs(timestamp_); + EXPECT_EQ(parser->emm_payload().SerializeAsString(), + expected_emm_payload.SerializeAsString()); + EXPECT_EQ(parser->signature(), kDefaultSignature); + } + + private: + std::vector section_header_; + uint64_t timestamp_; + std::string signature_; + std::string serialized_signed_emm_; + std::string serialized_emm_payload_; +}; + +TEST_F(EmmParserTest, ParseDefaultSuccess) { + auto parser = EmmParser::Create(BuildEmm()); + ValidateParserAgainstDefault(parser.get()); +} + +TEST_F(EmmParserTest, EmmWithSectionHeaderSuccess) { + SetSectionHeader({kSectionHeader, 0, 0}); + auto parser = EmmParser::Create(BuildEmm()); + ValidateParserAgainstDefault(parser.get()); +} + +TEST_F(EmmParserTest, EmmWithSectionHeaderAndPointerFieldSuccess) { + SetSectionHeader({0, kSectionHeader, 0, 0}); + auto parser = EmmParser::Create(BuildEmm()); + ValidateParserAgainstDefault(parser.get()); +} + +TEST_F(EmmParserTest, EmmWithMalformedEmmCreateFail) { + SetSignedEmm("some emm"); + EXPECT_THAT(EmmParser::Create(BuildEmm()), ::testing::IsNull()); +} + +TEST_F(EmmParserTest, EmmWithMalformedPayloadCreateFail) { + SetEmmPayload("some payload"); + EXPECT_THAT(EmmParser::Create(BuildEmm()), ::testing::IsNull()); +} + +TEST_F(EmmParserTest, EmmWithNoSignatureCreateFail) { + SetSignature(""); + EXPECT_THAT(EmmParser::Create(BuildEmm()), ::testing::IsNull()); +} + +class EmmParserWrongPrefixTest + : public EmmParserTest, + public ::testing::WithParamInterface> {}; + +TEST_P(EmmParserWrongPrefixTest, EmmWithWrongPrefixCreateFail) { + SetSectionHeader(GetParam()); + EXPECT_THAT(EmmParser::Create(BuildEmm()), ::testing::IsNull()); +} + +INSTANTIATE_TEST_SUITE_P( + EmmParserWrongPrefixes, EmmParserWrongPrefixTest, + ::testing::Values(std::vector({0}), + std::vector({1, 0, 0}), + std::vector({kSectionHeader, 0}), + std::vector({1, kSectionHeader, 0, 0}))); + +} // namespace +} // namespace wvcas diff --git a/tests/src/license_key_status_test.cpp b/tests/src/license_key_status_test.cpp new file mode 100644 index 0000000..8a5423e --- /dev/null +++ b/tests/src/license_key_status_test.cpp @@ -0,0 +1,937 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#include "license_key_status.h" + +#include +#include + +#include + +#include "cas_types.h" + +namespace wvcas { + +namespace { + +static const uint32_t dev_lo_res = 200; +static const uint32_t dev_hi_res = 400; +static const uint32_t dev_top_res = 800; + +static const uint32_t key_lo_res_min = 151; +static const uint32_t key_lo_res_max = 300; +static const uint32_t key_hi_res_min = 301; +static const uint32_t key_hi_res_max = 450; +static const uint32_t key_top_res_min = 451; +static const uint32_t key_top_res_max = 650; + +// Content Keys +static const KeyId ck_sw_crypto = "c_key_SW_SECURE_CRYPTO"; +static const KeyId ck_sw_decode = "c_key_SW_SECURE_DECODE"; +static const KeyId ck_hw_crypto = "c_key_HW_SECURE_CRYPTO"; +static const KeyId ck_hw_decode = "c_key_HW_SECURE_DECODE"; +static const KeyId ck_hw_secure = "c_key_HW_SECURE_ALL"; + +// Operator Session Keys +static const KeyId osk_decrypt = "os_key_generic_decrypt"; +static const KeyId osk_encrypt = "os_key_generic_encrypt"; +static const KeyId osk_sign = "os_key_generic_sign"; +static const KeyId osk_verify = "os_key_generic_verify"; +static const KeyId osk_encrypt_decrypt = "os_key_generic_encrypt_decrypt"; +static const KeyId osk_sign_verify = "os_key_generic_sign_verify"; +static const KeyId osk_all = "os_key_generic_all"; + +// HDCP test keys +static const KeyId ck_sw_crypto_NO_HDCP = "ck_sw_crypto_NO_HDCP"; +static const KeyId ck_hw_secure_NO_HDCP = "ck_hw_secure_NO_HDCP"; +static const KeyId ck_sw_crypto_HDCP_V2_1 = "ck_sw_crypto_HDCP_V2_1"; +static const KeyId ck_hw_secure_HDCP_V2_1 = "ck_hw_secure_HDCP_V2_1"; +static const KeyId ck_sw_crypto_HDCP_NO_OUTPUT = "ck_sw_crypto_HDCP_NO_OUT"; +static const KeyId ck_hw_secure_HDCP_NO_OUTPUT = "ck_hw_secure_HDCP_NO_OUT"; + +// Constraint test keys +static const KeyId ck_NO_HDCP_lo_res = "ck_NO_HDCP_lo_res"; +static const KeyId ck_HDCP_NO_OUTPUT_hi_res = "ck_HDCP_NO_OUTPUT_hi_res"; +static const KeyId ck_HDCP_V2_1_max_res = "ck_HDCP_V2_1_max_res"; +static const KeyId ck_NO_HDCP_dual_res = "ck_NO_HDCP_dual_res"; + +} // namespace + +// protobuf generated classes. +using video_widevine::License; +using video_widevine::LicenseIdentification; +using video_widevine::OFFLINE; +using video_widevine::STREAMING; + +typedef ::video_widevine::License::KeyContainer KeyContainer; +typedef KeyContainer::VideoResolutionConstraint VideoResolutionConstraint; + +class LicenseKeysTest : public ::testing::Test { + protected: + enum KeyFlag { kKeyFlagNull, kKeyFlagFalse, kKeyFlagTrue }; + + static const KeyFlag kEncryptNull = kKeyFlagNull; + static const KeyFlag kEncryptFalse = kKeyFlagFalse; + static const KeyFlag kEncryptTrue = kKeyFlagTrue; + static const KeyFlag kDecryptNull = kKeyFlagNull; + static const KeyFlag kDecryptFalse = kKeyFlagFalse; + static const KeyFlag kDecryptTrue = kKeyFlagTrue; + static const KeyFlag kSignNull = kKeyFlagNull; + static const KeyFlag kSignFalse = kKeyFlagFalse; + static const KeyFlag kSignTrue = kKeyFlagTrue; + static const KeyFlag kVerifyNull = kKeyFlagNull; + static const KeyFlag kVerifyFalse = kKeyFlagFalse; + static const KeyFlag kVerifyTrue = kKeyFlagTrue; + + static const KeyFlag kContentSecureFalse = kKeyFlagFalse; + static const KeyFlag kContentSecureTrue = kKeyFlagTrue; + static const KeyFlag kContentClearFalse = kKeyFlagFalse; + static const KeyFlag kContentClearTrue = kKeyFlagTrue; + + virtual void SetUp() { + LicenseIdentification* id = license_.mutable_id(); + id->set_version(1); + id->set_type(STREAMING); + } + + virtual void AddContentKey( + const KeyId& key_id, bool set_level = false, + KeyContainer::SecurityLevel level = KeyContainer::SW_SECURE_CRYPTO, + bool set_hdcp = false, + KeyContainer::OutputProtection::HDCP hdcp_value = + KeyContainer::OutputProtection::HDCP_NONE, + bool set_constraints = false, + std::vector* constraints = NULL) { + KeyContainer* key = license_.add_key(); + key->set_type(KeyContainer::CONTENT); + if (set_level) { + key->set_level(level); + } + if (set_hdcp) { + KeyContainer::OutputProtection* pro = key->mutable_required_protection(); + pro->set_hdcp(hdcp_value); + } + if (set_constraints) { + for (std::vector::iterator it = + constraints->begin(); + it != constraints->end(); ++it) { + VideoResolutionConstraint* constraint = + key->add_video_resolution_constraints(); + constraint->set_min_resolution_pixels(it->min_resolution_pixels()); + constraint->set_max_resolution_pixels(it->max_resolution_pixels()); + constraint->mutable_required_protection()->set_hdcp( + it->required_protection().hdcp()); + } + } + key->set_id(key_id); + } + + virtual void AddEntitlementKey( + const KeyId& key_id, bool set_level = false, + KeyContainer::SecurityLevel level = KeyContainer::SW_SECURE_CRYPTO, + bool set_hdcp = false, + KeyContainer::OutputProtection::HDCP hdcp_value = + KeyContainer::OutputProtection::HDCP_NONE, + bool set_constraints = false, + std::vector* constraints = NULL) { + AddContentKey(key_id, set_level, level, set_hdcp, hdcp_value, + set_constraints, constraints); + license_.mutable_key(license_.key_size() - 1) + ->set_type(KeyContainer::ENTITLEMENT); + } + + virtual void AddOperatorSessionKey(const KeyId& key_id, + bool set_perms = false, + KeyFlag encrypt = kKeyFlagNull, + KeyFlag decrypt = kKeyFlagNull, + KeyFlag sign = kKeyFlagNull, + KeyFlag verify = kKeyFlagNull) { + KeyContainer* non_content_key = license_.add_key(); + non_content_key->set_type(KeyContainer::OPERATOR_SESSION); + non_content_key->set_id(key_id); + if (set_perms) { + KeyContainer::OperatorSessionKeyPermissions* permissions = + non_content_key->mutable_operator_session_key_permissions(); + if (encrypt != kKeyFlagNull) { + permissions->set_allow_encrypt(encrypt == kKeyFlagTrue); + } + if (decrypt != kKeyFlagNull) { + permissions->set_allow_decrypt(decrypt == kKeyFlagTrue); + } + if (sign != kKeyFlagNull) { + permissions->set_allow_sign(sign == kKeyFlagTrue); + } + if (verify != kKeyFlagNull) { + permissions->set_allow_signature_verify(verify == kKeyFlagTrue); + } + } + } + + virtual void AddSigningKey(const KeyId& key_id) { + KeyContainer* key = license_.add_key(); + key->set_type(KeyContainer::SIGNING); + key->set_id(key_id); + } + + virtual void ExpectAllowedUsageContent(const KeyAllowedUsage& key_usage, + KeyFlag secure, KeyFlag clear, + KeySecurityLevel key_security_level) { + EXPECT_EQ(key_usage.decrypt_to_secure_buffer, secure == kKeyFlagTrue); + EXPECT_EQ(key_usage.decrypt_to_clear_buffer, clear == kKeyFlagTrue); + EXPECT_EQ(key_usage.key_security_level_, key_security_level); + EXPECT_FALSE(key_usage.generic_encrypt); + EXPECT_FALSE(key_usage.generic_decrypt); + EXPECT_FALSE(key_usage.generic_sign); + EXPECT_FALSE(key_usage.generic_verify); + } + + virtual void ExpectAllowedUsageOperator(const KeyAllowedUsage& key_usage, + KeyFlag encrypt, KeyFlag decrypt, + KeyFlag sign, KeyFlag verify) { + EXPECT_FALSE(key_usage.decrypt_to_secure_buffer); + EXPECT_FALSE(key_usage.decrypt_to_clear_buffer); + EXPECT_EQ(key_usage.generic_encrypt, encrypt == kKeyFlagTrue); + EXPECT_EQ(key_usage.generic_decrypt, decrypt == kKeyFlagTrue); + EXPECT_EQ(key_usage.generic_sign, sign == kKeyFlagTrue); + EXPECT_EQ(key_usage.generic_verify, verify == kKeyFlagTrue); + } + + virtual int NumContentKeys() { return content_key_count_; } + + virtual void StageContentKeys() { + content_key_count_ = 0; + AddContentKey(ck_sw_crypto, true, KeyContainer::SW_SECURE_CRYPTO); + content_key_count_++; + AddContentKey(ck_sw_decode, true, KeyContainer::SW_SECURE_DECODE); + content_key_count_++; + AddContentKey(ck_hw_crypto, true, KeyContainer::HW_SECURE_CRYPTO); + content_key_count_++; + AddContentKey(ck_hw_decode, true, KeyContainer::HW_SECURE_DECODE); + content_key_count_++; + AddContentKey(ck_hw_secure, true, KeyContainer::HW_SECURE_ALL); + content_key_count_++; + license_keys_.SetFromLicense(license_); + } + + virtual void StageOperatorSessionKeys() { + AddOperatorSessionKey(osk_decrypt, true, kEncryptNull, kDecryptTrue, + kSignNull, kVerifyNull); + AddOperatorSessionKey(osk_encrypt, true, kEncryptTrue, kDecryptNull, + kSignNull, kVerifyNull); + AddOperatorSessionKey(osk_sign, true, kEncryptNull, kDecryptNull, kSignTrue, + kVerifyNull); + AddOperatorSessionKey(osk_verify, true, kEncryptNull, kDecryptNull, + kSignNull, kVerifyTrue); + AddOperatorSessionKey(osk_encrypt_decrypt, true, kEncryptTrue, kDecryptTrue, + kSignNull, kVerifyNull); + AddOperatorSessionKey(osk_sign_verify, true, kEncryptNull, kDecryptNull, + kSignTrue, kVerifyTrue); + AddOperatorSessionKey(osk_all, true, kEncryptTrue, kDecryptTrue, kSignTrue, + kVerifyTrue); + license_keys_.SetFromLicense(license_); + } + + virtual void StageHdcpKeys() { + content_key_count_ = 0; + AddContentKey(ck_sw_crypto_NO_HDCP, true, KeyContainer::SW_SECURE_CRYPTO, + true, KeyContainer::OutputProtection::HDCP_NONE); + content_key_count_++; + AddContentKey(ck_hw_secure_NO_HDCP, true, KeyContainer::HW_SECURE_ALL, true, + KeyContainer::OutputProtection::HDCP_NONE); + content_key_count_++; + + AddContentKey(ck_sw_crypto_HDCP_V2_1, true, KeyContainer::SW_SECURE_CRYPTO, + true, KeyContainer::OutputProtection::HDCP_V2_1); + content_key_count_++; + AddContentKey(ck_hw_secure_HDCP_V2_1, true, KeyContainer::HW_SECURE_ALL, + true, KeyContainer::OutputProtection::HDCP_V2_1); + content_key_count_++; + + AddContentKey(ck_sw_crypto_HDCP_NO_OUTPUT, true, + KeyContainer::SW_SECURE_CRYPTO, true, + KeyContainer::OutputProtection::HDCP_NO_DIGITAL_OUTPUT); + content_key_count_++; + AddContentKey(ck_hw_secure_HDCP_NO_OUTPUT, true, + KeyContainer::HW_SECURE_ALL, true, + KeyContainer::OutputProtection::HDCP_NO_DIGITAL_OUTPUT); + content_key_count_++; + license_keys_.SetFromLicense(license_); + } + + virtual void AddConstraint( + std::vector& constraints, uint32_t min_res, + uint32_t max_res, bool set_hdcp = false, + KeyContainer::OutputProtection::HDCP hdcp = + KeyContainer::OutputProtection::HDCP_NONE) { + VideoResolutionConstraint constraint; + constraint.set_min_resolution_pixels(min_res); + constraint.set_max_resolution_pixels(max_res); + if (set_hdcp) { + constraint.mutable_required_protection()->set_hdcp(hdcp); + } + constraints.push_back(constraint); + } + + virtual void StageConstraintKeys() { + content_key_count_ = 0; + + std::vector constraints; + + AddConstraint(constraints, key_lo_res_min, key_lo_res_max); + + AddContentKey(ck_NO_HDCP_lo_res, true, KeyContainer::SW_SECURE_CRYPTO, true, + KeyContainer::OutputProtection::HDCP_NONE, true, + &constraints); + content_key_count_++; + + constraints.clear(); + AddConstraint(constraints, key_hi_res_min, key_hi_res_max); + + AddContentKey(ck_HDCP_NO_OUTPUT_hi_res, true, + KeyContainer::SW_SECURE_CRYPTO, true, + KeyContainer::OutputProtection::HDCP_NO_DIGITAL_OUTPUT, true, + &constraints); + content_key_count_++; + + constraints.clear(); + AddConstraint(constraints, key_top_res_min, key_top_res_max); + + AddContentKey(ck_HDCP_V2_1_max_res, true, KeyContainer::SW_SECURE_CRYPTO, + true, KeyContainer::OutputProtection::HDCP_V2_1, true, + &constraints); + content_key_count_++; + + constraints.clear(); + AddConstraint(constraints, key_lo_res_min, key_lo_res_max); + AddConstraint(constraints, key_hi_res_min, key_hi_res_max, true, + KeyContainer::OutputProtection::HDCP_NO_DIGITAL_OUTPUT); + + AddContentKey(ck_NO_HDCP_dual_res, true, KeyContainer::HW_SECURE_ALL, true, + KeyContainer::OutputProtection::HDCP_NONE, true, + &constraints); + content_key_count_++; + + license_keys_.SetFromLicense(license_); + } + + virtual void ExpectKeyStatusesEqual(KeyStatusMap& key_status_map, + KeyStatus expected_status) { + for (KeyStatusMap::iterator it = key_status_map.begin(); + it != key_status_map.end(); ++it) { + EXPECT_TRUE(it->second == expected_status); + } + } + + virtual void ExpectKeyStatusEqual(KeyStatusMap& key_status_map, + const KeyId& key_id, + KeyStatus expected_status) { + for (KeyStatusMap::iterator it = key_status_map.begin(); + it != key_status_map.end(); ++it) { + if (key_id == it->first) { + EXPECT_TRUE(it->second == expected_status); + } + } + } + + size_t content_key_count_; + LicenseKeys license_keys_; + License license_; +}; + +TEST_F(LicenseKeysTest, Empty) { EXPECT_TRUE(license_keys_.Empty()); } + +TEST_F(LicenseKeysTest, NotEmpty) { + const KeyId c_key = "content_key"; + AddContentKey(c_key); + license_keys_.SetFromLicense(license_); + EXPECT_FALSE(license_keys_.Empty()); +} + +TEST_F(LicenseKeysTest, BadKeyId) { + const KeyId c_key = "content_key"; + const KeyId os_key = "op_sess_key"; + const KeyId unk_key = "unknown_key"; + KeyAllowedUsage allowed_usage; + AddContentKey(c_key); + AddOperatorSessionKey(os_key); + license_keys_.SetFromLicense(license_); + EXPECT_FALSE(license_keys_.IsContentKey(unk_key)); + EXPECT_FALSE(license_keys_.CanDecryptContent(unk_key)); + EXPECT_TRUE(license_keys_.MeetsConstraints(unk_key)); + EXPECT_FALSE(license_keys_.GetAllowedUsage(unk_key, &allowed_usage)); +} + +TEST_F(LicenseKeysTest, SigningKey) { + const KeyId c_key = "content_key"; + const KeyId os_key = "op_sess_key"; + const KeyId sign_key = "signing_key"; + KeyAllowedUsage allowed_usage; + AddSigningKey(sign_key); + AddContentKey(c_key); + AddOperatorSessionKey(os_key); + license_keys_.SetFromLicense(license_); + EXPECT_FALSE(license_keys_.IsContentKey(sign_key)); + EXPECT_FALSE(license_keys_.CanDecryptContent(sign_key)); + EXPECT_TRUE(license_keys_.MeetsConstraints(sign_key)); + EXPECT_FALSE(license_keys_.GetAllowedUsage(sign_key, &allowed_usage)); +} + +TEST_F(LicenseKeysTest, ContentKey) { + const KeyId c_key = "content_key"; + AddContentKey(c_key); + EXPECT_FALSE(license_keys_.IsContentKey(c_key)); + + license_keys_.SetFromLicense(license_); + EXPECT_TRUE(license_keys_.IsContentKey(c_key)); +} + +TEST_F(LicenseKeysTest, EntitlementKey) { + const KeyId e_key = "entitlement_key"; + const KeyId c_key = "content_key"; + AddEntitlementKey(e_key); + EXPECT_FALSE(license_keys_.IsContentKey(e_key)); + + license_keys_.SetFromLicense(license_); + // TODO(juce, rfrias): For simplicity entitlement keys are indicated as + // content keys. It doesn't break anything, but CanDecryptContent returns true + // for and entitlement key id. + EXPECT_TRUE(license_keys_.IsContentKey(e_key)); + + std::vector entitled_keys(1); + entitled_keys[0].set_entitlement_key_id(e_key); + entitled_keys[0].set_key_id(c_key); + EXPECT_FALSE(license_keys_.IsContentKey(c_key)); + license_keys_.SetEntitledKeys(entitled_keys); + EXPECT_TRUE(license_keys_.IsContentKey(c_key)); +} + +TEST_F(LicenseKeysTest, OperatorSessionKey) { + const KeyId os_key = "op_sess_key"; + EXPECT_FALSE(license_keys_.IsContentKey(os_key)); + AddOperatorSessionKey(os_key); + + license_keys_.SetFromLicense(license_); + EXPECT_FALSE(license_keys_.IsContentKey(os_key)); +} + +TEST_F(LicenseKeysTest, CanDecrypt) { + const KeyId os_key = "op_sess_key"; + const KeyId c_key = "content_key"; + const KeyId e_key = "entitlement_key"; + EXPECT_FALSE(license_keys_.CanDecryptContent(c_key)); + EXPECT_FALSE(license_keys_.CanDecryptContent(os_key)); + EXPECT_FALSE(license_keys_.CanDecryptContent(e_key)); + AddOperatorSessionKey(os_key); + AddContentKey(c_key); + AddEntitlementKey(e_key); + license_keys_.SetFromLicense(license_); + EXPECT_FALSE(license_keys_.CanDecryptContent(c_key)); + EXPECT_FALSE(license_keys_.CanDecryptContent(os_key)); + EXPECT_FALSE(license_keys_.CanDecryptContent(e_key)); + bool new_usable_keys = false; + bool any_change = false; + any_change = + license_keys_.ApplyStatusChange(kKeyStatusUsable, &new_usable_keys); + EXPECT_TRUE(any_change); + EXPECT_TRUE(new_usable_keys); + EXPECT_TRUE(license_keys_.CanDecryptContent(c_key)); + EXPECT_FALSE(license_keys_.CanDecryptContent(os_key)); + + any_change = + license_keys_.ApplyStatusChange(kKeyStatusExpired, &new_usable_keys); + EXPECT_TRUE(any_change); + EXPECT_FALSE(new_usable_keys); + EXPECT_FALSE(license_keys_.CanDecryptContent(c_key)); + EXPECT_FALSE(license_keys_.CanDecryptContent(os_key)); + EXPECT_FALSE(license_keys_.CanDecryptContent(e_key)); +} + +TEST_F(LicenseKeysTest, AllowedUsageNull) { + const KeyId os_key = "op_sess_key"; + const KeyId c_key = "content_key"; + const KeyId sign_key = "signing_key"; + const KeyId e_key = "entitlement_key"; + AddOperatorSessionKey(os_key); + AddContentKey(c_key); + AddSigningKey(sign_key); + AddEntitlementKey(e_key); + license_keys_.SetFromLicense(license_); + KeyAllowedUsage usage_1; + EXPECT_FALSE(license_keys_.GetAllowedUsage(sign_key, &usage_1)); + + KeyAllowedUsage usage_2; + EXPECT_TRUE(license_keys_.GetAllowedUsage(c_key, &usage_2)); + ExpectAllowedUsageContent(usage_2, kContentClearTrue, kContentSecureTrue, + kKeySecurityLevelUnset); + + KeyAllowedUsage usage_3; + EXPECT_TRUE(license_keys_.GetAllowedUsage(os_key, &usage_3)); + ExpectAllowedUsageContent(usage_3, kContentClearFalse, kContentSecureFalse, + kKeySecurityLevelUnset); + + KeyAllowedUsage usage_4; + EXPECT_TRUE(license_keys_.GetAllowedUsage(os_key, &usage_4)); + ExpectAllowedUsageContent(usage_4, kContentClearFalse, kContentSecureFalse, + kKeySecurityLevelUnset); +} + +TEST_F(LicenseKeysTest, AllowedUsageContent) { + StageContentKeys(); + KeyAllowedUsage u_sw_crypto; + EXPECT_TRUE(license_keys_.GetAllowedUsage(ck_sw_crypto, &u_sw_crypto)); + ExpectAllowedUsageContent(u_sw_crypto, kContentSecureTrue, kContentClearTrue, + kSoftwareSecureCrypto); + + KeyAllowedUsage u_sw_decode; + EXPECT_TRUE(license_keys_.GetAllowedUsage(ck_sw_decode, &u_sw_decode)); + ExpectAllowedUsageContent(u_sw_decode, kContentSecureTrue, kContentClearTrue, + kSoftwareSecureDecode); + + KeyAllowedUsage u_hw_crypto; + EXPECT_TRUE(license_keys_.GetAllowedUsage(ck_hw_crypto, &u_hw_crypto)); + ExpectAllowedUsageContent(u_hw_crypto, kContentSecureTrue, kContentClearTrue, + kHardwareSecureCrypto); + + KeyAllowedUsage u_hw_decode; + EXPECT_TRUE(license_keys_.GetAllowedUsage(ck_hw_decode, &u_hw_decode)); + ExpectAllowedUsageContent(u_hw_decode, kContentSecureTrue, kContentClearFalse, + kHardwareSecureDecode); + + KeyAllowedUsage u_hw_secure; + EXPECT_TRUE(license_keys_.GetAllowedUsage(ck_hw_secure, &u_hw_secure)); + ExpectAllowedUsageContent(u_hw_secure, kContentSecureTrue, kContentClearFalse, + kHardwareSecureAll); +} + +TEST_F(LicenseKeysTest, AllowedUsageOperatorSession) { + StageOperatorSessionKeys(); + KeyAllowedUsage u_encrypt; + EXPECT_TRUE(license_keys_.GetAllowedUsage(osk_encrypt, &u_encrypt)); + ExpectAllowedUsageOperator(u_encrypt, kEncryptTrue, kDecryptFalse, kSignFalse, + kVerifyFalse); + + KeyAllowedUsage u_decrypt; + EXPECT_TRUE(license_keys_.GetAllowedUsage(osk_decrypt, &u_decrypt)); + ExpectAllowedUsageOperator(u_decrypt, kEncryptFalse, kDecryptTrue, kSignFalse, + kVerifyFalse); + + KeyAllowedUsage u_sign; + EXPECT_TRUE(license_keys_.GetAllowedUsage(osk_sign, &u_sign)); + ExpectAllowedUsageOperator(u_sign, kEncryptFalse, kDecryptFalse, kSignTrue, + kVerifyFalse); + + KeyAllowedUsage u_verify; + EXPECT_TRUE(license_keys_.GetAllowedUsage(osk_verify, &u_verify)); + ExpectAllowedUsageOperator(u_verify, kEncryptFalse, kDecryptFalse, kSignFalse, + kVerifyTrue); + + KeyAllowedUsage u_encrypt_decrypt; + EXPECT_TRUE( + license_keys_.GetAllowedUsage(osk_encrypt_decrypt, &u_encrypt_decrypt)); + ExpectAllowedUsageOperator(u_encrypt_decrypt, kEncryptTrue, kDecryptTrue, + kSignFalse, kVerifyFalse); + + KeyAllowedUsage u_sign_verify; + EXPECT_TRUE(license_keys_.GetAllowedUsage(osk_sign_verify, &u_sign_verify)); + ExpectAllowedUsageOperator(u_sign_verify, kEncryptFalse, kDecryptFalse, + kSignTrue, kVerifyTrue); + + KeyAllowedUsage u_all; + EXPECT_TRUE(license_keys_.GetAllowedUsage(osk_all, &u_all)); + ExpectAllowedUsageOperator(u_all, kEncryptTrue, kDecryptTrue, kSignTrue, + kVerifyTrue); +} + +TEST_F(LicenseKeysTest, ExtractKeyStatuses) { + KeyStatusMap key_status_map; + StageOperatorSessionKeys(); + license_keys_.ExtractKeyStatuses(&key_status_map); + EXPECT_EQ(0u, key_status_map.size()); + StageContentKeys(); + license_keys_.ExtractKeyStatuses(&key_status_map); + EXPECT_EQ(content_key_count_, key_status_map.size()); + ExpectKeyStatusesEqual(key_status_map, kKeyStatusInternalError); +} + +TEST_F(LicenseKeysTest, KeyStatusChanges) { + bool new_usable_keys = false; + bool any_change = false; + KeyStatusMap key_status_map; + StageOperatorSessionKeys(); + StageContentKeys(); + + license_keys_.ExtractKeyStatuses(&key_status_map); + EXPECT_EQ(content_key_count_, key_status_map.size()); + ExpectKeyStatusesEqual(key_status_map, kKeyStatusInternalError); + + // change to pending + any_change = + license_keys_.ApplyStatusChange(kKeyStatusPending, &new_usable_keys); + EXPECT_TRUE(any_change); + EXPECT_FALSE(new_usable_keys); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_sw_crypto)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_hw_secure)); + + license_keys_.ExtractKeyStatuses(&key_status_map); + EXPECT_EQ(content_key_count_, key_status_map.size()); + ExpectKeyStatusesEqual(key_status_map, kKeyStatusPending); + + // change to pending (again) + any_change = + license_keys_.ApplyStatusChange(kKeyStatusPending, &new_usable_keys); + EXPECT_FALSE(any_change); + EXPECT_FALSE(new_usable_keys); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_sw_crypto)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_hw_secure)); + + license_keys_.ExtractKeyStatuses(&key_status_map); + EXPECT_EQ(content_key_count_, key_status_map.size()); + ExpectKeyStatusesEqual(key_status_map, kKeyStatusPending); + + // change to usable + any_change = + license_keys_.ApplyStatusChange(kKeyStatusUsable, &new_usable_keys); + EXPECT_TRUE(any_change); + EXPECT_TRUE(new_usable_keys); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure)); + + license_keys_.ExtractKeyStatuses(&key_status_map); + EXPECT_EQ(content_key_count_, key_status_map.size()); + ExpectKeyStatusesEqual(key_status_map, kKeyStatusUsable); + + // change to usable (again) + any_change = + license_keys_.ApplyStatusChange(kKeyStatusUsable, &new_usable_keys); + EXPECT_FALSE(any_change); + EXPECT_FALSE(new_usable_keys); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure)); + + license_keys_.ExtractKeyStatuses(&key_status_map); + EXPECT_EQ(content_key_count_, key_status_map.size()); + ExpectKeyStatusesEqual(key_status_map, kKeyStatusUsable); + + // change to expired + any_change = + license_keys_.ApplyStatusChange(kKeyStatusExpired, &new_usable_keys); + EXPECT_TRUE(any_change); + EXPECT_FALSE(new_usable_keys); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_sw_crypto)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_hw_secure)); + + license_keys_.ExtractKeyStatuses(&key_status_map); + EXPECT_EQ(content_key_count_, key_status_map.size()); + ExpectKeyStatusesEqual(key_status_map, kKeyStatusExpired); +} + +TEST_F(LicenseKeysTest, HdcpChanges) { + bool new_usable_keys = false; + bool any_change = false; + KeyStatusMap key_status_map; + StageHdcpKeys(); + + any_change = + license_keys_.ApplyStatusChange(kKeyStatusUsable, &new_usable_keys); + EXPECT_TRUE(any_change); + EXPECT_TRUE(new_usable_keys); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto_NO_HDCP)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure_NO_HDCP)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_V2_1)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_V2_1)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_NO_OUTPUT)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_NO_OUTPUT)); + + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_sw_crypto_NO_HDCP)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_hw_secure_NO_HDCP)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_V2_1)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_V2_1)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_NO_OUTPUT)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_NO_OUTPUT)); + + license_keys_.ApplyConstraints(100, HDCP_NONE); + any_change = + license_keys_.ApplyStatusChange(kKeyStatusUsable, &new_usable_keys); + + EXPECT_TRUE(any_change); + EXPECT_FALSE(new_usable_keys); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto_NO_HDCP)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure_NO_HDCP)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_V2_1)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_V2_1)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_NO_OUTPUT)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_NO_OUTPUT)); + + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_sw_crypto_NO_HDCP)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_hw_secure_NO_HDCP)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_V2_1)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_V2_1)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_NO_OUTPUT)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_NO_OUTPUT)); + + license_keys_.ExtractKeyStatuses(&key_status_map); + ExpectKeyStatusEqual(key_status_map, ck_hw_secure_NO_HDCP, kKeyStatusUsable); + ExpectKeyStatusEqual(key_status_map, ck_hw_secure_HDCP_V2_1, + kKeyStatusOutputNotAllowed); + + license_keys_.ApplyConstraints(100, HDCP_V1); + any_change = + license_keys_.ApplyStatusChange(kKeyStatusUsable, &new_usable_keys); + + EXPECT_FALSE(any_change); + EXPECT_FALSE(new_usable_keys); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto_NO_HDCP)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure_NO_HDCP)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_V2_1)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_V2_1)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_NO_OUTPUT)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_NO_OUTPUT)); + + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_sw_crypto_NO_HDCP)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_hw_secure_NO_HDCP)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_V2_1)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_V2_1)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_NO_OUTPUT)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_NO_OUTPUT)); + + license_keys_.ExtractKeyStatuses(&key_status_map); + ExpectKeyStatusEqual(key_status_map, ck_sw_crypto_NO_HDCP, kKeyStatusUsable); + ExpectKeyStatusEqual(key_status_map, ck_sw_crypto_HDCP_V2_1, + kKeyStatusOutputNotAllowed); + + license_keys_.ApplyConstraints(100, HDCP_V2_2); + any_change = + license_keys_.ApplyStatusChange(kKeyStatusUsable, &new_usable_keys); + + EXPECT_TRUE(any_change); + EXPECT_TRUE(new_usable_keys); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto_NO_HDCP)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure_NO_HDCP)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_V2_1)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_V2_1)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_NO_OUTPUT)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_NO_OUTPUT)); + + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_sw_crypto_NO_HDCP)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_hw_secure_NO_HDCP)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_V2_1)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_V2_1)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_NO_OUTPUT)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_NO_OUTPUT)); + + license_keys_.ExtractKeyStatuses(&key_status_map); + ExpectKeyStatusEqual(key_status_map, ck_sw_crypto_HDCP_V2_1, + kKeyStatusUsable); + ExpectKeyStatusEqual(key_status_map, ck_sw_crypto_HDCP_NO_OUTPUT, + kKeyStatusOutputNotAllowed); + + license_keys_.ApplyConstraints(100, HDCP_NO_DIGITAL_OUTPUT); + any_change = + license_keys_.ApplyStatusChange(kKeyStatusUsable, &new_usable_keys); + + EXPECT_TRUE(any_change); + EXPECT_TRUE(new_usable_keys); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto_NO_HDCP)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure_NO_HDCP)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_V2_1)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_V2_1)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_NO_OUTPUT)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_NO_OUTPUT)); + + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_sw_crypto_NO_HDCP)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_hw_secure_NO_HDCP)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_V2_1)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_V2_1)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_NO_OUTPUT)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_NO_OUTPUT)); + + license_keys_.ExtractKeyStatuses(&key_status_map); + ExpectKeyStatusEqual(key_status_map, ck_hw_secure_HDCP_V2_1, + kKeyStatusUsable); + ExpectKeyStatusEqual(key_status_map, ck_hw_secure_HDCP_NO_OUTPUT, + kKeyStatusUsable); + + license_keys_.ApplyConstraints(100, HDCP_NONE); + any_change = + license_keys_.ApplyStatusChange(kKeyStatusUsable, &new_usable_keys); + + EXPECT_TRUE(any_change); + EXPECT_FALSE(new_usable_keys); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto_NO_HDCP)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure_NO_HDCP)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_V2_1)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_V2_1)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_NO_OUTPUT)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_NO_OUTPUT)); + + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_sw_crypto_NO_HDCP)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_hw_secure_NO_HDCP)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_V2_1)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_V2_1)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_NO_OUTPUT)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_NO_OUTPUT)); + + license_keys_.ExtractKeyStatuses(&key_status_map); + ExpectKeyStatusEqual(key_status_map, ck_hw_secure_NO_HDCP, kKeyStatusUsable); + ExpectKeyStatusEqual(key_status_map, ck_hw_secure_HDCP_NO_OUTPUT, + kKeyStatusOutputNotAllowed); +} + +TEST_F(LicenseKeysTest, ConstraintChanges) { + bool new_usable_keys = false; + bool any_change = false; + KeyStatusMap key_status_map; + StageConstraintKeys(); + + // No constraints set by device + any_change = + license_keys_.ApplyStatusChange(kKeyStatusUsable, &new_usable_keys); + EXPECT_TRUE(any_change); + EXPECT_TRUE(new_usable_keys); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_NO_HDCP_lo_res)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_HDCP_NO_OUTPUT_hi_res)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_HDCP_V2_1_max_res)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_NO_HDCP_dual_res)); + + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_NO_HDCP_lo_res)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_HDCP_NO_OUTPUT_hi_res)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_HDCP_V2_1_max_res)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_NO_HDCP_dual_res)); + + // Low-res device, no HDCP support + license_keys_.ApplyConstraints(dev_lo_res, HDCP_NONE); + any_change = + license_keys_.ApplyStatusChange(kKeyStatusUsable, &new_usable_keys); + + EXPECT_TRUE(any_change); + EXPECT_FALSE(new_usable_keys); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_NO_HDCP_lo_res)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_HDCP_NO_OUTPUT_hi_res)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_HDCP_V2_1_max_res)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_NO_HDCP_dual_res)); + + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_NO_HDCP_lo_res)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_HDCP_NO_OUTPUT_hi_res)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_HDCP_V2_1_max_res)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_NO_HDCP_dual_res)); + + license_keys_.ExtractKeyStatuses(&key_status_map); + ExpectKeyStatusEqual(key_status_map, ck_hw_secure_NO_HDCP, kKeyStatusUsable); + ExpectKeyStatusEqual(key_status_map, ck_hw_secure_HDCP_V2_1, + kKeyStatusOutputNotAllowed); + + // Hi-res device, HDCP_V1 support + license_keys_.ApplyConstraints(dev_hi_res, HDCP_V1); + any_change = + license_keys_.ApplyStatusChange(kKeyStatusUsable, &new_usable_keys); + + EXPECT_TRUE(any_change); + EXPECT_TRUE(new_usable_keys); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_NO_HDCP_lo_res)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_HDCP_NO_OUTPUT_hi_res)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_HDCP_V2_1_max_res)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_NO_HDCP_dual_res)); + + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_NO_HDCP_lo_res)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_HDCP_NO_OUTPUT_hi_res)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_HDCP_V2_1_max_res)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_NO_HDCP_dual_res)); + + license_keys_.ExtractKeyStatuses(&key_status_map); + ExpectKeyStatusEqual(key_status_map, ck_sw_crypto_NO_HDCP, kKeyStatusUsable); + ExpectKeyStatusEqual(key_status_map, ck_sw_crypto_HDCP_V2_1, + kKeyStatusOutputNotAllowed); + + // Lo-res device, HDCP V2.2 support + license_keys_.ApplyConstraints(dev_lo_res, HDCP_V2_2); + any_change = + license_keys_.ApplyStatusChange(kKeyStatusUsable, &new_usable_keys); + + EXPECT_TRUE(any_change); + EXPECT_TRUE(new_usable_keys); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_NO_HDCP_lo_res)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_HDCP_NO_OUTPUT_hi_res)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_HDCP_V2_1_max_res)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_NO_HDCP_dual_res)); + + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_NO_HDCP_lo_res)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_HDCP_NO_OUTPUT_hi_res)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_HDCP_V2_1_max_res)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_NO_HDCP_dual_res)); + + license_keys_.ExtractKeyStatuses(&key_status_map); + ExpectKeyStatusEqual(key_status_map, ck_sw_crypto_HDCP_V2_1, + kKeyStatusUsable); + ExpectKeyStatusEqual(key_status_map, ck_sw_crypto_HDCP_NO_OUTPUT, + kKeyStatusOutputNotAllowed); + + // Hi-res device, Maximal HDCP support + license_keys_.ApplyConstraints(dev_hi_res, HDCP_NO_DIGITAL_OUTPUT); + any_change = + license_keys_.ApplyStatusChange(kKeyStatusUsable, &new_usable_keys); + + EXPECT_TRUE(any_change); + EXPECT_TRUE(new_usable_keys); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_NO_HDCP_lo_res)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_HDCP_NO_OUTPUT_hi_res)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_HDCP_V2_1_max_res)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_NO_HDCP_dual_res)); + + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_NO_HDCP_lo_res)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_HDCP_NO_OUTPUT_hi_res)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_HDCP_V2_1_max_res)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_NO_HDCP_dual_res)); + + license_keys_.ExtractKeyStatuses(&key_status_map); + ExpectKeyStatusEqual(key_status_map, ck_hw_secure_HDCP_V2_1, + kKeyStatusUsable); + ExpectKeyStatusEqual(key_status_map, ck_hw_secure_HDCP_NO_OUTPUT, + kKeyStatusUsable); + + // Lo-res device, no HDCP support + license_keys_.ApplyConstraints(dev_lo_res, HDCP_NONE); + any_change = + license_keys_.ApplyStatusChange(kKeyStatusUsable, &new_usable_keys); + + EXPECT_TRUE(any_change); + EXPECT_TRUE(new_usable_keys); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_NO_HDCP_lo_res)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_HDCP_NO_OUTPUT_hi_res)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_HDCP_V2_1_max_res)); + EXPECT_TRUE(license_keys_.CanDecryptContent(ck_NO_HDCP_dual_res)); + + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_NO_HDCP_lo_res)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_HDCP_NO_OUTPUT_hi_res)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_HDCP_V2_1_max_res)); + EXPECT_TRUE(license_keys_.MeetsConstraints(ck_NO_HDCP_dual_res)); + + license_keys_.ExtractKeyStatuses(&key_status_map); + ExpectKeyStatusEqual(key_status_map, ck_hw_secure_NO_HDCP, kKeyStatusUsable); + ExpectKeyStatusEqual(key_status_map, ck_hw_secure_HDCP_NO_OUTPUT, + kKeyStatusOutputNotAllowed); + + // Too-high-res -- all keys rejected + license_keys_.ApplyConstraints(dev_top_res, HDCP_NONE); + any_change = + license_keys_.ApplyStatusChange(kKeyStatusUsable, &new_usable_keys); + + EXPECT_TRUE(any_change); + EXPECT_FALSE(new_usable_keys); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_NO_HDCP_lo_res)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_HDCP_NO_OUTPUT_hi_res)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_HDCP_V2_1_max_res)); + EXPECT_FALSE(license_keys_.CanDecryptContent(ck_NO_HDCP_dual_res)); + + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_NO_HDCP_lo_res)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_HDCP_NO_OUTPUT_hi_res)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_HDCP_V2_1_max_res)); + EXPECT_FALSE(license_keys_.MeetsConstraints(ck_NO_HDCP_dual_res)); + + license_keys_.ExtractKeyStatuses(&key_status_map); + ExpectKeyStatusEqual(key_status_map, ck_hw_secure_NO_HDCP, kKeyStatusUsable); + ExpectKeyStatusEqual(key_status_map, ck_hw_secure_HDCP_NO_OUTPUT, + kKeyStatusOutputNotAllowed); +} + +} // namespace wvcas diff --git a/tests/src/mediacas_integration_test.cpp b/tests/src/mediacas_integration_test.cpp new file mode 100644 index 0000000..450dbd7 --- /dev/null +++ b/tests/src/mediacas_integration_test.cpp @@ -0,0 +1,154 @@ +// Dynamically generated header created during build. The Runtest entry point is +// defined in gopkg_carchive.go + +#include "golang/src/gowvcas_carchive.h" +#include "gtest/gtest.h" + +extern "C" int RunTest(GoString); + +constexpr int kIntegrationTestPassed = 0; + +// Invokes a test. Tests are named to allow them to be run individually. This +// may be the best solution. The downside is the test prints PASS for each +// individual test. + +// It is also possible to not address them individually but run +// them all as a batch. If running the tests as a single batch it may be +// possible to pass command line flags to the test to indicate individual tests. +// The downside of this is that it forces a user to pass in two command line +// flags for each test. +int RunNamedTest(const std::string testname) { + GoString go_testname = { + p : testname.data(), + n : static_cast(testname.size()) + }; + return RunTest(go_testname); +} + +TEST(IntegrationTests, TestCasFactoryCreation) { + EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestCasFactoryCreation")); +} + +TEST(IntegrationTests, TestCreateCasPlugin) { + EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestCreateCasPlugin")); +} + +TEST(IntegrationTests, TestCreateCasPluginWithNewCasIds) { + EXPECT_EQ(kIntegrationTestPassed, + RunNamedTest("TestCreateCasPluginWithNewCasIds")); +} + +TEST(IntegrationTests, TestCreateCasPluginWithInvalidCasIds) { + EXPECT_EQ(kIntegrationTestPassed, + RunNamedTest("TestCreateCasPluginWithInvalidCasIds")); +} + +TEST(IntegrationTests, TestCreateCasPluginWithSessionEvent) { + EXPECT_EQ(kIntegrationTestPassed, + RunNamedTest("TestCreateCasPluginWithSessionEvent")); +} + +TEST(IntegrationTests, TestCreateCasPluginWithSessionEventWithNewCasIds) { + EXPECT_EQ(kIntegrationTestPassed, + RunNamedTest("TestCreateCasPluginWithSessionEventWithNewCasIds")); +} + +TEST(IntegrationTests, TestCasPluginEventPassing) { + EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestCasPluginEventPassing")); +} + +TEST(IntegrationTests, TestSessionFailWithoutProvisioning) { + EXPECT_EQ(kIntegrationTestPassed, + RunNamedTest("TestSessionFailWithoutProvisioning")); +} + +TEST(IntegrationTests, TestUniqueIdQuery) { + EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestUniqueIdQuery")); +} + +TEST(IntegrationTests, TestCasPluginProvision) { + EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestCasPluginProvision")); +} + +TEST(IntegrationTests, TestCasPluginEmmRequestWithInitData) { + EXPECT_EQ(kIntegrationTestPassed, + RunNamedTest("TestCasPluginEmmRequestWithInitData")); +} + +TEST(IntegrationTests, TestCasEmmRequestWithPrivateData) { + EXPECT_EQ(kIntegrationTestPassed, + RunNamedTest("TestCasEmmRequestWithPrivateData")); +} + +TEST(IntegrationTests, TestCasWithOfflineEMM) { + EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestCasWithOfflineEMM")); +} + +TEST(IntegrationTests, TestCasCanStoreOfflineEMM) { + EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestCasCanStoreOfflineEMM")); +} + +TEST(IntegrationTests, TestCasCanNotStoreOfflineEMM) { + EXPECT_EQ(kIntegrationTestPassed, + RunNamedTest("TestCasCanNotStoreOfflineEMM")); +} + +TEST(IntegrationTests, TestSession) { + EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestSession")); +} + +TEST(IntegrationTests, TestCasRenewal) { + EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestCasRenewal")); +} + +TEST(IntegrationTests, TestRestoreRenewalAndExpiredLicense) { + EXPECT_EQ(kIntegrationTestPassed, + RunNamedTest("TestRestoreRenewalAndExpiredLicense")); +} + +TEST(IntegrationTests, TestLicenseExpiration) { + EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestLicenseExpiration")); +} + +TEST(IntegrationTests, TestParentalControl) { + EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestParentalControl")); +} + +TEST(IntegrationTests, TestRemoveLicense) { + EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestRemoveLicense")); +} + +TEST(IntegrationTests, TestSessionEventPassing) { + EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestSessionEventPassing")); +} + +TEST(IntegrationTests, TestProcessEcmV3) { + EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestProcessEcmV3")); +} + +TEST(IntegrationTests, TestGroupLicense) { + EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestGroupLicense")); +} + +TEST(IntegrationTests, TestMultiContentLicense) { + EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestMultiContentLicense")); +} + +TEST(IntegrationTests, TestAssignGroupLicense) { + EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestAssignGroupLicense")); +} + +TEST(IntegrationTests, TestLicenseRequestWithEntitlementPeriodIndex) { + EXPECT_EQ(kIntegrationTestPassed, + RunNamedTest("TestLicenseRequestWithEntitlementPeriodIndex")); +} + +TEST(IntegrationTests, TestOfflineLicenseWithEntitlementPeriodIndex) { + EXPECT_EQ(kIntegrationTestPassed, + RunNamedTest("TestOfflineLicenseWithEntitlementPeriodIndex")); +} + +TEST(IntegrationTests, TestNewLicenseRequestWithOutdatedOfflineLicense) { + EXPECT_EQ(kIntegrationTestPassed, + RunNamedTest("TestNewLicenseRequestWithOutdatedOfflineLicense")); +} diff --git a/tests/src/mock_crypto_session.h b/tests/src/mock_crypto_session.h new file mode 100644 index 0000000..f93d206 --- /dev/null +++ b/tests/src/mock_crypto_session.h @@ -0,0 +1,93 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#ifndef MOCK_CRYPTO_SESSION_H +#define MOCK_CRYPTO_SESSION_H + +#include +#include + +#include "crypto_session.h" + +class MockCryptoSession : public wvcas::CryptoSession { + public: + MockCryptoSession() {} + virtual ~MockCryptoSession() {} + + MOCK_METHOD0(initialize, wvcas::CasStatus()); + MOCK_METHOD0(reset, wvcas::CasStatus()); + MOCK_METHOD0(close, wvcas::CasStatus()); + MOCK_METHOD0(provisioning_method, wvcas::CasProvisioningMethod()); + MOCK_METHOD2(GetKeyData, + wvcas::CasStatus(uint8_t* keyData, size_t* keyDataLength)); + MOCK_METHOD0(supported_certificates, wvcas::SupportedCertificates()); + MOCK_METHOD1(APIVersion, wvcas::CasStatus(uint32_t* api_version)); + MOCK_METHOD1(GenerateNonce, wvcas::CasStatus(uint32_t* nonce)); + MOCK_METHOD4(GenerateDerivedKeys, + wvcas::CasStatus(const uint8_t* mac_key_context, + uint32_t mac_key_context_length, + const uint8_t* enc_key_context, + uint32_t enc_key_context_length)); + MOCK_METHOD5(PrepareAndSignLicenseRequest, + wvcas::CasStatus(const std::string& message, + std::string* core_message, + std::string* signature, bool&, + OEMCrypto_SignatureHashAlgorithm&) + ); + MOCK_METHOD3(PrepareAndSignRenewalRequest, + wvcas::CasStatus(const std::string& message, + std::string* core_message, + std::string* signature)); + MOCK_METHOD5(PrepareAndSignProvisioningRequest, + wvcas::CasStatus(const std::string& message, + std::string* core_message, + std::string* signature, bool&, + OEMCrypto_SignatureHashAlgorithm&) + ); + MOCK_METHOD4(LoadProvisioning, + wvcas::CasStatus(const std::string& signed_message, + const std::string& core_message, + const std::string& signature, + std::string* wrapped_private_key)); + MOCK_METHOD2(GetOEMPublicCertificate, + wvcas::CasStatus(uint8_t* public_cert, + size_t* public_cert_length)); + MOCK_METHOD5(GenerateRSASignature, + wvcas::CasStatus(const uint8_t* message, size_t message_length, + uint8_t* signature, size_t* signature_length, + RSA_Padding_Scheme padding_scheme)); + MOCK_METHOD6(DeriveKeysFromSessionKey, + wvcas::CasStatus(const uint8_t* enc_session_key, + size_t enc_session_key_length, + const uint8_t* mac_key_context, + size_t mac_key_context_length, + const uint8_t* enc_key_context, + size_t enc_key_context_length)); + MOCK_METHOD3(LoadLicense, wvcas::CasStatus(const std::string& signed_message, + const std::string& core_message, + const std::string& signature)); + MOCK_METHOD3(LoadRenewal, wvcas::CasStatus(const std::string& signed_message, + const std::string& core_message, + const std::string& signature)); + MOCK_METHOD3(LoadCasECMKeys, wvcas::CasStatus(OEMCrypto_SESSION session, + const wvcas::KeySlot* even_key, + const wvcas::KeySlot* odd_key)); + MOCK_METHOD2(GetHdcpCapabilities, bool(wvcas::HdcpCapability* current, + wvcas::HdcpCapability* max)); + MOCK_METHOD1(GetDeviceID, wvcas::CasStatus(std::string* buffer)); + MOCK_METHOD2(LoadDeviceRSAKey, + wvcas::CasStatus(const uint8_t* wrapped_rsa_key, + size_t wrapped_rsa_key_length)); + MOCK_METHOD1(CreateEntitledKeySession, + wvcas::CasStatus(uint32_t* entitled_key_session_id)); + MOCK_METHOD1(RemoveEntitledKeySession, + wvcas::CasStatus(uint32_t entitled_key_session_id)); + MOCK_METHOD(wvcas::CasStatus, ReassociateEntitledKeySession, + (uint32_t entitled_key_session_id)); + MOCK_METHOD(wvcas::CasStatus, GetOEMKeyToken, + (OEMCrypto_SESSION entitled_key_session_id, + std::vector& token)); +}; + +#endif // MOCK_CRYPTO_SESSION_H diff --git a/tests/src/mock_ecm_parser.h b/tests/src/mock_ecm_parser.h new file mode 100644 index 0000000..a4c5b87 --- /dev/null +++ b/tests/src/mock_ecm_parser.h @@ -0,0 +1,45 @@ +// Copyright 2021 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#ifndef MOCK_ECM_PARSER_H +#define MOCK_ECM_PARSER_H + +#include +#include + +#include "ecm_parser.h" + +class MockEcmParser : public wvcas::EcmParser { + public: + MOCK_METHOD(uint8_t, version, (), (const, override)); + MOCK_METHOD(uint8_t, age_restriction, (), (const, override)); + MOCK_METHOD(wvcas::CryptoMode, crypto_mode, (), (const, override)); + MOCK_METHOD(bool, rotation_enabled, (), (const, override)); + MOCK_METHOD(size_t, content_iv_size, (), (const, override)); + MOCK_METHOD(std::vector, entitlement_key_id, (wvcas::KeySlotId id), + (const, override)); + MOCK_METHOD(std::vector, content_key_id, (wvcas::KeySlotId id), + (const, override)); + MOCK_METHOD(std::vector, wrapped_key_data, (wvcas::KeySlotId id), + (const, override)); + MOCK_METHOD(std::vector, wrapped_key_iv, (wvcas::KeySlotId id), + (const, override)); + MOCK_METHOD(std::vector, content_iv, (wvcas::KeySlotId id), + (const, override)); + MOCK_METHOD(bool, set_group_id, (const std::string& group_id), (override)); + MOCK_METHOD(bool, has_fingerprinting, (), (const, override)); + MOCK_METHOD(video_widevine::Fingerprinting, fingerprinting, (), + (const, override)); + MOCK_METHOD(bool, has_service_blocking, (), (const, override)); + MOCK_METHOD(video_widevine::ServiceBlocking, service_blocking, (), + (const, override)); + MOCK_METHOD(std::string, ecm_serialized_payload, (), (const, override)); + MOCK_METHOD(std::string, signature, (), (const, override)); + MOCK_METHOD(bool, is_entitlement_rotation_enabled, (), (const, override)); + MOCK_METHOD(uint32_t, entitlement_period_index, (), (const, override)); + MOCK_METHOD(uint32_t, entitlement_rotation_window_left, (), + (const, override)); +}; + +#endif // MOCK_ECM_PARSER_H \ No newline at end of file diff --git a/tests/src/mock_event_listener.h b/tests/src/mock_event_listener.h new file mode 100644 index 0000000..688c819 --- /dev/null +++ b/tests/src/mock_event_listener.h @@ -0,0 +1,47 @@ +// Copyright 2021 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#ifndef MOCK_EVENT_LISTENER_H +#define MOCK_EVENT_LISTENER_H + +#include +#include + +#include "cas_types.h" + +class MockEventListener : public wvcas::CasEventListener { + public: + MockEventListener() {} + virtual ~MockEventListener() {} + + MOCK_METHOD(void, OnSessionRenewalNeeded, (), (override)); + MOCK_METHOD(void, OnSessionKeysChange, + (const wvcas::KeyStatusMap& keys_status, bool has_new_usable_key), + (override)); + MOCK_METHOD(void, OnExpirationUpdate, (int64_t new_expiry_time_seconds), + (override)); + MOCK_METHOD(void, OnNewRenewalServerUrl, + (const std::string& renewal_server_url), (override)); + MOCK_METHOD(void, OnLicenseExpiration, (), (override)); + MOCK_METHOD(void, OnAgeRestrictionUpdated, + (const wvcas::WvCasSessionId& sessionId, + uint8_t ecm_age_restriction), + (override)); + MOCK_METHOD(void, OnSessionFingerprintingUpdated, + (const wvcas::WvCasSessionId& sessionId, + const std::vector& fingerprinting), + (override)); + MOCK_METHOD(void, OnSessionServiceBlockingUpdated, + (const wvcas::WvCasSessionId& sessionId, + const std::vector& service_blocking), + (override)); + MOCK_METHOD(void, OnFingerprintingUpdated, + (const std::vector& fingerprinting), (override)); + MOCK_METHOD(void, OnServiceBlockingUpdated, + (const std::vector& service_blocking), (override)); + MOCK_METHOD(void, OnEntitlementPeriodUpdateNeeded, + (const std::string& signed_license_request), (override)); +}; + +#endif // MOCK_EVENT_LISTENER_H \ No newline at end of file diff --git a/tests/src/policy_engine_test.cpp b/tests/src/policy_engine_test.cpp new file mode 100644 index 0000000..9a20aad --- /dev/null +++ b/tests/src/policy_engine_test.cpp @@ -0,0 +1,340 @@ +#include "policy_engine.h" + +#include +#include + +#include "cas_types.h" +#include "cas_util.h" +#include "clock.h" +#include "license_key_status.h" +#include "license_protocol.pb.h" +#include "mock_crypto_session.h" +#include "mock_event_listener.h" + +using ::testing::_; +using ::testing::DoAll; +using ::testing::NotNull; +using ::testing::Return; +using ::testing::Sequence; +using ::testing::SetArgPointee; +using ::testing::StrictMock; +using wvcas::HdcpCapability; +using wvcas::KeyAllowedUsage; +using wvcas::KeyId; +using wvcas::KeyStatus; +using wvcas::KeyStatusMap; +using wvcas::WidevinePsshData_EntitledKey; + +namespace { +const int64_t kDurationUnlimited = 0; +const int64_t kLicenseStartTime = 1413517500; // ~ 01/01/2013 +const int64_t kPlaybackStartTime = kLicenseStartTime + 5; +const int64_t kRentalDuration = 604800; // 7 days +const int64_t kPlaybackDuration = 172800; // 48 hours +const int64_t kLicenseDuration = kRentalDuration + kPlaybackDuration; +const int64_t kLicenseRenewalPeriod = 120; // 2 minutes +const int64_t kLicenseRenewalRetryInterval = 30; // 30 seconds +const int64_t kLicenseRenewalRecoveryDuration = 30; // 30 seconds +const int64_t kLowDuration = 300; // 5 minutes +const int64_t kHighDuration = + std::max(std::max(kRentalDuration, kPlaybackDuration), kLicenseDuration); +const char* kRenewalServerUrl = + "https://test.google.com/license/GetCencLicense"; +const KeyId kKeyId = "357adc89f1673433c36c621f1b5c41ee"; +const KeyId kEntitlementKeyId = "entitlementkeyid"; +const KeyId kAnotherKeyId = "another_key_id"; +const KeyId kSomeRandomKeyId = "some_random_key_id"; +const KeyId kUnknownKeyId = "some_random_unknown_key_id"; +} // namespace + +class MockClock : public wvutil::Clock { + public: + MockClock() {} + virtual ~MockClock() {} + + MOCK_METHOD0(GetCurrentTime, int64_t()); +}; + +class MockLicenseKeys : public wvcas::LicenseKeys { + public: + MockLicenseKeys() {} + virtual ~MockLicenseKeys() {} + + MOCK_METHOD0(Empty, bool()); + MOCK_METHOD1(IsContentKey, bool(const KeyId& key_id)); + MOCK_METHOD1(CanDecryptContent, bool(const KeyId& key_id)); + MOCK_METHOD2(GetAllowedUsage, + bool(const KeyId& key_id, KeyAllowedUsage* allowed_usage)); + MOCK_METHOD2(ApplyStatusChange, + bool(KeyStatus new_status, bool* new_usable_keys)); + MOCK_METHOD1(GetKeyStatus, KeyStatus(const KeyId& key_id)); + MOCK_METHOD1(ExtractKeyStatuses, void(KeyStatusMap* content_keys)); + MOCK_METHOD1(MeetsConstraints, bool(const KeyId& key_id)); + MOCK_METHOD2(ApplyConstraints, + void(uint32_t new_resolution, HdcpCapability new_hdcp_level)); + MOCK_METHOD1(SetFromLicense, void(const video_widevine::License& license)); + MOCK_METHOD1(SetEntitledKeys, + void(const std::vector& keys)); +}; + +class TestablePolicyEngine : public wvcas::PolicyEngine { + std::unique_ptr CreateLicenseKeys() override { + std::unique_ptr > license_keys = + make_unique >(); + license_keys_ = license_keys.get(); + return license_keys; + } + std::unique_ptr CreateClock() override { + std::unique_ptr > clock = + make_unique >(); + clock_ = clock.get(); + return clock; + } + + public: + MockClock* clock_ = nullptr; + MockLicenseKeys* license_keys_ = nullptr; +}; + +class PolicyEngineTest : public ::testing::Test { + public: + PolicyEngineTest() {} + virtual ~PolicyEngineTest() {} + + void SetUp() { + crypto_session_ = std::make_shared >(); + policy_engine_.initialize(crypto_session_, &event_listener_); + + ASSERT_NE(policy_engine_.clock_, nullptr); + ASSERT_NE(policy_engine_.license_keys_, nullptr); + + license_.set_license_start_time(kLicenseStartTime); + video_widevine::LicenseIdentification* id = license_.mutable_id(); + id->set_version(1); + id->set_type(video_widevine::STREAMING); + video_widevine::License::KeyContainer* key = license_.add_key(); + key->set_type(video_widevine::License::KeyContainer::CONTENT); + key->set_id(kKeyId); + video_widevine::License_Policy* policy = license_.mutable_policy(); + policy = license_.mutable_policy(); + policy->set_can_play(true); + policy->set_can_persist(false); + policy->set_can_renew(false); + // This is similar to an OFFLINE policy. + policy->set_rental_duration_seconds(kRentalDuration); + policy->set_playback_duration_seconds(kPlaybackDuration); + policy->set_license_duration_seconds(kLicenseDuration); + policy->set_renewal_recovery_duration_seconds( + kLicenseRenewalRecoveryDuration); + policy->set_renewal_delay_seconds(0); + policy->set_renewal_retry_interval_seconds(kLicenseRenewalRetryInterval); + policy->set_renew_with_usage(false); + } + + MockEventListener event_listener_; + std::shared_ptr > crypto_session_; + video_widevine::License license_; + TestablePolicyEngine policy_engine_; +}; + +TEST_F(PolicyEngineTest, CanDecryptContent) { + EXPECT_CALL(*policy_engine_.license_keys_, IsContentKey(kKeyId)) + .WillOnce(Return(false)) + .WillOnce(Return(true)) + .WillOnce(Return(true)); + EXPECT_CALL(*policy_engine_.license_keys_, CanDecryptContent(kKeyId)) + .WillOnce(Return(false)) + .WillOnce(Return(true)); + + // IsContentKey == false : CanDecryptContent is not called. + EXPECT_FALSE(policy_engine_.CanDecryptContent(kKeyId)); + // IsContentKey == true : CanDecryptContent is false. + EXPECT_FALSE(policy_engine_.CanDecryptContent(kKeyId)); + // IsContentKey == true : CanDecryptContent is true. + EXPECT_TRUE(policy_engine_.CanDecryptContent(kKeyId)); +} + +TEST_F(PolicyEngineTest, SetLicense_NoKey) { + video_widevine::License license; + license.mutable_policy(); + license.set_license_start_time(kLicenseStartTime); + + EXPECT_FALSE(policy_engine_.IsExpired()); + + EXPECT_CALL(*policy_engine_.license_keys_, SetFromLicense(_)); + EXPECT_CALL(*policy_engine_.clock_, GetCurrentTime()) + .WillOnce(Return(kLicenseStartTime + 1)); + EXPECT_CALL(*policy_engine_.license_keys_, + ApplyStatusChange(wvcas::kKeyStatusExpired, _)) + .WillOnce(Return(false)); + EXPECT_CALL(event_listener_, OnLicenseExpiration); + policy_engine_.SetLicense(license); + EXPECT_TRUE(policy_engine_.IsExpired()); +} + +TEST_F(PolicyEngineTest, PlaybackSuccess_OfflineLicense) { + // CryptoSession + EXPECT_CALL(*crypto_session_, GetHdcpCapabilities(NotNull(), NotNull())) + .WillRepeatedly( + DoAll(SetArgPointee<0>(HDCP_NO_DIGITAL_OUTPUT), Return(true))); + EXPECT_CALL(*policy_engine_.clock_, GetCurrentTime()) + .WillOnce(Return(kLicenseStartTime + 1)) + .WillOnce(Return(kPlaybackStartTime)) + .WillOnce(Return(kLicenseStartTime + 10)); + EXPECT_CALL(*policy_engine_.license_keys_, Empty()) + .WillOnce(Return(true)) + .WillRepeatedly(Return(false)); + EXPECT_CALL(*policy_engine_.license_keys_, SetFromLicense(_)); + EXPECT_CALL(*policy_engine_.license_keys_, + ApplyStatusChange(wvcas::kKeyStatusUsable, NotNull())) + .WillOnce(DoAll(SetArgPointee<1>(true), Return(true))) + .WillRepeatedly(DoAll(SetArgPointee<1>(false), Return(false))); + EXPECT_CALL(*policy_engine_.license_keys_, ExtractKeyStatuses(NotNull())); + EXPECT_CALL(event_listener_, OnSessionKeysChange(_, true)); + EXPECT_CALL(event_listener_, + OnExpirationUpdate(kLicenseStartTime + kRentalDuration)); + EXPECT_CALL(event_listener_, + OnExpirationUpdate(kPlaybackStartTime + kPlaybackDuration)); + EXPECT_CALL(*policy_engine_.license_keys_, + ApplyConstraints(_, HDCP_NO_DIGITAL_OUTPUT)) + .Times(2); + + policy_engine_.SetLicense(license_); + policy_engine_.BeginDecryption(); + policy_engine_.OnTimerEvent(); +} + +TEST_F(PolicyEngineTest, PlaybackSuccess_EntitlementLicenseExpiration) { + EXPECT_CALL(*policy_engine_.license_keys_, SetFromLicense(_)); + EXPECT_CALL(*policy_engine_.clock_, GetCurrentTime()) + .WillOnce(Return(kLicenseStartTime + 1)) + .WillOnce(Return(kPlaybackStartTime)) + .WillOnce(Return(kPlaybackStartTime + kPlaybackDuration + 10)); + EXPECT_CALL(*policy_engine_.license_keys_, Empty()) + .WillOnce(Return(true)) + .WillRepeatedly(Return(false)); + EXPECT_CALL(*policy_engine_.license_keys_, + ApplyStatusChange(wvcas::kKeyStatusUsable, NotNull())) + .WillOnce(DoAll(SetArgPointee<1>(true), Return(true))) + .WillRepeatedly(DoAll(SetArgPointee<1>(false), Return(false))); + EXPECT_CALL(*policy_engine_.license_keys_, + ApplyStatusChange(wvcas::kKeyStatusExpired, NotNull())) + .WillOnce(DoAll(SetArgPointee<1>(true), Return(false))); + EXPECT_CALL(*policy_engine_.license_keys_, ExtractKeyStatuses(NotNull())); + EXPECT_CALL(event_listener_, OnSessionKeysChange(_, true)); + EXPECT_CALL(event_listener_, OnLicenseExpiration); + EXPECT_CALL(event_listener_, + OnExpirationUpdate(kLicenseStartTime + kRentalDuration)); + EXPECT_CALL(event_listener_, + OnExpirationUpdate(kPlaybackStartTime + kPlaybackDuration)); + EXPECT_CALL(*policy_engine_.license_keys_, SetEntitledKeys(_)); + + video_widevine::License::KeyContainer* key = license_.mutable_key(0); + key->set_type(video_widevine::License::KeyContainer::ENTITLEMENT); + key->set_id(kEntitlementKeyId); + + EXPECT_FALSE(policy_engine_.IsExpired()); + policy_engine_.SetLicense(license_); + policy_engine_.BeginDecryption(); + policy_engine_.OnTimerEvent(); + + std::vector entitled_keys(1); + entitled_keys[0].set_entitlement_key_id(kEntitlementKeyId); + entitled_keys[0].set_key_id(kKeyId); + policy_engine_.SetEntitledLicenseKeys(entitled_keys); + EXPECT_TRUE(policy_engine_.IsExpired()); +} + +TEST_F(PolicyEngineTest, PlaybackSuccess_StreamingLicense) { + video_widevine::License_Policy* policy = license_.mutable_policy(); + policy->set_license_duration_seconds(kLowDuration); + + EXPECT_CALL(*policy_engine_.license_keys_, SetFromLicense(_)); + EXPECT_CALL(*policy_engine_.clock_, GetCurrentTime()) + .WillOnce(Return(kLicenseStartTime + 1)) + .WillOnce(Return(kPlaybackStartTime)) + .WillOnce(Return(kLicenseStartTime + 10)); + EXPECT_CALL(*policy_engine_.license_keys_, Empty()) + .WillOnce(Return(true)) + .WillRepeatedly(Return(false)); + EXPECT_CALL(*policy_engine_.license_keys_, + ApplyStatusChange(wvcas::kKeyStatusUsable, NotNull())) + .WillOnce(DoAll(SetArgPointee<1>(true), Return(true))) + .WillRepeatedly(DoAll(SetArgPointee<1>(false), Return(false))); + EXPECT_CALL(event_listener_, OnSessionKeysChange(_, true)); + EXPECT_CALL(event_listener_, + OnExpirationUpdate(kLicenseStartTime + kLowDuration)); + EXPECT_CALL(*policy_engine_.license_keys_, ExtractKeyStatuses(NotNull())); + EXPECT_CALL(*crypto_session_, GetHdcpCapabilities(_, _)) + .WillRepeatedly( + DoAll(SetArgPointee<0>(HDCP_NO_DIGITAL_OUTPUT), Return(false))); + EXPECT_CALL(*policy_engine_.license_keys_, ApplyConstraints(_, HDCP_NONE)) + .Times(2); + + policy_engine_.SetLicense(license_); + policy_engine_.BeginDecryption(); + policy_engine_.OnTimerEvent(); +} + +TEST_F(PolicyEngineTest, RenewalEvents) { + video_widevine::License_Policy* policy = license_.mutable_policy(); + policy->set_license_duration_seconds(kLowDuration); + policy->set_can_renew(true); + policy->set_renewal_delay_seconds(kLicenseRenewalPeriod); + ; + policy->set_renewal_retry_interval_seconds(kLicenseRenewalRetryInterval); + + { + Sequence set_license; + EXPECT_CALL(*policy_engine_.license_keys_, SetFromLicense(_)); + EXPECT_CALL(*policy_engine_.clock_, GetCurrentTime()) + .WillOnce(Return(kLicenseStartTime + 1)); + EXPECT_CALL(*policy_engine_.license_keys_, Empty()).WillOnce(Return(true)); + EXPECT_CALL(*policy_engine_.license_keys_, + ApplyStatusChange(wvcas::kKeyStatusUsable, NotNull())) + .WillOnce(DoAll(SetArgPointee<1>(true), Return(true))); + EXPECT_CALL(*policy_engine_.license_keys_, ExtractKeyStatuses(NotNull())); + EXPECT_CALL(event_listener_, + OnExpirationUpdate(kLicenseStartTime + kLowDuration)); + EXPECT_CALL(event_listener_, OnSessionKeysChange(_, true)); + policy_engine_.SetLicense(license_); + } + + { + Sequence begin_decryption; + EXPECT_CALL(*policy_engine_.clock_, GetCurrentTime()) + .WillOnce(Return(kPlaybackStartTime)); + policy_engine_.BeginDecryption(); + } + + EXPECT_CALL(*crypto_session_, GetHdcpCapabilities(_, _)) + .WillRepeatedly( + DoAll(SetArgPointee<0>(HDCP_NO_DIGITAL_OUTPUT), Return(false))); + EXPECT_CALL(*policy_engine_.license_keys_, + ApplyStatusChange(wvcas::kKeyStatusUsable, NotNull())) + .WillRepeatedly(DoAll(SetArgPointee<1>(false), Return(false))); + EXPECT_CALL(*policy_engine_.license_keys_, Empty()) + .WillRepeatedly(Return(false)); + + { + Sequence on_timer; + EXPECT_CALL(*policy_engine_.clock_, GetCurrentTime()) + .WillOnce(Return(kLicenseStartTime + 10)); + EXPECT_CALL(*policy_engine_.license_keys_, ApplyConstraints(_, HDCP_NONE)) + .Times(2); + policy_engine_.OnTimerEvent(); + } +} + +TEST_F(PolicyEngineTest, RenewalUrl) { + EXPECT_CALL(*policy_engine_.license_keys_, SetFromLicense(_)); + EXPECT_CALL(*policy_engine_.clock_, GetCurrentTime()); + EXPECT_CALL(*policy_engine_.license_keys_, ApplyStatusChange(_, NotNull())); + EXPECT_CALL(event_listener_, OnExpirationUpdate(_)); + EXPECT_CALL(event_listener_, + OnNewRenewalServerUrl(::testing::StrEq(kRenewalServerUrl))); + + video_widevine::License_Policy* policy = license_.mutable_policy(); + policy->set_renewal_server_url(kRenewalServerUrl); + policy_engine_.SetLicense(license_); +} diff --git a/tests/src/test_properties.cpp b/tests/src/test_properties.cpp new file mode 100644 index 0000000..53281ac --- /dev/null +++ b/tests/src/test_properties.cpp @@ -0,0 +1,71 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#include "cas_properties.h" + +// TODO(widevine-eng): Reevaluate using "www" in the company name and model name +// fields. For now this is consistent with the values used in unit cdm testing. +static constexpr char kGenericCompanyName[] = "www"; +static constexpr char kGenericModelName[] = "www"; +static constexpr char kProductName[] = "WidevineCasTests"; +static constexpr char kKeyArchitectureName[] = "architecture_name"; +static constexpr char kKeyDeviceName[] = "device_name"; +static constexpr char kOemcPath[] = "cas_oemc_path.so"; + +namespace wvcas { + +bool Properties::GetCompanyName(std::string* company_name) { + if (company_name == nullptr) { + return false; + } + *company_name = kGenericCompanyName; + return true; +} + +bool Properties::GetModelName(std::string* model_name) { + if (model_name == nullptr) { + return false; + } + *model_name = kGenericModelName; + return true; +} + +bool Properties::GetProductName(std::string* product_name) { + if (product_name == nullptr) { + return false; + } + *product_name = kProductName; + return true; +} + +bool Properties::GetArchitectureName(std::string* arch_name) { + if (arch_name == nullptr) { + return false; + } + *arch_name = kKeyArchitectureName; + return true; +} + +bool Properties::GetDeviceName(std::string* device_name) { + if (device_name == nullptr) { + return false; + } + *device_name = kKeyDeviceName; + return true; +} + +bool Properties::GetOEMCryptoPath(std::string* path) { + if (path == nullptr) { + return false; + } + *path = kOemcPath; + return true; +} + +bool Properties::GetWvCasPluginVersion(std::string& version) { + version = "unit-test"; + return true; +} + +} // namespace wvcas diff --git a/tests/src/timer_test.cpp b/tests/src/timer_test.cpp new file mode 100644 index 0000000..0ea6266 --- /dev/null +++ b/tests/src/timer_test.cpp @@ -0,0 +1,35 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#include "timer.h" + +#include +#include + +#include "clock.h" + +class TimerTest : public wvutil::TimerHandler, public ::testing::Test { + public: + void OnTimerEvent() override { ++count_; } + wvutil::Clock clock_; + wvutil::Timer timer_; + uint64_t event_time_ = 0; + uint64_t count_ = 0; +}; + +TEST_F(TimerTest, Timer) { + timer_.Start(this, 1); + EXPECT_TRUE(timer_.IsRunning()); + sleep(2); + timer_.Stop(); + EXPECT_EQ(2, count_); + EXPECT_FALSE(timer_.IsRunning()); + + timer_.Start(this, 1); + EXPECT_TRUE(timer_.IsRunning()); + sleep(3); + timer_.Stop(); + EXPECT_EQ(5, count_); + EXPECT_FALSE(timer_.IsRunning()); +} \ No newline at end of file diff --git a/tests/src/widevine_cas_api_test.cpp b/tests/src/widevine_cas_api_test.cpp new file mode 100644 index 0000000..c9f84e1 --- /dev/null +++ b/tests/src/widevine_cas_api_test.cpp @@ -0,0 +1,1396 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#include "widevine_cas_api.h" + +#include +#include +#include + +#include + +#include "cas_license.h" +#include "cas_util.h" +#include "device_files.pb.h" +#include "ecm_parser.h" +#include "media_cas.pb.h" +#include "mock_crypto_session.h" +#include "mock_ecm_parser.h" +#include "mock_event_listener.h" +#include "string_conversions.h" +#include "widevine_cas_session_map.h" + +namespace wvcas { +namespace { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::NiceMock; +using ::testing::NotNull; +using ::testing::Return; +using ::testing::SetArgPointee; +using ::testing::StrictMock; +using ::video_widevine_client::sdk::File; +using ::video_widevine_client::sdk::HashedFile; + +constexpr char kBasePathPrefix[] = "/data/vendor/mediacas/IDM/widevine/"; +constexpr char kLicenseFileNameSuffix[] = ".lic"; + +// Generate mocked license filename. +std::string GenerateTestLicenseFileName(const std::string& mocked_file_name) { + std::string hash; + hash.resize(SHA256_DIGEST_LENGTH); + const auto* input = + reinterpret_cast(mocked_file_name.data()); + auto* output = reinterpret_cast(&hash[0]); + SHA256(input, mocked_file_name.size(), output); + return kBasePathPrefix + wvutil::b2a_hex(hash) + kLicenseFileNameSuffix; +} + +typedef StrictMock StrictMockCryptoSession; + +class MockLicense : public wvcas::CasLicense { + public: + MockLicense() {} + ~MockLicense() override {} + + MOCK_CONST_METHOD0(IsExpired, bool()); + MOCK_METHOD5(GenerateEntitlementRequest, + wvcas::CasStatus(const std::string& init_data, + const std::string& device_certificate, + const std::string& wrapped_rsa_key, + wvcas::LicenseType license_type, + std::string* signed_license_request)); + MOCK_METHOD(wvcas::CasStatus, HandleStoredLicense, + (const std::string&, const std::string&), (override)); + MOCK_METHOD2(GenerateEntitlementRenewalRequest, + wvcas::CasStatus(const std::string& device_certificate, + std::string* signed_renewal_request)); + MOCK_METHOD2(HandleEntitlementRenewalResponse, + wvcas::CasStatus(const std::string& renewal_response, + std::string* device_file)); + MOCK_METHOD(wvcas::CasStatus, HandleEntitlementResponse, + (const std::string&, std::string*), (override)); + MOCK_METHOD0(BeginDecryption, void()); + MOCK_METHOD0(UpdateLicenseForLicenseRemove, void()); + MOCK_METHOD(std::string, GetGroupId, (), (const, override)); + MOCK_METHOD(std::vector, GetContentIdList, (), + (const, override)); + MOCK_METHOD(bool, IsMultiContentLicense, (), (const, override)); + MOCK_METHOD(bool, IsGroupLicense, (), (const, override)); +}; +typedef StrictMock StrictMockLicense; +typedef StrictMock StrictMockEventListener; + +class MockFile : public wvutil::File { + public: + MockFile() {} + ~MockFile() override {} + MOCK_METHOD2(Read, ssize_t(char* buffer, size_t bytes)); + MOCK_METHOD2(Write, ssize_t(const char* buffer, size_t bytes)); + MOCK_METHOD0(Close, void()); +}; +typedef StrictMock StrictMockFile; + +class MockFileSystem : public wvutil::FileSystem { + public: + MockFileSystem() {} + ~MockFileSystem() override {} + + // Until gmock is updated to a version post-April 2017, we need this + // workaround to test functions that take or return smart pointers. + // See + // https://github.com/abseil/googletest/blob/master/googlemock/docs/CookBook.md#legacy-workarounds-for-move-only-types + std::unique_ptr Open(const std::string& buffer, int flags) { + return std::unique_ptr(DoOpen(buffer, flags)); + } + MOCK_METHOD2(DoOpen, wvutil::File*(const std::string& file_path, int flags)); + MOCK_METHOD1(Exists, bool(const std::string& file_path)); + MOCK_METHOD1(Remove, bool(const std::string& file_path)); + MOCK_METHOD1(FileSize, ssize_t(const std::string& file_path)); + MOCK_METHOD2(List, bool(const std::string& dir_path, + std::vector* names)); + MOCK_CONST_METHOD0(origin, const std::string&()); + MOCK_METHOD1(SetOrigin, void(const std::string& origin)); + MOCK_CONST_METHOD0(identifier, const std::string&()); + MOCK_METHOD1(SetIdentifier, void(const std::string& identifier)); + MOCK_CONST_METHOD0(IsGlobal, bool()); +}; +typedef NiceMock NiceMockFileSystem; + +class MockWidevineSession : public wvcas::WidevineCasSession { + public: + MockWidevineSession() {} + ~MockWidevineSession() override {} + MOCK_METHOD(wvcas::CasStatus, processEcm, + (const wvcas::CasEcm& ecm, uint8_t parental_control_age, + const std::string& license_group_id), + (override)); + MOCK_METHOD2(HandleProcessEcm, + wvcas::CasStatus(const wvcas::WvCasSessionId& sessionId, + const wvcas::CasEcm& ecm)); +}; + +class MockEmmParser : public EmmParser { + public: + MOCK_METHOD(uint64_t, timestamp, (), (const, override)); + MOCK_METHOD(std::string, signature, (), (const, override)); + MOCK_METHOD(EmmPayload, emm_payload, (), (const, override)); +}; + +class TestWidevineCas : public wvcas::WidevineCas { + public: + TestWidevineCas() { + pass_thru_license_ = make_unique(); + pass_thru_crypto_session_ = make_unique(); + pass_thru_file_system_ = make_unique(); + crypto_session_ = pass_thru_crypto_session_.get(); + license_ = pass_thru_license_.get(); + file_system_ = pass_thru_file_system_.get(); + } + ~TestWidevineCas() override {} + + std::shared_ptr getCryptoSession() override { + return std::move(pass_thru_crypto_session_); + } + + void setCryptoSession( + std::unique_ptr crypto_session) { + pass_thru_crypto_session_ = std::move(crypto_session); + crypto_session_ = pass_thru_crypto_session_.get(); + } + + std::unique_ptr getCasLicense() override { + return std::move(pass_thru_license_); + } + + void setCasLicense(std::unique_ptr license) { + pass_thru_license_ = std::move(license); + license_ = pass_thru_license_.get(); + } + + std::unique_ptr getFileSystem() override { + return std::move(pass_thru_file_system_); + } + + std::shared_ptr newCasSession() override { + cas_session_ = std::make_shared >(); + return cas_session_; + } + + std::unique_ptr getEmmParser( + const wvcas::CasEmm& emm) const override { + return std::move(pass_thru_emm_parser_); + } + + void setEmmParser(std::unique_ptr parser) { + pass_thru_emm_parser_ = std::move(parser); + } + + MOCK_METHOD(std::unique_ptr, getEcmParser, + (const wvcas::CasEcm& ecm), (const override)); + + std::unique_ptr pass_thru_license_; + std::unique_ptr pass_thru_crypto_session_; + std::unique_ptr pass_thru_file_system_; + mutable std::unique_ptr pass_thru_emm_parser_; + + StrictMockLicense* license_ = nullptr; + StrictMockCryptoSession* crypto_session_ = nullptr; + NiceMockFileSystem* file_system_ = nullptr; + std::shared_ptr cas_session_ = nullptr; +}; + +struct EcmTestParams { + bool defer_ecm; + bool stored_license; +}; + +class WidevineCasTest : public testing::TestWithParam { + public: + WidevineCasTest() {} + ~WidevineCasTest() override {} + + StrictMockEventListener event_listener_; +}; + +TEST_F(WidevineCasTest, initialize) { + TestWidevineCas cas_api; + + EXPECT_CALL(*(cas_api.crypto_session_), initialize()) + .WillOnce(Return(wvcas::CasStatus::OkStatus())); + + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + cas_api.initialize(nullptr).status_code()); +} + +TEST_F(WidevineCasTest, openAndcloseSession) { + TestWidevineCas cas_api; + + // Invalid + EXPECT_NE(wvcas::CasStatusCode::kNoError, + cas_api.openSession(nullptr).status_code()); + EXPECT_NE(wvcas::CasStatusCode::kNoError, + cas_api.closeSession(wvcas::WvCasSessionId()).status_code()); + + // Valid + EXPECT_CALL(*(cas_api.crypto_session_), initialize()) + .WillOnce(Return(wvcas::CasStatus::OkStatus())); + + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + cas_api.initialize(nullptr).status_code()); + + EXPECT_CALL(*(cas_api.crypto_session_), CreateEntitledKeySession(_)) + .WillOnce(Return(wvcas::CasStatus::OkStatus())); + EXPECT_CALL(*(cas_api.crypto_session_), GetOEMKeyToken(_, _)) + .WillOnce(Return(wvcas::CasStatus::OkStatus())); + wvcas::WvCasSessionId sid; + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + cas_api.openSession(&sid).status_code()); + + EXPECT_CALL(*(cas_api.crypto_session_), RemoveEntitledKeySession(_)) + .WillOnce(Return(wvcas::CasStatus::OkStatus())); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + cas_api.closeSession(sid).status_code()); +} + +TEST_F(WidevineCasTest, generateEntitlementRequest) { + TestWidevineCas cas_api; + + EXPECT_CALL(*(cas_api.crypto_session_), initialize()) + .WillOnce(Return(wvcas::CasStatus::OkStatus())); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + cas_api.initialize(nullptr).status_code()); + EXPECT_CALL(*cas_api.license_, IsGroupLicense).WillRepeatedly(Return(false)); + + // Invalid parameter. + std::string request, init_data, license_id; + EXPECT_EQ(wvcas::CasStatusCode::kInvalidParameter, + cas_api.generateEntitlementRequest(init_data, nullptr, license_id) + .status_code()); + EXPECT_TRUE(license_id.empty()); + + // GenerateEntitlementRequest returns an error. + EXPECT_CALL(*cas_api.license_, GenerateEntitlementRequest(_, _, _, _, _)) + .WillRepeatedly(Return(wvcas::CasStatus( + wvcas::CasStatusCode::kCasLicenseError, "forced failure"))); + EXPECT_EQ(wvcas::CasStatusCode::kCasLicenseError, + cas_api.generateEntitlementRequest(init_data, &request, license_id) + .status_code()); + EXPECT_TRUE(license_id.empty()); + + // HandleStoredLicense returns an error. + // Call to Open will return a unique_ptr, freeing this mock_file object. + MockFile* mock_file = new MockFile(); + size_t mock_filesize = 10; + + EXPECT_CALL(*cas_api.file_system_, Exists(_)).WillRepeatedly(Return(true)); + EXPECT_CALL(*cas_api.file_system_, FileSize(_)) + .WillRepeatedly(Return(mock_filesize)); + EXPECT_CALL(*cas_api.file_system_, DoOpen(_, _)) + .WillRepeatedly(Return(mock_file)); + EXPECT_CALL(*mock_file, Read(_, _)).WillRepeatedly(Return(mock_filesize)); + + EXPECT_CALL(*cas_api.license_, HandleStoredLicense) + .WillRepeatedly(Return(wvcas::CasStatus( + wvcas::CasStatusCode::kCasLicenseError, "forced failure"))); + EXPECT_EQ(wvcas::CasStatusCode::kCasLicenseError, + cas_api.generateEntitlementRequest(init_data, &request, license_id) + .status_code()); + EXPECT_TRUE(license_id.empty()); + + mock_file = new MockFile(); + EXPECT_CALL(*cas_api.file_system_, DoOpen(_, _)) + .WillRepeatedly(Return(mock_file)); + EXPECT_CALL(*mock_file, Read(_, _)).WillRepeatedly(Return(mock_filesize)); + // For expired license file, remove it successfully + // and return CasLicenseError. + EXPECT_CALL(*cas_api.license_, HandleStoredLicense) + .WillRepeatedly(Return(wvcas::CasStatus::OkStatus())); + EXPECT_CALL(*cas_api.license_, IsExpired()).WillRepeatedly(Return(true)); + EXPECT_CALL(*cas_api.file_system_, Remove(_)).WillRepeatedly(Return(true)); + + EXPECT_EQ(wvcas::CasStatusCode::kCasLicenseError, + cas_api.generateEntitlementRequest(init_data, &request, license_id) + .status_code()); + EXPECT_TRUE(license_id.empty()); + + // Unable to remove the expired license file, return InvalidLicenseFile error. + mock_file = new MockFile(); + EXPECT_CALL(*cas_api.file_system_, DoOpen(_, _)) + .WillRepeatedly(Return(mock_file)); + EXPECT_CALL(*mock_file, Read(_, _)).WillRepeatedly(Return(mock_filesize)); + // Unable to remove the expired license file, return InvalidLicenseFile error + EXPECT_CALL(*cas_api.file_system_, Remove(_)).WillOnce(Return(false)); + EXPECT_EQ(wvcas::CasStatusCode::kInvalidLicenseFile, + cas_api.generateEntitlementRequest(init_data, &request, license_id) + .status_code()); + EXPECT_TRUE(license_id.empty()); + + // For stored license file not expired, return valid response. + mock_file = new MockFile(); + EXPECT_CALL(*cas_api.file_system_, DoOpen(_, _)) + .WillRepeatedly(Return(mock_file)); + EXPECT_CALL(*mock_file, Read(_, _)).WillRepeatedly(Return(mock_filesize)); + // For stored license file not expired, return valid response + EXPECT_CALL(*cas_api.license_, IsExpired()).WillRepeatedly(Return(false)); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + cas_api.generateEntitlementRequest(init_data, &request, license_id) + .status_code()); + // For existing license, license_id should be returned. + std::string full_filename = + GenerateTestLicenseFileName(/*mocked_file_name=*/""); + EXPECT_EQ(full_filename.substr( + 0, full_filename.size() - strlen(kLicenseFileNameSuffix)), + license_id); + + // Valid response for new request. + mock_file = new MockFile(); + EXPECT_CALL(*cas_api.file_system_, DoOpen(_, _)) + .WillRepeatedly(Return(mock_file)); + // Valid response for new request + license_id.clear(); + EXPECT_CALL(*cas_api.file_system_, Exists(_)).WillRepeatedly(Return(false)); + EXPECT_CALL(*cas_api.license_, GenerateEntitlementRequest(_, _, _, _, _)) + .WillRepeatedly(Return(wvcas::CasStatus::OkStatus())); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + cas_api.generateEntitlementRequest(init_data, &request, license_id) + .status_code()); + // For the first time license request, the license_id will be returned empty. + EXPECT_TRUE(license_id.empty()); +} + +TEST_F(WidevineCasTest, GenerateLicenseRenewal) { + TestWidevineCas cas_api; + + EXPECT_CALL(*(cas_api.crypto_session_), initialize()) + .WillOnce(Return(wvcas::CasStatus::OkStatus())); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + cas_api.initialize(nullptr).status_code()); + + // Invalid parameter. + EXPECT_NE(wvcas::CasStatusCode::kNoError, + cas_api.generateEntitlementRenewalRequest(nullptr).status_code()); + + // GenerateEntitlementRenewalRequest returns an error. + std::string request; + EXPECT_CALL(*cas_api.license_, GenerateEntitlementRenewalRequest(_, _)) + .WillOnce(Return(wvcas::CasStatus(wvcas::CasStatusCode::kCasLicenseError, + "forced failure"))); + EXPECT_NE(wvcas::CasStatusCode::kNoError, + cas_api.generateEntitlementRenewalRequest(&request).status_code()); + + // Valid + EXPECT_CALL(*cas_api.license_, GenerateEntitlementRenewalRequest(_, _)) + .WillOnce(Return(wvcas::CasStatus::OkStatus())); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + cas_api.generateEntitlementRenewalRequest(&request).status_code()); +} + +TEST_F(WidevineCasTest, EntitlementRenewalResponse) { + TestWidevineCas cas_api; + + EXPECT_CALL(*cas_api.crypto_session_, initialize()) + .WillOnce(Return(wvcas::CasStatus::OkStatus())); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + cas_api.initialize(nullptr).status_code()); + + // Empty response. + std::string init_data; + EXPECT_EQ( + wvcas::CasStatusCode::kCasLicenseError, + cas_api.handleEntitlementRenewalResponse("", init_data).status_code()); + + // Valid. + EXPECT_CALL(*cas_api.license_, HandleEntitlementRenewalResponse(_, _)); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + cas_api.handleEntitlementRenewalResponse("response", init_data) + .status_code()); +} + +TEST_F(WidevineCasTest, RemoveExpiredLicenseInTimerEvent) { + TestWidevineCas cas_api; + + EXPECT_CALL(*cas_api.crypto_session_, initialize()) + .WillOnce(Return(wvcas::CasStatus::OkStatus())); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + cas_api.initialize(nullptr).status_code()); + + // Unable to remove: License is expired but mediaId object is null. + EXPECT_CALL(*cas_api.license_, IsExpired()).WillOnce(Return(true)); + EXPECT_CALL(*cas_api.file_system_, Exists(_)).Times(0); + cas_api.OnTimerEvent(); + + // CasMediaId has been initialized. + EXPECT_CALL(*cas_api.file_system_, Exists(_)).WillOnce(Return(false)); + EXPECT_CALL(*cas_api.license_, GenerateEntitlementRequest(_, _, _, _, _)) + .WillRepeatedly(Return(wvcas::CasStatus::OkStatus())); + + std::string request, init_data, license_id; + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + cas_api.generateEntitlementRequest(init_data, &request, license_id) + .status_code()); + + // Valid: License is expired then start to remove. + EXPECT_TRUE(license_id.empty()); + EXPECT_CALL(*cas_api.license_, IsExpired()).WillOnce(Return(true)); + EXPECT_CALL(*cas_api.file_system_, Exists(_)).WillRepeatedly(Return(true)); + EXPECT_CALL(*cas_api.file_system_, Remove(_)).WillOnce(Return(true)); + cas_api.OnTimerEvent(); + + // Valid: License is expired but now it does not exist on file system. + EXPECT_TRUE(license_id.empty()); + EXPECT_CALL(*cas_api.license_, IsExpired()).WillOnce(Return(true)); + EXPECT_CALL(*cas_api.file_system_, Exists(_)).WillOnce(Return(false)); + EXPECT_CALL(*cas_api.file_system_, Remove(_)).Times(0); + cas_api.OnTimerEvent(); +} + +// Test ecm processing with both license storage and license request. +EcmTestParams params[] = { + {false, false}, // No deferral, No offline license + {true, false}, // Defer ecms, No offline license + {false, true}, // No deferral, Offline license + {true, true}, // Defer ecms, Offline license +}; +INSTANTIATE_TEST_SUITE_P(ECMProcessing, WidevineCasTest, + testing::ValuesIn(params)); + +TEST_P(WidevineCasTest, ECMProcessing) { + TestWidevineCas cas_api; + bool test_deferred_ecm = GetParam().defer_ecm; + bool test_stored_license = GetParam().stored_license; + + EXPECT_CALL(*cas_api.crypto_session_, initialize()) + .WillOnce(Return(wvcas::CasStatus::OkStatus())); + EXPECT_CALL(*cas_api.license_, IsExpired()).WillRepeatedly(Return(false)); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + cas_api.initialize(nullptr).status_code()); + EXPECT_CALL(*cas_api.license_, IsGroupLicense).WillRepeatedly(Return(false)); + + WvCasSessionId video_sid; + WvCasSessionId audio_sid; + // In real implementation, these ids are generated by OEMCrypto. + uint32_t video_key_session_id = 1; + uint32_t audio_key_session_id = 2; + WvCasSessionId video_session_id = {0x01, 0x02}; + WvCasSessionId audio_session_id = {0x0a, 0x0b}; + EXPECT_CALL(*(cas_api.crypto_session_), CreateEntitledKeySession(_)) + .WillOnce(DoAll(SetArgPointee<0>(video_key_session_id), + Return(wvcas::CasStatus::OkStatus()))); + EXPECT_CALL(*(cas_api.crypto_session_), GetOEMKeyToken(_, _)) + .WillOnce([&video_session_id](OEMCrypto_SESSION entitled_key_session_id, + std::vector& token) { + token.assign(video_session_id.begin(), video_session_id.end()); + return CasStatus::OkStatus(); + }); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + cas_api.openSession(&video_sid).status_code()); + EXPECT_EQ(video_sid, video_session_id); + + EXPECT_CALL(*(cas_api.crypto_session_), CreateEntitledKeySession(_)) + .WillOnce(DoAll(SetArgPointee<0>(audio_key_session_id), + Return(wvcas::CasStatus::OkStatus()))); + EXPECT_CALL(*(cas_api.crypto_session_), GetOEMKeyToken(_, _)) + .WillOnce([&audio_session_id](OEMCrypto_SESSION entitled_key_session_id, + std::vector& token) { + token.assign(audio_session_id.begin(), audio_session_id.end()); + return CasStatus::OkStatus(); + }); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + cas_api.openSession(&audio_sid).status_code()); + EXPECT_EQ(audio_sid, audio_session_id); + + wvcas::CasSessionPtr video_session = + wvcas::WidevineCasSessionMap::instance().GetSession(video_sid); + wvcas::CasSessionPtr audio_session = + wvcas::WidevineCasSessionMap::instance().GetSession(audio_sid); + + ASSERT_TRUE(video_session); + ASSERT_TRUE(audio_session); + + std::string video_ecm_str("video_ecm"); + std::string audio_ecm_str("audio_ecm"); + wvcas::CasEcm video_ecm(video_ecm_str.begin(), video_ecm_str.end()); + wvcas::CasEcm audio_ecm(audio_ecm_str.begin(), audio_ecm_str.end()); + + int expected_process_ecm_calls = 1; + if (test_deferred_ecm) { + expected_process_ecm_calls = 2; + EXPECT_EQ(wvcas::CasStatusCode::kDeferedEcmProcessing, + cas_api.processEcm(video_sid, video_ecm).status_code()); + EXPECT_EQ(wvcas::CasStatusCode::kDeferedEcmProcessing, + cas_api.processEcm(audio_sid, audio_ecm).status_code()); + } + // Two ecm streams * the number of processed ecms. + int expected_begin_decryption_calls = expected_process_ecm_calls * 2; + + EXPECT_CALL(*reinterpret_cast(video_session.get()), + processEcm(video_ecm, 0, "")) + .Times(expected_process_ecm_calls); + EXPECT_CALL(*reinterpret_cast(audio_session.get()), + processEcm(audio_ecm, 0, "")) + .Times(expected_process_ecm_calls); + EXPECT_CALL(*cas_api.license_, BeginDecryption()) + .Times(expected_begin_decryption_calls); + + if (test_stored_license) { + std::string request, license_id; + std::string file_data("license_file"); + + EXPECT_CALL(*cas_api.file_system_, Exists(_)).WillOnce(Return(true)); + EXPECT_CALL(*cas_api.file_system_, FileSize(_)) + .WillOnce(Return(file_data.size())); + auto* file_handle = new NiceMock; + EXPECT_CALL(*cas_api.file_system_, DoOpen(_, _)) + .WillOnce(Return(file_handle)); + + EXPECT_CALL(*file_handle, Read(_, _)).WillOnce(Return(file_data.size())); + EXPECT_CALL(*cas_api.license_, HandleStoredLicense); + EXPECT_EQ( + wvcas::CasStatusCode::kNoError, + cas_api.generateEntitlementRequest("init_data", &request, license_id) + .status_code()); + // For existing license, license_id should be returned. + std::string full_filename = + GenerateTestLicenseFileName(/*mocked_file_name=*/""); + EXPECT_EQ(full_filename.substr( + 0, full_filename.size() - strlen(kLicenseFileNameSuffix)), + license_id); + } else { + // Empty response. + // Initialize CaMediaId. + EXPECT_CALL(*cas_api.file_system_, Exists(_)).WillOnce(Return(false)); + EXPECT_CALL(*cas_api.license_, GenerateEntitlementRequest(_, _, _, _, _)) + .WillRepeatedly(Return(wvcas::CasStatus::OkStatus())); + std::string request, init_data, license_id; + EXPECT_EQ( + wvcas::CasStatusCode::kNoError, + cas_api.generateEntitlementRequest(init_data, &request, license_id) + .status_code()); + + EXPECT_CALL(*cas_api.license_, HandleEntitlementResponse) + .WillOnce(Return(wvcas::CasStatusCode::kNoError)); + std::string multi_content_license_info; + std::string group_license_info; + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + cas_api + .handleEntitlementResponse("response", init_data, + multi_content_license_info, + group_license_info) + .status_code()); + } + + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + cas_api.processEcm(video_sid, video_ecm).status_code()); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + cas_api.processEcm(audio_sid, audio_ecm).status_code()); + + EXPECT_CALL(*(cas_api.crypto_session_), + RemoveEntitledKeySession(video_key_session_id)); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + cas_api.closeSession(video_sid).status_code()); + EXPECT_CALL(*(cas_api.crypto_session_), + RemoveEntitledKeySession(audio_key_session_id)); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + cas_api.closeSession(audio_sid).status_code()); +} + +TEST_F(WidevineCasTest, RemoveLicense) { + TestWidevineCas cas_api; + + EXPECT_CALL(*cas_api.crypto_session_, initialize()) + .WillOnce(Return(wvcas::CasStatus::OkStatus())); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + cas_api.initialize(nullptr).status_code()); + + // mediaId is not initialized. + std::string mocked_file_name; + EXPECT_EQ(wvcas::CasStatusCode::kCasLicenseError, + cas_api.RemoveLicense(mocked_file_name).status_code()); + + // CasMediaId has been initialized. + EXPECT_CALL(*cas_api.file_system_, Exists(_)).WillOnce(Return(false)); + EXPECT_CALL(*cas_api.license_, GenerateEntitlementRequest(_, _, _, _, _)) + .WillRepeatedly(Return(wvcas::CasStatus::OkStatus())); + std::string request, init_data, license_id; + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + cas_api.generateEntitlementRequest(init_data, &request, license_id) + .status_code()); + // For the first time license request, the license_id will be returned empty. + EXPECT_TRUE(license_id.empty()); + + // Unable to remove license file, return InvalidLicenseFile error. + EXPECT_CALL(*cas_api.file_system_, Exists(_)).WillOnce(Return(true)); + EXPECT_CALL(*cas_api.file_system_, Remove(_)).WillOnce(Return(false)); + + EXPECT_EQ(wvcas::CasStatusCode::kInvalidLicenseFile, + cas_api.RemoveLicense(mocked_file_name).status_code()); + + // Happy case: remove the unused license file + MockFile mock_file; + size_t mock_filesize = 10; + EXPECT_CALL(*cas_api.file_system_, Exists(_)).WillRepeatedly(Return(true)); + EXPECT_CALL(*cas_api.file_system_, FileSize(_)) + .WillRepeatedly(Return(mock_filesize)); + EXPECT_CALL(*cas_api.file_system_, DoOpen(_, _)) + .WillRepeatedly(Return(&mock_file)); + EXPECT_CALL(mock_file, Read(_, _)).WillRepeatedly(Return(mock_filesize)); + EXPECT_CALL(mock_file, Close()).WillRepeatedly(Return()); + EXPECT_CALL(*cas_api.file_system_, Remove(_)).WillRepeatedly(Return(true)); + EXPECT_CALL(*cas_api.license_, UpdateLicenseForLicenseRemove()).Times(0); + + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + cas_api.RemoveLicense(mocked_file_name).status_code()); +} + +TEST_F(WidevineCasTest, RemoveLicenseInUse) { + TestWidevineCas cas_api; + EXPECT_CALL(*cas_api.crypto_session_, initialize()) + .WillOnce(Return(wvcas::CasStatus::OkStatus())); + EXPECT_EQ(cas_api.initialize(nullptr).status_code(), + wvcas::CasStatusCode::kNoError); + EXPECT_CALL(*cas_api.license_, IsExpired()).WillRepeatedly(Return(false)); + + // Initialize media_id_. + EXPECT_CALL(*cas_api.license_, GenerateEntitlementRequest) + .WillOnce(Return(wvcas::CasStatus::OkStatus())); + std::string request, init_data, license_id; + EXPECT_EQ(cas_api.generateEntitlementRequest(init_data, &request, license_id) + .status_code(), + wvcas::CasStatusCode::kNoError); + // Install the license + EXPECT_CALL(*cas_api.license_, HandleEntitlementResponse(_, NotNull())) + .WillOnce(DoAll(SetArgPointee<1>("device_file"), + Return(wvcas::CasStatus::OkStatus()))); + EXPECT_CALL(*cas_api.license_, IsMultiContentLicense) + .WillRepeatedly(Return(false)); + EXPECT_CALL(*cas_api.license_, IsGroupLicense).WillRepeatedly(Return(false)); + EXPECT_CALL(*cas_api.license_, GetGroupId).WillRepeatedly(Return("")); + std::string multi_content_license_info; + std::string group_license_info; + EXPECT_EQ(cas_api + .handleEntitlementResponse("response", license_id, + multi_content_license_info, + group_license_info) + .status_code(), + wvcas::CasStatusCode::kNoError); + EXPECT_FALSE(license_id.empty()); + + MockFile mock_file; + EXPECT_CALL(*cas_api.file_system_, Exists(_)).WillRepeatedly(Return(true)); + EXPECT_CALL(*cas_api.file_system_, Remove(_)).WillRepeatedly(Return(true)); + EXPECT_CALL(*cas_api.license_, UpdateLicenseForLicenseRemove()).Times(1); + EXPECT_EQ(cas_api.RemoveLicense(license_id + ".lic").status_code(), + wvcas::CasStatusCode::kNoError); +} + +TEST_F(WidevineCasTest, handleMultiContentEntitlementResponse) { + TestWidevineCas cas_api; + EXPECT_CALL(*cas_api.crypto_session_, initialize()) + .WillOnce(Return(wvcas::CasStatus::OkStatus())); + EXPECT_EQ(cas_api.initialize(nullptr).status_code(), + wvcas::CasStatusCode::kNoError); + EXPECT_CALL(*cas_api.license_, IsExpired()).WillRepeatedly(Return(false)); + + // Initialize media_id_. + EXPECT_CALL(*cas_api.license_, GenerateEntitlementRequest) + .WillOnce(Return(wvcas::CasStatus::OkStatus())); + std::string request, init_data, license_id; + EXPECT_EQ(cas_api.generateEntitlementRequest(init_data, &request, license_id) + .status_code(), + wvcas::CasStatusCode::kNoError); + + std::string license_group_id = "license_group_id"; + std::vector content_list = {"content1", "content2"}; + EXPECT_CALL(*cas_api.license_, HandleEntitlementResponse(_, NotNull())) + .WillOnce(DoAll(SetArgPointee<1>("device_file"), + Return(wvcas::CasStatus::OkStatus()))); + EXPECT_CALL(*cas_api.license_, IsMultiContentLicense) + .WillRepeatedly(Return(true)); + EXPECT_CALL(*cas_api.license_, IsGroupLicense).WillRepeatedly(Return(false)); + EXPECT_CALL(*cas_api.license_, GetGroupId) + .WillRepeatedly(Return(license_group_id)); + EXPECT_CALL(*cas_api.license_, GetContentIdList) + .WillRepeatedly(Return(content_list)); + + std::string multi_content_license_info; + std::string group_license_info; + EXPECT_EQ(cas_api + .handleEntitlementResponse("response", license_id, + multi_content_license_info, + group_license_info) + .status_code(), + wvcas::CasStatusCode::kNoError); + + std::string expected_license_id = + GenerateTestLicenseFileName(license_group_id); + expected_license_id = expected_license_id.substr( + 0, expected_license_id.size() - strlen(kLicenseFileNameSuffix)); + EXPECT_EQ(license_id, expected_license_id); + + std::string expected_info; + expected_info.push_back(0); + expected_info.push_back(0); + expected_info.push_back(license_id.size()); + expected_info.append(license_id); + expected_info.push_back(1); + expected_info.push_back(0); + expected_info.push_back(content_list[0].size()); + expected_info.append(content_list[0]); + expected_info.push_back(1); + expected_info.push_back(0); + expected_info.push_back(content_list[1].size()); + expected_info.append(content_list[1]); + EXPECT_EQ(multi_content_license_info, expected_info); + EXPECT_TRUE(group_license_info.empty()); +} + +TEST_F(WidevineCasTest, handleGroupEntitlementResponse) { + TestWidevineCas cas_api; + EXPECT_CALL(*cas_api.crypto_session_, initialize()) + .WillOnce(Return(wvcas::CasStatus::OkStatus())); + EXPECT_EQ(cas_api.initialize(nullptr).status_code(), + wvcas::CasStatusCode::kNoError); + EXPECT_CALL(*cas_api.license_, IsExpired()).WillRepeatedly(Return(false)); + + // Initialize media_id_. + EXPECT_CALL(*cas_api.license_, GenerateEntitlementRequest) + .WillOnce(Return(wvcas::CasStatus::OkStatus())); + std::string request, init_data, license_id; + EXPECT_EQ(cas_api.generateEntitlementRequest(init_data, &request, license_id) + .status_code(), + wvcas::CasStatusCode::kNoError); + + std::string license_group_id = "license_group_id"; + std::vector content_list = {}; + EXPECT_CALL(*cas_api.license_, HandleEntitlementResponse(_, NotNull())) + .WillOnce(DoAll(SetArgPointee<1>("device_file"), + Return(wvcas::CasStatus::OkStatus()))); + EXPECT_CALL(*cas_api.license_, IsMultiContentLicense) + .WillRepeatedly(Return(false)); + EXPECT_CALL(*cas_api.license_, IsGroupLicense).WillRepeatedly(Return(true)); + EXPECT_CALL(*cas_api.license_, GetGroupId) + .WillRepeatedly(Return(license_group_id)); + EXPECT_CALL(*cas_api.license_, GetContentIdList) + .WillRepeatedly(Return(content_list)); + + std::string multi_content_license_info; + std::string group_license_info; + EXPECT_EQ(cas_api + .handleEntitlementResponse("response", license_id, + multi_content_license_info, + group_license_info) + .status_code(), + wvcas::CasStatusCode::kNoError); + + std::string expected_license_id = + GenerateTestLicenseFileName(license_group_id); + expected_license_id = expected_license_id.substr( + 0, expected_license_id.size() - strlen(kLicenseFileNameSuffix)); + EXPECT_EQ(license_id, expected_license_id); + + std::string expected_info; + expected_info.push_back(0); + expected_info.push_back(0); + expected_info.push_back(license_id.size()); + expected_info.append(license_id); + expected_info.push_back(1); + expected_info.push_back(0); + expected_info.push_back(license_group_id.size()); + expected_info.append(license_group_id); + EXPECT_EQ(group_license_info, expected_info); + EXPECT_TRUE(multi_content_license_info.empty()); +} + +TEST_F(WidevineCasTest, ECMProcessingWithGroupId) { + TestWidevineCas cas_api; + EXPECT_CALL(*cas_api.crypto_session_, initialize()) + .WillOnce(Return(wvcas::CasStatus::OkStatus())); + EXPECT_EQ(cas_api.initialize(nullptr).status_code(), + wvcas::CasStatusCode::kNoError); + EXPECT_CALL(*cas_api.license_, IsExpired()).WillRepeatedly(Return(false)); + + // Initialize media_id_. + EXPECT_CALL(*cas_api.license_, GenerateEntitlementRequest) + .WillOnce(Return(wvcas::CasStatus::OkStatus())); + std::string request, init_data, license_id; + EXPECT_EQ(cas_api.generateEntitlementRequest(init_data, &request, license_id) + .status_code(), + wvcas::CasStatusCode::kNoError); + // Install license + const std::string license_group_id = "license_group_id"; + EXPECT_CALL(*cas_api.license_, HandleEntitlementResponse) + .WillOnce(Return(wvcas::CasStatus::OkStatus())); + EXPECT_CALL(*cas_api.license_, IsMultiContentLicense) + .WillRepeatedly(Return(false)); + EXPECT_CALL(*cas_api.license_, IsGroupLicense).WillRepeatedly(Return(true)); + EXPECT_CALL(*cas_api.license_, GetGroupId) + .WillRepeatedly(Return(license_group_id)); + std::string multi_content_license_info; + std::string group_license_info; + EXPECT_EQ(cas_api + .handleEntitlementResponse("response", license_id, + multi_content_license_info, + group_license_info) + .status_code(), + wvcas::CasStatusCode::kNoError); + // Init a session + wvcas::WvCasSessionId sid; + EXPECT_CALL(*(cas_api.crypto_session_), CreateEntitledKeySession(_)) + .WillOnce(Return(wvcas::CasStatus::OkStatus())); + EXPECT_CALL(*(cas_api.crypto_session_), GetOEMKeyToken(_, _)) + .WillOnce([](OEMCrypto_SESSION entitled_key_session_id, + std::vector& token) { + token = {0x10}; + return CasStatus::OkStatus(); + }); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + cas_api.openSession(&sid).status_code()); + wvcas::CasSessionPtr session = + wvcas::WidevineCasSessionMap::instance().GetSession(sid); + ASSERT_TRUE(session != nullptr); + const wvcas::CasEcm ecm = {1, 2, 3}; + // It is expected that process ecm with group_id + EXPECT_CALL(*reinterpret_cast(session.get()), + processEcm(ecm, 0, license_group_id)) + .Times(1); + EXPECT_CALL(*cas_api.license_, BeginDecryption()); + + EXPECT_EQ(cas_api.processEcm(sid, ecm).status_code(), + wvcas::CasStatusCode::kNoError); + EXPECT_CALL(*(cas_api.crypto_session_), RemoveEntitledKeySession(_)); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + cas_api.closeSession(sid).status_code()); +} + +TEST_F(WidevineCasTest, ProcessCAPrivateData) { + TestWidevineCas cas_api; + video_widevine::CaDescriptorPrivateData private_data; + private_data.set_provider("provider"); + private_data.set_content_id("content_id"); + private_data.add_group_ids("group1"); + private_data.add_group_ids("group2"); + std::string serialized_private_data; + private_data.SerializeToString(&serialized_private_data); + video_widevine::WidevinePsshData expected_pssh; + expected_pssh.set_provider("provider"); + expected_pssh.set_content_id("content_id"); + expected_pssh.add_group_ids("group1"); + expected_pssh.add_group_ids("group2"); + expected_pssh.set_type(video_widevine::WidevinePsshData::ENTITLEMENT); + std::string expected_serialized_pssh; + expected_pssh.SerializeToString(&expected_serialized_pssh); + std::string init_data; + + ASSERT_EQ(cas_api + .ProcessCAPrivateData({serialized_private_data.begin(), + serialized_private_data.end()}, + &init_data) + .status_code(), + wvcas::CasStatusCode::kNoError); + + EXPECT_EQ(init_data, expected_serialized_pssh); +} + +TEST_F(WidevineCasTest, ProcessEmmEmptyEmmPayload) { + TestWidevineCas cas_api; + MockEventListener event_listener; + EXPECT_CALL(*cas_api.crypto_session_, initialize()) + .WillOnce(Return(wvcas::CasStatus::OkStatus())); + EXPECT_EQ(cas_api.initialize(&event_listener).status_code(), + wvcas::CasStatusCode::kNoError); + + auto emm_parser = make_unique(); + MockEmmParser* parser = emm_parser.get(); + cas_api.setEmmParser(std::move(emm_parser)); + + video_widevine::EmmPayload emm_payload; + EXPECT_CALL(*parser, emm_payload).WillOnce(Return(emm_payload)); + EXPECT_CALL(event_listener, OnFingerprintingUpdated).Times(0); + EXPECT_CALL(event_listener, OnServiceBlockingUpdated).Times(0); + cas_api.processEmm({}); +} + +class WidevineCasProcessEmmTest : public WidevineCasTest { + protected: + void LoadFingerprinting() { + auto fingerprinting_1 = emm_payload_.add_fingerprinting(); + fingerprinting_1->add_channels("CH1"); + fingerprinting_1->add_channels("CH2"); + fingerprinting_1->set_control("control"); + auto fingerprinting_2 = emm_payload_.add_fingerprinting(); + fingerprinting_2->add_channels("1003"); + fingerprinting_2->set_control("off"); + } + + void LoadServiceBlocking() { + auto service_blocking_1 = emm_payload_.add_service_blocking(); + service_blocking_1->add_channels("CH1"); + service_blocking_1->add_channels("CH2"); + service_blocking_1->add_device_groups("g1"); + service_blocking_1->add_device_groups("g2"); + service_blocking_1->set_start_time_sec(0x12345678); + service_blocking_1->set_end_time_sec(0x87654321); + auto service_blocking_2 = emm_payload_.add_service_blocking(); + service_blocking_2->add_channels("CH3"); + service_blocking_2->add_device_groups("100"); + service_blocking_2->set_end_time_sec(0x987654321); + } + + video_widevine::EmmPayload emm_payload_; +}; + +TEST_F(WidevineCasProcessEmmTest, FingerprintingEmm) { + TestWidevineCas cas_api; + MockEventListener event_listener; + EXPECT_CALL(*cas_api.crypto_session_, initialize()) + .WillOnce(Return(wvcas::CasStatus::OkStatus())); + EXPECT_EQ(cas_api.initialize(&event_listener).status_code(), + wvcas::CasStatusCode::kNoError); + + LoadFingerprinting(); + const std::vector expected_message_1 = { + 0x00, // Type FINGERPRINTING_CHANNEL + 0x00, 0x03, // Length (bytes) + 'C', 'H', '1', // Value (channel) + 0x00, // Type FINGERPRINTING_CHANNEL + 0x00, 0x03, // Length (bytes) + 'C', 'H', '2', // Value (channel) + 0x01, // Type FINGERPRINTING_CONTROL + 0x00, 0x07, // Length (bytes) + 'c', 'o', 'n', 't', 'r', 'o', 'l' // Value (channel) + }; + const std::vector expected_message_2 = { + 0x00, // Type FINGERPRINTING_CHANNEL + 0x00, 0x04, // Length (bytes) + '1', '0', '0', '3', // Value (channel) + 0x01, // Type FINGERPRINTING_CONTROL + 0x00, 0x03, // Length (bytes) + 'o', 'f', 'f' // Value (channel) + }; + + auto emm_parser = make_unique(); + MockEmmParser* parser = emm_parser.get(); + cas_api.setEmmParser(std::move(emm_parser)); + + EXPECT_CALL(*parser, emm_payload).WillOnce(Return(emm_payload_)); + EXPECT_CALL(event_listener, OnServiceBlockingUpdated).Times(0); + EXPECT_CALL(event_listener, OnFingerprintingUpdated(expected_message_1)) + .Times(1); + EXPECT_CALL(event_listener, OnFingerprintingUpdated(expected_message_2)) + .Times(1); + + EXPECT_EQ(cas_api.processEmm({}).status_code(), + wvcas::CasStatusCode::kNoError); +} + +TEST_F(WidevineCasProcessEmmTest, ServiceBlockingEmm) { + TestWidevineCas cas_api; + MockEventListener event_listener; + EXPECT_CALL(*cas_api.crypto_session_, initialize()) + .WillOnce(Return(wvcas::CasStatus::OkStatus())); + EXPECT_EQ(cas_api.initialize(&event_listener).status_code(), + wvcas::CasStatusCode::kNoError); + + LoadServiceBlocking(); + const std::vector expected_message_1 = { + 0x00, // Type SERVICE_BLOCKING_CHANNEL + 0x00, 0x03, // Length (bytes) + 'C', 'H', '1', // Value + 0x00, // Type SERVICE_BLOCKING_CHANNEL + 0x00, 0x03, // Length (bytes) + 'C', 'H', '2', // Value + 0x01, // Type SERVICE_BLOCKING_DEVICE_GROUP + 0x00, 0x02, // Length (bytes) + 'g', '1', // Value + 0x01, // Type SERVICE_BLOCKING_DEVICE_GROUP + 0x00, 0x02, // Length (bytes) + 'g', '2', // Value + 0x02, // Type SERVICE_BLOCKING_START_TIME_SECONDS + 0x00, 0x08, // Length (bytes) + 0x00, 0x00, 0x00, 0x00, 0x12, 0x34, 0x56, 0x78, // Value + 0x03, // Type SERVICE_BLOCKING_END_TIME_SECONDS + 0x00, 0x08, // Length (bytes) + 0x00, 0x00, 0x00, 0x00, 0x87, 0x65, 0x43, 0x21 // Value + }; + const std::vector expected_message_2 = { + 0x00, // Type SERVICE_BLOCKING_CHANNEL + 0x00, 0x03, // Length (bytes) + 'C', 'H', '3', // Value + 0x01, // Type SERVICE_BLOCKING_DEVICE_GROUP + 0x00, 0x03, // Length (bytes) + '1', '0', '0', // Value + 0x03, // Type SERVICE_BLOCKING_END_TIME_SECONDS + 0x00, 0x08, // Length (bytes) + 0x00, 0x00, 0x00, 0x09, 0x87, 0x65, 0x43, 0x21 // Value + }; + + auto emm_parser = make_unique(); + MockEmmParser* parser = emm_parser.get(); + cas_api.setEmmParser(std::move(emm_parser)); + + EXPECT_CALL(*parser, emm_payload).WillOnce(Return(emm_payload_)); + EXPECT_CALL(event_listener, OnFingerprintingUpdated).Times(0); + EXPECT_CALL(event_listener, OnServiceBlockingUpdated(expected_message_1)) + .Times(1); + EXPECT_CALL(event_listener, OnServiceBlockingUpdated(expected_message_2)) + .Times(1); + + EXPECT_EQ(cas_api.processEmm({}).status_code(), + wvcas::CasStatusCode::kNoError); +} + +TEST_F(WidevineCasProcessEmmTest, MultipleSameEmm) { + TestWidevineCas cas_api; + MockEventListener event_listener; + EXPECT_CALL(*cas_api.crypto_session_, initialize()) + .WillOnce(Return(wvcas::CasStatus::OkStatus())); + EXPECT_EQ(cas_api.initialize(&event_listener).status_code(), + wvcas::CasStatusCode::kNoError); + + LoadFingerprinting(); + LoadServiceBlocking(); + + auto emm_parser = make_unique(); + MockEmmParser* parser = emm_parser.get(); + cas_api.setEmmParser(std::move(emm_parser)); + EXPECT_CALL(*parser, emm_payload).WillOnce(Return(emm_payload_)); + EXPECT_CALL(event_listener, OnServiceBlockingUpdated).Times(2); + EXPECT_CALL(event_listener, OnFingerprintingUpdated).Times(2); + EXPECT_EQ(cas_api.processEmm({}).status_code(), + wvcas::CasStatusCode::kNoError); + + // Calling again will not trigger the events again. + auto emm_parser2 = make_unique(); + MockEmmParser* parser2 = emm_parser2.get(); + cas_api.setEmmParser(std::move(emm_parser2)); + EXPECT_CALL(*parser2, emm_payload).WillOnce(Return(emm_payload_)); + EXPECT_CALL(event_listener, OnServiceBlockingUpdated).Times(0); + EXPECT_CALL(event_listener, OnFingerprintingUpdated).Times(0); + EXPECT_EQ(cas_api.processEmm({}).status_code(), + wvcas::CasStatusCode::kNoError); +} + +TEST_F(WidevineCasProcessEmmTest, MultipleDifferentEmms) { + TestWidevineCas cas_api; + MockEventListener event_listener; + EXPECT_CALL(*cas_api.crypto_session_, initialize()) + .WillOnce(Return(wvcas::CasStatus::OkStatus())); + EXPECT_EQ(cas_api.initialize(&event_listener).status_code(), + wvcas::CasStatusCode::kNoError); + + LoadFingerprinting(); + LoadServiceBlocking(); + + auto emm_parser = make_unique(); + MockEmmParser* parser = emm_parser.get(); + cas_api.setEmmParser(std::move(emm_parser)); + EXPECT_CALL(*parser, emm_payload).WillOnce(Return(emm_payload_)); + EXPECT_CALL(event_listener, OnServiceBlockingUpdated).Times(2); + EXPECT_CALL(event_listener, OnFingerprintingUpdated).Times(2); + EXPECT_EQ(cas_api.processEmm({}).status_code(), + wvcas::CasStatusCode::kNoError); + + // Change one of the fingerprinting + emm_payload_.mutable_fingerprinting(0)->set_control("changed"); + emm_payload_.mutable_service_blocking(0)->set_end_time_sec(100); + // Calling again will trigger only one event. + auto emm_parser2 = make_unique(); + MockEmmParser* parser2 = emm_parser2.get(); + cas_api.setEmmParser(std::move(emm_parser2)); + EXPECT_CALL(*parser2, emm_payload).WillOnce(Return(emm_payload_)); + EXPECT_CALL(event_listener, OnServiceBlockingUpdated).Times(1); + EXPECT_CALL(event_listener, OnFingerprintingUpdated).Times(1); + EXPECT_EQ(cas_api.processEmm({}).status_code(), + wvcas::CasStatusCode::kNoError); +} + +TEST_F(WidevineCasTest, ProcessCAPrivateDataWithEntitlementPeriodIndex) { + TestWidevineCas cas_api; + EXPECT_CALL(*(cas_api.crypto_session_), initialize()) + .WillOnce(Return(wvcas::CasStatus::OkStatus())); + EXPECT_EQ(cas_api.initialize(nullptr).status_code(), + wvcas::CasStatusCode::kNoError); + // Set up mock ecm parser. + const uint32_t expected_entitlement_period_index = 10; + auto ecm_parser = make_unique(); + EXPECT_CALL(*ecm_parser, is_entitlement_rotation_enabled) + .WillOnce(Return(true)); + EXPECT_CALL(*ecm_parser, entitlement_period_index) + .WillOnce(Return(expected_entitlement_period_index)); + EXPECT_CALL(cas_api, getEcmParser) + .WillOnce(Return(testing::ByMove(std::move(ecm_parser)))); + // Construct the private data. + std::string private_data_str; + video_widevine::CaDescriptorPrivateData private_data; + private_data.set_provider("provider"); + private_data.set_content_id("content_id"); + private_data.SerializeToString(&private_data_str); + std::string actual_init_data; + + // Process the ECM without license to will extract the entitlement period + // index. + EXPECT_EQ( + cas_api.processEcm(/*sessionId=*/{}, /*ecm=*/{1, 2, 3}).status_code(), + wvcas::CasStatusCode::kDeferedEcmProcessing); + EXPECT_EQ(cas_api + .ProcessCAPrivateData( + {private_data_str.begin(), private_data_str.end()}, + &actual_init_data) + .status_code(), + wvcas::CasStatusCode::kNoError); + + video_widevine::WidevinePsshData pssh; + pssh.ParseFromString(actual_init_data); + EXPECT_EQ(pssh.entitlement_period_index(), expected_entitlement_period_index); +} + +bool Hash(const std::string& data, std::string* hash) { + if (!hash) return false; + hash->resize(SHA256_DIGEST_LENGTH); + const unsigned char* input = + reinterpret_cast(data.data()); + unsigned char* output = reinterpret_cast(&(*hash)[0]); + SHA256(input, data.size(), output); + return true; +} + +TEST_F(WidevineCasTest, GenerateEntitlementRequestWithEntitlementPeriodIndex) { + TestWidevineCas cas_api; + EXPECT_CALL(*(cas_api.crypto_session_), initialize()) + .WillOnce(Return(wvcas::CasStatus::OkStatus())); + EXPECT_EQ(cas_api.initialize(nullptr).status_code(), + wvcas::CasStatusCode::kNoError); + // pssh is the init data input to generateEntitlementRequest(). + video_widevine::WidevinePsshData pssh; + pssh.set_provider("provider"); + pssh.set_content_id("content_id"); + pssh.set_type(video_widevine::WidevinePsshData::ENTITLEMENT); + pssh.set_entitlement_period_index(123); + // Create a license request containing the pssh. + video_widevine::LicenseRequest license_request; + license_request.mutable_content_id()->mutable_cenc_id_deprecated()->add_pssh( + pssh.SerializeAsString()); + // Create a license file. + File file; + file.set_type(File::LICENSE); + license_request.SerializeToString( + file.mutable_license()->mutable_license_request()); + // Hash the created file + HashedFile hashed_file; + file.SerializeToString(hashed_file.mutable_file()); + Hash(hashed_file.file(), hashed_file.mutable_hash()); + const std::string license_file = hashed_file.SerializeAsString(); + + // Call to Open will return a unique_ptr, freeing this mock_file object. + MockFile* mock_file = new MockFile(); + EXPECT_CALL(*cas_api.file_system_, Exists(_)).WillRepeatedly(Return(true)); + EXPECT_CALL(*cas_api.file_system_, FileSize(_)) + .WillRepeatedly(Return(license_file.size())); + EXPECT_CALL(*cas_api.file_system_, DoOpen(_, _)) + .WillRepeatedly(Return(mock_file)); + EXPECT_CALL(*mock_file, Read(NotNull(), license_file.size())) + .WillRepeatedly([&](char* buffer, size_t bytes) { + memcpy(buffer, license_file.data(), bytes); + return bytes; + }); + EXPECT_CALL(*cas_api.license_, HandleStoredLicense) + .WillRepeatedly(Return(wvcas::CasStatus::OkStatus())); + EXPECT_CALL(*cas_api.license_, IsExpired()).WillRepeatedly(Return(false)); + EXPECT_CALL(*cas_api.license_, IsGroupLicense()) + .WillRepeatedly(Return(false)); + // A new license request will not be generated. + EXPECT_CALL(*cas_api.license_, GenerateEntitlementRequest).Times(0); + std::string entitlement_request; + std::string license_id; + + EXPECT_TRUE(cas_api + .generateEntitlementRequest(pssh.SerializeAsString(), + &entitlement_request, license_id) + .ok()); + + EXPECT_TRUE(entitlement_request.empty()); + EXPECT_FALSE(license_id.empty()); +} + +TEST_F(WidevineCasTest, + GenerateEntitlementRequestWithOutdatedEntitlementPeriodIndex) { + TestWidevineCas cas_api; + EXPECT_CALL(*(cas_api.crypto_session_), initialize()) + .WillOnce(Return(wvcas::CasStatus::OkStatus())); + EXPECT_EQ(cas_api.initialize(nullptr).status_code(), + wvcas::CasStatusCode::kNoError); + video_widevine::WidevinePsshData pssh; + pssh.set_provider("provider"); + pssh.set_content_id("content_id"); + pssh.set_type(video_widevine::WidevinePsshData::ENTITLEMENT); + pssh.set_entitlement_period_index(123); + video_widevine::WidevinePsshData stored_pssh; + stored_pssh.set_provider("provider"); + stored_pssh.set_content_id("content_id"); + stored_pssh.set_type(video_widevine::WidevinePsshData::ENTITLEMENT); + stored_pssh.set_entitlement_period_index( + 122); // Not equal to 123. + // Create a license request containing the pssh. + video_widevine::LicenseRequest license_request; + license_request.mutable_content_id()->mutable_cenc_id_deprecated()->add_pssh( + stored_pssh.SerializeAsString()); + // Create a license file. + File file; + file.set_type(File::LICENSE); + license_request.SerializeToString( + file.mutable_license()->mutable_license_request()); + // Hash the created file + HashedFile hashed_file; + file.SerializeToString(hashed_file.mutable_file()); + const std::string license_file = hashed_file.SerializeAsString(); + + // Call to Open will return a unique_ptr, freeing this mock_file object. + MockFile* mock_file = new MockFile(); + EXPECT_CALL(*cas_api.file_system_, Exists(_)).WillRepeatedly(Return(true)); + EXPECT_CALL(*cas_api.file_system_, FileSize(_)) + .WillRepeatedly(Return(license_file.size())); + EXPECT_CALL(*cas_api.file_system_, DoOpen(_, _)) + .WillRepeatedly(Return(mock_file)); + EXPECT_CALL(*mock_file, Read(NotNull(), license_file.size())) + .WillRepeatedly([&](char* buffer, size_t bytes) { + memcpy(buffer, license_file.data(), bytes); + return bytes; + }); + EXPECT_CALL(*cas_api.license_, HandleStoredLicense) + .WillRepeatedly(Return(wvcas::CasStatus::OkStatus())); + EXPECT_CALL(*cas_api.license_, IsExpired()).WillRepeatedly(Return(false)); + EXPECT_CALL(*cas_api.license_, IsGroupLicense()) + .WillRepeatedly(Return(false)); + std::string expected_request = "entitlement_request"; + EXPECT_CALL(*cas_api.license_, + GenerateEntitlementRequest(_, _, _, _, NotNull())) + .Times(1) + .WillOnce(DoAll(SetArgPointee<4>(expected_request), + Return(wvcas::CasStatus::OkStatus()))); + std::string entitlement_request; + std::string license_id; + + EXPECT_TRUE(cas_api + .generateEntitlementRequest(pssh.SerializeAsString(), + &entitlement_request, license_id) + .ok()); + + // A license request is generated. + EXPECT_EQ(entitlement_request, expected_request); + EXPECT_TRUE(license_id.empty()); +} + +TEST_F(WidevineCasTest, generateEntitlementPeriodUpdateRequestSuccess) { + TestWidevineCas cas_api; + EXPECT_CALL(*cas_api.crypto_session_, initialize()) + .WillOnce(Return(wvcas::CasStatus::OkStatus())); + EXPECT_EQ(cas_api.initialize(&event_listener_).status_code(), + wvcas::CasStatusCode::kNoError); + // Prepare for second call for initializing crypto session and cas license. + auto second_license_pass_through = make_unique(); + StrictMockLicense* second_license = second_license_pass_through.get(); + cas_api.setCasLicense(std::move(second_license_pass_through)); + auto second_crypto_pass_through = make_unique(); + StrictMockCryptoSession* second_crypto = second_crypto_pass_through.get(); + cas_api.setCryptoSession(std::move(second_crypto_pass_through)); + EXPECT_CALL(*second_crypto, initialize()) + .WillOnce(Return(wvcas::CasStatus::OkStatus())); + EXPECT_CALL(*second_license, GenerateEntitlementRequest) + .WillOnce(Return(wvcas::CasStatus::OkStatus())); + EXPECT_CALL(event_listener_, OnEntitlementPeriodUpdateNeeded).Times(1); + std::string init_data; + + EXPECT_EQ( + cas_api.generateEntitlementPeriodUpdateRequest(init_data).status_code(), + wvcas::CasStatusCode::kNoError); +} + +TEST_F(WidevineCasTest, + handleEntitlementPeriodUpdateResponseWithoutRequestFail) { + TestWidevineCas cas_api; + EXPECT_CALL(*cas_api.crypto_session_, initialize()) + .WillOnce(Return(wvcas::CasStatus::OkStatus())); + EXPECT_EQ(cas_api.initialize(nullptr).status_code(), + wvcas::CasStatusCode::kNoError); + std::string response, license_id; + + EXPECT_EQ(cas_api.handleEntitlementPeriodUpdateResponse(response, license_id) + .status_code(), + wvcas::CasStatusCode::kInvalidParameter); +} + +TEST_F(WidevineCasTest, handleEntitlementPeriodUpdateResponseSuccess) { + TestWidevineCas cas_api; + auto first_license_pass_through = make_unique(); + cas_api.setCasLicense(std::move(first_license_pass_through)); + auto first_crypto_pass_through = make_unique(); + StrictMockCryptoSession* first_crypto = first_crypto_pass_through.get(); + cas_api.setCryptoSession(std::move(first_crypto_pass_through)); + EXPECT_CALL(*first_crypto, initialize()) + .WillOnce(Return(wvcas::CasStatus::OkStatus())); + EXPECT_EQ(cas_api.initialize(&event_listener_).status_code(), + wvcas::CasStatusCode::kNoError); + // Set up two sessions. + wvcas::WvCasSessionId session_id_1; + wvcas::WvCasSessionId session_id_2; + EXPECT_CALL(*first_crypto, CreateEntitledKeySession(NotNull())) + .Times(2) + .WillRepeatedly(Return(wvcas::CasStatus::OkStatus())); + EXPECT_CALL(*(cas_api.crypto_session_), GetOEMKeyToken(_, _)) + .WillOnce([](OEMCrypto_SESSION entitled_key_session_id, + std::vector& token) { + token = {0x03}; + return CasStatus::OkStatus(); + }) + .WillOnce([](OEMCrypto_SESSION entitled_key_session_id, + std::vector& token) { + token = {0x04}; + return CasStatus::OkStatus(); + }); + EXPECT_EQ(cas_api.openSession(&session_id_1).status_code(), + wvcas::CasStatusCode::kNoError); + EXPECT_EQ(cas_api.openSession(&session_id_2).status_code(), + wvcas::CasStatusCode::kNoError); + // Prepare for second call for initializing crypto session and cas license. + auto second_license_pass_through = make_unique(); + StrictMockLicense* second_license = second_license_pass_through.get(); + cas_api.setCasLicense(std::move(second_license_pass_through)); + auto second_crypto_pass_through = make_unique(); + StrictMockCryptoSession* second_crypto = second_crypto_pass_through.get(); + cas_api.setCryptoSession(std::move(second_crypto_pass_through)); + EXPECT_CALL(*second_crypto, initialize()) + .WillOnce(Return(wvcas::CasStatus::OkStatus())); + EXPECT_CALL(*second_license, GenerateEntitlementRequest) + .WillOnce(Return(wvcas::CasStatus::OkStatus())); + EXPECT_CALL(event_listener_, OnEntitlementPeriodUpdateNeeded).Times(1); + std::string init_data; + // Generate switch request. + EXPECT_EQ( + cas_api.generateEntitlementPeriodUpdateRequest(init_data).status_code(), + wvcas::CasStatusCode::kNoError); + std::string response, license_id; + EXPECT_CALL(*second_license, HandleEntitlementResponse) + .WillOnce(Return(wvcas::CasStatus::OkStatus())); + // Sessions will be reassociated. + EXPECT_CALL(*second_crypto, ReassociateEntitledKeySession) + .Times(2) + .WillRepeatedly(Return(wvcas::CasStatus::OkStatus())); + EXPECT_CALL(*first_crypto, close) + .WillOnce(Return(wvcas::CasStatus::OkStatus())); + + EXPECT_EQ(cas_api.handleEntitlementPeriodUpdateResponse(response, license_id) + .status_code(), + wvcas::CasStatusCode::kNoError); + + EXPECT_CALL(*(cas_api.crypto_session_), RemoveEntitledKeySession).Times(2); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + cas_api.closeSession(session_id_1).status_code()); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + cas_api.closeSession(session_id_2).status_code()); +} + +} // namespace +} // namespace wvcas diff --git a/tests/src/widevine_cas_session_test.cpp b/tests/src/widevine_cas_session_test.cpp new file mode 100644 index 0000000..e75ed1c --- /dev/null +++ b/tests/src/widevine_cas_session_test.cpp @@ -0,0 +1,436 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#include "widevine_cas_session.h" + +#include +#include +#include + +#include +#include +#include + +#include "cas_types.h" +#include "cas_util.h" +#include "media_cas.pb.h" +#include "mock_crypto_session.h" +#include "mock_ecm_parser.h" +#include "mock_event_listener.h" +#include "string_conversions.h" + +namespace wvcas { +namespace { +using ::testing::_; +using ::testing::DoAll; +using ::testing::Eq; +using ::testing::Invoke; +using ::testing::NiceMock; +using ::testing::NotNull; +using ::testing::Return; +using ::testing::SetArgPointee; +using ::testing::StrictMock; + +static const char kEvenEntitlementKeyId[] = "even_entitlement_key_id"; +static const char kOddEntitlementKeyId[] = "odd_entitlement_key_id"; +static const char kEvenKeyId[] = "even_key_id"; +static const char kOddKeyId[] = "odd_key_id"; +static const char kEvenWrappedKey[] = "even_wrapped_content_key"; +static const char kOddWrappedKey[] = "odd_wrapped_content_key"; +static const char kEvenWrappedKeyIv[] = "even_wrapped_content_key_iv"; +static const char kOddWrappedKeyIv[] = "odd_wrapped_content_key_iv"; +static const char kEvenContentIv[] = "even_content_iv"; +static const char kOddContentIv[] = "odd_content_iv"; +static const OEMCrypto_SESSION kEntitledKeySessionId = 0x1111; +constexpr char kEmptyGroupId[] = ""; + +MATCHER(IsValidKeyEvenSlotData, "") { + if (nullptr == arg) { + *result_listener << " keyslot is nullptr"; + return false; + } + std::string value = kEvenEntitlementKeyId; + if (arg->entitlement_key_id != + std::vector(value.begin(), value.end())) { + *result_listener << " entitlement key_id is invalid"; + return false; + } + value = kEvenKeyId; + if (arg->key_id != std::vector(value.begin(), value.end())) { + *result_listener << " key_id is invalid"; + return false; + } + value = kEvenWrappedKey; + if (arg->wrapped_key != std::vector(value.begin(), value.end())) { + *result_listener << " wrapped_key is invalid"; + return false; + } + value = kEvenWrappedKeyIv; + if (arg->wrapped_key_iv != std::vector(value.begin(), value.end())) { + *result_listener << " wapped_key_iv is invalid"; + return false; + } + value = kEvenContentIv; + if (arg->content_iv != std::vector(value.begin(), value.end())) { + *result_listener << " content_iv is invalid"; + return false; + } + + return true; +} + +MATCHER(IsValidKeyOddSlotData, "") { + if (nullptr == arg) { + *result_listener << " keyslot is nullptr"; + return false; + } + std::string value = kOddEntitlementKeyId; + if (arg->entitlement_key_id != + std::vector(value.begin(), value.end())) { + *result_listener << " entitlement key_id is invalid"; + return false; + } + value = kOddKeyId; + if (arg->key_id != std::vector(value.begin(), value.end())) { + *result_listener << " key_id is invalid"; + return false; + } + value = kOddWrappedKey; + if (arg->wrapped_key != std::vector(value.begin(), value.end())) { + *result_listener << " wrapped_key is invalid"; + return false; + } + value = kOddWrappedKeyIv; + if (arg->wrapped_key_iv != std::vector(value.begin(), value.end())) { + *result_listener << " wapped_key_iv is invalid"; + return false; + } + value = kOddContentIv; + if (arg->content_iv != std::vector(value.begin(), value.end())) { + *result_listener << " content_iv is invalid"; + return false; + } + + return true; +} + +class CasSessionTest : public ::testing::Test { + public: + CasSessionTest() {} + virtual ~CasSessionTest() {} + + void SetUp() override { + mock_crypto_session_ = std::make_shared(); + ON_CALL(*mock_crypto_session_, CreateEntitledKeySession(NotNull())) + .WillByDefault(DoAll(SetArgPointee<0>(kEntitledKeySessionId), + Return(wvcas::CasStatusCode::kNoError))); + ON_CALL(*mock_crypto_session_, GetOEMKeyToken(kEntitledKeySessionId, _)) + .WillByDefault([&](OEMCrypto_SESSION, std::vector& token) { + token.assign(expected_session_id_.begin(), + expected_session_id_.end()); + return CasStatusCode::kNoError; + }); + } + + std::shared_ptr mock_crypto_session_; + WvCasSessionId expected_session_id_ = {0x01, 0x02, 0x03}; +}; + +// Allow getEcmParser to return a mocked ecm. +class TestCasSession : public wvcas::WidevineCasSession { + public: + TestCasSession() {} + virtual ~TestCasSession() {} + + std::unique_ptr getEcmParser( + const wvcas::CasEcm& ecm) const override; + + std::vector entitlement_key_id(wvcas::KeySlotId id) const { + std::string key_id; + if (id == wvcas::KeySlotId::kEvenKeySlot) { + key_id = kEvenEntitlementKeyId; + } else if (id == wvcas::KeySlotId::kOddKeySlot) { + key_id = kOddEntitlementKeyId; + } + return std::vector(key_id.begin(), key_id.end()); + } + + std::vector content_key_id(wvcas::KeySlotId id) const { + std::string key_id; + if (id == wvcas::KeySlotId::kEvenKeySlot) { + key_id = kEvenKeyId; + } else if (id == wvcas::KeySlotId::kOddKeySlot) { + key_id = kOddKeyId; + } + return std::vector(key_id.begin(), key_id.end()); + } + + std::vector wrapped_key_data(wvcas::KeySlotId id) const { + std::string key; + if (id == wvcas::KeySlotId::kEvenKeySlot) { + key = kEvenWrappedKey; + } else if (id == wvcas::KeySlotId::kOddKeySlot) { + key = kOddWrappedKey; + } + return std::vector(key.begin(), key.end()); + } + + std::vector wrapped_key_iv(wvcas::KeySlotId id) const { + std::string iv; + if (id == wvcas::KeySlotId::kEvenKeySlot) { + iv = kEvenWrappedKeyIv; + } else if (id == wvcas::KeySlotId::kOddKeySlot) { + iv = kOddWrappedKeyIv; + } + return std::vector(iv.begin(), iv.end()); + } + + std::vector content_iv(wvcas::KeySlotId id) const { + std::string iv; + if (id == wvcas::KeySlotId::kEvenKeySlot) { + iv = kEvenContentIv; + } else if (id == wvcas::KeySlotId::kOddKeySlot) { + iv = kOddContentIv; + } + return std::vector(iv.begin(), iv.end()); + } + + void set_age_restriction(uint8_t age_restriction) { + age_restriction_ = age_restriction; + } + + void set_fingerprinting_control(const std::string& control) { + fingerprinting_.clear_control(); + if (!control.empty()) { + fingerprinting_.set_control(control); + } + } + + void set_service_blocking_groups(const std::vector& groups) { + service_blocking_.clear_device_groups(); + for (auto const& group : groups) { + service_blocking_.add_device_groups(group); + } + } + + private: + uint8_t age_restriction_ = 0; + video_widevine::Fingerprinting fingerprinting_; + video_widevine::ServiceBlocking service_blocking_; +}; + +std::unique_ptr TestCasSession::getEcmParser( + const wvcas::CasEcm& ecm) const { + std::unique_ptr> mock_ecm_parser( + new NiceMock); + ON_CALL(*mock_ecm_parser, age_restriction()) + .WillByDefault(Return(age_restriction_)); + ON_CALL(*mock_ecm_parser, crypto_mode()) + .WillByDefault(Return(wvcas::CryptoMode::kAesCTR)); + ON_CALL(*mock_ecm_parser, rotation_enabled()).WillByDefault(Return(true)); + ON_CALL(*mock_ecm_parser, entitlement_key_id(_)) + .WillByDefault(Invoke(this, &TestCasSession::entitlement_key_id)); + ON_CALL(*mock_ecm_parser, content_key_id(_)) + .WillByDefault(Invoke(this, &TestCasSession::content_key_id)); + ON_CALL(*mock_ecm_parser, wrapped_key_data(_)) + .WillByDefault(Invoke(this, &TestCasSession::wrapped_key_data)); + ON_CALL(*mock_ecm_parser, wrapped_key_iv(_)) + .WillByDefault(Invoke(this, &TestCasSession::wrapped_key_iv)); + ON_CALL(*mock_ecm_parser, content_iv(_)) + .WillByDefault(Invoke(this, &TestCasSession::content_iv)); + ON_CALL(*mock_ecm_parser, set_group_id(_)).WillByDefault(Return(true)); + ON_CALL(*mock_ecm_parser, has_fingerprinting()) + .WillByDefault(Return(fingerprinting_.has_control())); + ON_CALL(*mock_ecm_parser, fingerprinting()) + .WillByDefault(Return(fingerprinting_)); + ON_CALL(*mock_ecm_parser, has_service_blocking()) + .WillByDefault(Return(service_blocking_.device_groups_size() > 0)); + ON_CALL(*mock_ecm_parser, service_blocking()) + .WillByDefault(Return(service_blocking_)); + return std::unique_ptr(mock_ecm_parser.release()); +} + +TEST_F(CasSessionTest, sessionInitializeTest) { + TestCasSession session; + WvCasSessionId session_id; + + ASSERT_EQ(session + .initialize(mock_crypto_session_, /*event_listener=*/nullptr, + &session_id) + .status_code(), + CasStatusCode::kNoError); + + EXPECT_EQ(session_id, expected_session_id_); +} + +TEST_F(CasSessionTest, processEcm) { + TestCasSession session; + WvCasSessionId session_id; + ASSERT_EQ(session + .initialize(mock_crypto_session_, /*event_listener=*/nullptr, + &session_id) + .status_code(), + CasStatusCode::kNoError); + + wvcas::CasEcm ecm(184); + EXPECT_CALL(*mock_crypto_session_, + LoadCasECMKeys(kEntitledKeySessionId, IsValidKeyEvenSlotData(), + IsValidKeyOddSlotData())); + session.processEcm(ecm, 0, kEmptyGroupId); + EXPECT_CALL(*mock_crypto_session_, + RemoveEntitledKeySession(kEntitledKeySessionId)); +} + +TEST_F(CasSessionTest, parentalControl) { + TestCasSession session; + WvCasSessionId session_id; + ASSERT_EQ(session + .initialize(mock_crypto_session_, /*event_listener=*/nullptr, + &session_id) + .status_code(), + CasStatusCode::kNoError); + + EXPECT_CALL(*mock_crypto_session_, LoadCasECMKeys(_, IsValidKeyEvenSlotData(), + IsValidKeyOddSlotData())); + wvcas::CasEcm ecm(184); + session.set_age_restriction(0); // No restriction. + // Different Ecm to make sure processEcm() processes this ecm. + std::generate(ecm.begin(), ecm.end(), std::rand); + ASSERT_EQ(wvcas::CasStatusCode::kNoError, + session.processEcm(ecm, 0, kEmptyGroupId).status_code()); + std::generate(ecm.begin(), ecm.end(), std::rand); + ASSERT_EQ(wvcas::CasStatusCode::kNoError, + session.processEcm(ecm, 13, kEmptyGroupId).status_code()); + + // Parental control age must >= 10 (if non-zero). + session.set_age_restriction(10); + std::generate(ecm.begin(), ecm.end(), std::rand); + ASSERT_EQ(wvcas::CasStatusCode::kNoError, + session.processEcm(ecm, 0, kEmptyGroupId).status_code()); + std::generate(ecm.begin(), ecm.end(), std::rand); + ASSERT_EQ(wvcas::CasStatusCode::kNoError, + session.processEcm(ecm, 10, kEmptyGroupId).status_code()); + std::generate(ecm.begin(), ecm.end(), std::rand); + ASSERT_EQ(wvcas::CasStatusCode::kNoError, + session.processEcm(ecm, 13, kEmptyGroupId).status_code()); + std::generate(ecm.begin(), ecm.end(), std::rand); + ASSERT_EQ(wvcas::CasStatusCode::kAccessDeniedByParentalControl, + session.processEcm(ecm, 3, kEmptyGroupId).status_code()); + EXPECT_CALL(*mock_crypto_session_, RemoveEntitledKeySession(_)); +} + +TEST_F(CasSessionTest, FingerprintingSuccess) { + TestCasSession session; + auto mock_crypto = std::make_shared(); + MockEventListener mock_listener; + WvCasSessionId session_id; + ASSERT_EQ(session.initialize(mock_crypto, &mock_listener, &session_id) + .status_code(), + wvcas::CasStatusCode::kNoError); + session.set_fingerprinting_control("control"); + std::vector expected_message = {0x00, 0x00, 0x07, 'c', 'o', + 'n', 't', 'r', 'o', 'l'}; + EXPECT_CALL(mock_listener, + OnSessionFingerprintingUpdated(session_id, expected_message)) + .Times(1); + + session.processEcm(wvcas::CasEcm(184, '0'), 0, kEmptyGroupId); +} + +TEST_F(CasSessionTest, RepeatedFingerprintingNoEventSuccess) { + TestCasSession session; + auto mock_crypto = std::make_shared(); + MockEventListener mock_listener; + WvCasSessionId session_id; + ASSERT_EQ(session.initialize(mock_crypto, &mock_listener, &session_id) + .status_code(), + wvcas::CasStatusCode::kNoError); + session.set_fingerprinting_control("control"); + EXPECT_CALL(mock_listener, OnSessionFingerprintingUpdated).Times(1); + + session.processEcm(wvcas::CasEcm(184, '0'), 0, kEmptyGroupId); + // Same fingerprinting will not trigger event. + EXPECT_CALL(mock_listener, OnSessionFingerprintingUpdated).Times(0); + session.processEcm(wvcas::CasEcm(184, '1'), 0, kEmptyGroupId); +} + +TEST_F(CasSessionTest, DifferentFingerprintingTriggerEventSuccess) { + TestCasSession session; + auto mock_crypto = std::make_shared(); + MockEventListener mock_listener; + WvCasSessionId session_id; + ASSERT_EQ(session.initialize(mock_crypto, &mock_listener, &session_id) + .status_code(), + wvcas::CasStatusCode::kNoError); + session.set_fingerprinting_control("control"); + EXPECT_CALL(mock_listener, OnSessionFingerprintingUpdated).Times(1); + + session.processEcm(wvcas::CasEcm(184, '0'), 0, kEmptyGroupId); + // Different fingerprinting will trigger event. + session.set_fingerprinting_control("control2"); + EXPECT_CALL(mock_listener, OnSessionFingerprintingUpdated).Times(1); + session.processEcm(wvcas::CasEcm(184, '1'), 0, kEmptyGroupId); + // Different fingerprinting (including empty) will trigger event. + session.set_fingerprinting_control(""); + EXPECT_CALL(mock_listener, OnSessionFingerprintingUpdated).Times(1); + session.processEcm(wvcas::CasEcm(184, '2'), 0, kEmptyGroupId); +} + +TEST_F(CasSessionTest, ServiceBlockingSuccess) { + TestCasSession session; + auto mock_crypto = std::make_shared(); + MockEventListener mock_listener; + WvCasSessionId session_id; + ASSERT_EQ(session.initialize(mock_crypto, &mock_listener, &session_id) + .status_code(), + wvcas::CasStatusCode::kNoError); + session.set_service_blocking_groups({"Group1", "g2"}); + std::vector expected_message = {0x00, 0x00, 0x06, 'G', 'r', + 'o', 'u', 'p', '1', 0x00, + 0x00, 0x02, 'g', '2'}; + EXPECT_CALL(mock_listener, + OnSessionServiceBlockingUpdated(session_id, expected_message)) + .Times(1); + + session.processEcm(wvcas::CasEcm(184, '0'), 0, kEmptyGroupId); +} + +TEST_F(CasSessionTest, RepeatedServiceBlockingNoEventSuccess) { + TestCasSession session; + auto mock_crypto = std::make_shared(); + MockEventListener mock_listener; + WvCasSessionId session_id; + ASSERT_EQ(session.initialize(mock_crypto, &mock_listener, &session_id) + .status_code(), + wvcas::CasStatusCode::kNoError); + session.set_service_blocking_groups({"Group1", "g2"}); + EXPECT_CALL(mock_listener, OnSessionServiceBlockingUpdated).Times(1); + + session.processEcm(wvcas::CasEcm(184, '0'), 0, kEmptyGroupId); + EXPECT_CALL(mock_listener, OnSessionServiceBlockingUpdated).Times(0); + session.processEcm(wvcas::CasEcm(184, '1'), 0, kEmptyGroupId); +} + +TEST_F(CasSessionTest, DifferentServiceBlockingTriggerEventSuccess) { + TestCasSession session; + auto mock_crypto = std::make_shared(); + MockEventListener mock_listener; + WvCasSessionId session_id; + ASSERT_EQ(session.initialize(mock_crypto, &mock_listener, &session_id) + .status_code(), + wvcas::CasStatusCode::kNoError); + session.set_service_blocking_groups({"Group1", "g2"}); + EXPECT_CALL(mock_listener, OnSessionServiceBlockingUpdated).Times(1); + + session.processEcm(wvcas::CasEcm(184, '0'), 0, kEmptyGroupId); + EXPECT_CALL(mock_listener, OnSessionServiceBlockingUpdated).Times(1); + session.set_service_blocking_groups({"Group1"}); + session.processEcm(wvcas::CasEcm(184, '1'), 0, kEmptyGroupId); + EXPECT_CALL(mock_listener, OnSessionServiceBlockingUpdated).Times(1); + session.set_service_blocking_groups({}); + session.processEcm(wvcas::CasEcm(184, '2'), 0, kEmptyGroupId); +} + +} // namespace +} // namespace wvcas diff --git a/tests/src/widevine_media_cas_plugin_test.cpp b/tests/src/widevine_media_cas_plugin_test.cpp new file mode 100644 index 0000000..29a9aab --- /dev/null +++ b/tests/src/widevine_media_cas_plugin_test.cpp @@ -0,0 +1,227 @@ +// Copyright 2021 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#include "widevine_media_cas_plugin.h" + +#include +#include +#include + +#include "cas_events.h" +#include "cas_status.h" +#include "media/cas/CasAPI.h" +#include "media/stagefright/MediaErrors.h" +#include "widevine_cas_api.h" + +namespace android { +// Minimalist implementation of Android string class to support test. +std::map > string8s; + +String8::String8(const String8& value) + : String8(value.c_str(), value.length()) {} + +String8::String8(char const* data, size_t data_length) { + auto result = + string8s.emplace(this, make_unique(data, data_length)); + mString = result.first->second->data(); +} + +size_t String8::length() const { + auto it = string8s.find(this); + return it == string8s.end() ? 0 : it->second->size(); +} + +String8::~String8() { string8s.erase(this); } + +} // namespace android + +namespace wvcas { +namespace { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::Eq; +using ::testing::IsNull; +using ::testing::NotNull; +using ::testing::Return; +using ::testing::SetArgPointee; +using ::testing::SetArgReferee; + +class MockWidevineCas : public WidevineCas { + public: + MockWidevineCas() {} + ~MockWidevineCas() override {} + + MOCK_METHOD(CasStatus, openSession, (WvCasSessionId * sessionId), (override)); + MOCK_METHOD(bool, is_provisioned, (), (const, override)); + MOCK_METHOD(CasStatus, generateEntitlementRequest, + (const std::string& init_data, std::string* entitlement_request, + std::string& license_id), + (override)); + MOCK_METHOD(CasStatus, RecordLicenseId, (const std::string& license_id), + (override)); + MOCK_METHOD(CasStatus, handleEntitlementResponse, + (const std::string& response, std::string& license_id, + std::string& multi_content_license_info, + std::string& group_license_info), + (override)); +}; + +// Override WidevineCasPlugin to set WidevineCas and mock callbacks. +class TestWidevineCasPlugin : public WidevineCasPlugin { + public: + TestWidevineCasPlugin() : WidevineCasPlugin() {} + ~TestWidevineCasPlugin() override {} + + void SetWidevineCasApi( + std::unique_ptr widevine_cas_api) override { + WidevineCasPlugin::SetWidevineCasApi(std::move(widevine_cas_api)); + } + + MOCK_METHOD(void, CallBack, + (void* appData, int32_t event, int32_t arg, uint8_t* data, + size_t size, const CasSessionId* sessionId), + (const, override)); +}; + +TEST(WidevineCasPluginTest, openSessionSuccess) { + TestWidevineCasPlugin plugin; + auto pass_through_cas_api = make_unique(); + MockWidevineCas* cas_api = pass_through_cas_api.get(); + plugin.SetWidevineCasApi(std::move(pass_through_cas_api)); + const std::vector expected_android_session_id = {0x78, 0x56, 0x34, + 0x12}; + const int32_t created_session_id = 0x12345678; + EXPECT_CALL(*cas_api, is_provisioned).WillOnce(Return(true)); + EXPECT_CALL(*cas_api, openSession(NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(expected_android_session_id), + Return(CasStatus::OkStatus()))); + EXPECT_CALL(plugin, CallBack(_, CAS_SESSION_ID, created_session_id, NotNull(), + expected_android_session_id.size(), IsNull())); + std::vector session_id; + + EXPECT_EQ(plugin.openSession(&session_id), android::OK); + + EXPECT_EQ(session_id, expected_android_session_id); +} + +TEST(WidevineCasPluginTest, openSessionWithoutProvisionFail) { + TestWidevineCasPlugin plugin; + auto pass_through_cas_api = make_unique(); + MockWidevineCas* cas_api = pass_through_cas_api.get(); + plugin.SetWidevineCasApi(std::move(pass_through_cas_api)); + EXPECT_CALL(*cas_api, is_provisioned).WillOnce(Return(false)); + EXPECT_CALL(*cas_api, openSession(NotNull())).Times((0)); + std::vector session_id; + + EXPECT_EQ(plugin.openSession(&session_id), + android::ERROR_CAS_NOT_PROVISIONED); +} + +TEST(WidevineCasPluginTest, + provisionWithProvisionStringAlreadyProvisionedSuccess) { + TestWidevineCasPlugin plugin; + auto pass_through_cas_api = make_unique(); + MockWidevineCas* cas_api = pass_through_cas_api.get(); + plugin.SetWidevineCasApi(std::move(pass_through_cas_api)); + const std::string provision_string = "init_data"; + EXPECT_CALL(*cas_api, is_provisioned).WillOnce(Return(true)); + EXPECT_CALL(plugin, CallBack(_, INDIVIDUALIZATION_COMPLETE, _, _, _, _)); + // Provision string is init data; it triggers license request. + EXPECT_CALL(*cas_api, + generateEntitlementRequest(Eq(provision_string), NotNull(), _)) + .WillOnce(DoAll(SetArgPointee<1>("signed_license_request"), + Return(CasStatus::OkStatus()))); + EXPECT_CALL(plugin, CallBack(_, LICENSE_REQUEST, _, _, _, _)); + + EXPECT_EQ(plugin.provision(android::String8(provision_string.c_str(), + provision_string.size())), + android::OK); +} + +TEST(WidevineCasPluginTest, HandleAssignLicenseIDSuccess) { + TestWidevineCasPlugin plugin; + auto pass_through_cas_api = make_unique(); + MockWidevineCas* cas_api = pass_through_cas_api.get(); + plugin.SetWidevineCasApi(std::move(pass_through_cas_api)); + const std::string license_id = "license_id"; + EXPECT_CALL(*cas_api, RecordLicenseId(license_id)) + .WillOnce(Return(CasStatus::OkStatus())); + EXPECT_CALL(plugin, CallBack(_, LICENSE_ID_ASSIGNED, _, _, _, _)).Times(1); + + EXPECT_EQ(plugin.sendEvent(ASSIGN_LICENSE_ID, /*arg=*/0, + {license_id.begin(), license_id.end()}), + android::OK); +} + +TEST(WidevineCasPluginTest, HandleAssignLicenseIDApiError) { + TestWidevineCasPlugin plugin; + auto pass_through_cas_api = make_unique(); + MockWidevineCas* cas_api = pass_through_cas_api.get(); + plugin.SetWidevineCasApi(std::move(pass_through_cas_api)); + const std::string license_id = "license_id"; + EXPECT_CALL(*cas_api, RecordLicenseId) + .WillOnce(Return(CasStatus(CasStatusCode::kInvalidParameter, "invalid"))); + EXPECT_CALL(plugin, CallBack(_, CAS_ERROR, _, _, _, _)).Times(1); + + EXPECT_NE(plugin.sendEvent(ASSIGN_LICENSE_ID, /*arg=*/0, + {license_id.begin(), license_id.end()}), + android::OK); +} + +TEST(WidevineCasPluginTest, HandleEntitlementResponseSuccess) { + TestWidevineCasPlugin plugin; + auto pass_through_cas_api = make_unique(); + MockWidevineCas* cas_api = pass_through_cas_api.get(); + plugin.SetWidevineCasApi(std::move(pass_through_cas_api)); + const std::string license = "license"; + const std::string license_id = "id"; + const std::string multi_content_license_info = "info"; + const std::string group_license_info = "info2"; + EXPECT_CALL(*cas_api, handleEntitlementResponse(_, _, _, _)) + .WillOnce(DoAll(SetArgReferee<1>(license_id), + SetArgReferee<2>(multi_content_license_info), + SetArgReferee<3>(group_license_info), + Return(CasStatus::OkStatus()))); + EXPECT_CALL(plugin, + CallBack(_, LICENSE_CAS_READY, _, _, license_id.size(), _)) + .Times(1); + EXPECT_CALL(plugin, CallBack(_, MULTI_CONTENT_LICENSE_INFO, _, _, + multi_content_license_info.size(), _)) + .Times(1); + EXPECT_CALL(plugin, CallBack(_, GROUP_LICENSE_INFO, _, _, + group_license_info.size(), _)) + .Times(1); + + EXPECT_EQ(plugin.sendEvent(LICENSE_RESPONSE, /*arg=*/0, + {license.begin(), license.end()}), + android::OK); +} + +TEST(WidevineCasPluginTest, HandleEntitlementResponseEmptyResponseFail) { + TestWidevineCasPlugin plugin; + auto pass_through_cas_api = make_unique(); + plugin.SetWidevineCasApi(std::move(pass_through_cas_api)); + EXPECT_CALL(plugin, CallBack(_, CAS_ERROR, _, _, _, _)).Times(1); + + EXPECT_NE(plugin.sendEvent(LICENSE_RESPONSE, /*arg=*/0, /*eventData=*/{}), + android::OK); +} + +TEST(WidevineCasPluginTest, HandlePluginVersionQuerySuccess) { + TestWidevineCasPlugin plugin; + auto pass_through_cas_api = make_unique(); + plugin.SetWidevineCasApi(std::move(pass_through_cas_api)); + std::string expected_version = "uint-test"; + EXPECT_CALL(plugin, CallBack(_, WV_CAS_PLUGIN_VERSION, _, NotNull(), + expected_version.size(), _)) + .Times(1); + + EXPECT_EQ(plugin.sendEvent(QUERY_WV_CAS_PLUGIN_VERSION, /*arg=*/0, + /*eventData=*/{}), + android::OK); +} + +} // namespace +} // namespace wvcas \ No newline at end of file diff --git a/tests/src/wv_cas_test_main.cpp b/tests/src/wv_cas_test_main.cpp new file mode 100644 index 0000000..f971bee --- /dev/null +++ b/tests/src/wv_cas_test_main.cpp @@ -0,0 +1,20 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#include + +#include + +#include "OEMCryptoCENC.h" +#include "log.h" + +namespace wvutil { +extern LogPriority g_cutoff; +} // namespace wvutil + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + wvutil::g_cutoff = wvutil::CDM_LOG_INFO; + return RUN_ALL_TESTS(); +} diff --git a/wvutil/Android.bp b/wvutil/Android.bp new file mode 100644 index 0000000..6b2541c --- /dev/null +++ b/wvutil/Android.bp @@ -0,0 +1,31 @@ +// Builds libcasutil.a +cc_library_static { + + name: "libcasutil", + + proprietary: true, + + local_include_dirs: [ + "include", + ], + + srcs: [ + "src/clock.cpp", + "src/log.cpp", + "src/file_store.cpp", + "src/file_utils.cpp", + "src/rw_lock.cpp", + "src/string_conversions.cpp", + "src/android_properties.cpp", + "src/timer.cpp", + ], + + shared_libs: [ + "liblog", + "libutils", + "libcrypto", + "libhidlbase", + ], + + export_include_dirs: ["include"], +} diff --git a/wvutil/include/advance_iv_ctr.h b/wvutil/include/advance_iv_ctr.h new file mode 100644 index 0000000..a8a6c60 --- /dev/null +++ b/wvutil/include/advance_iv_ctr.h @@ -0,0 +1,46 @@ +// Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. + +#ifndef WVCDM_UTIL_ADVANCE_IV_CTR_H_ +#define WVCDM_UTIL_ADVANCE_IV_CTR_H_ + +#include +#include + +#include "string_conversions.h" + +namespace wvutil { + +// Advance an IV according to ISO-CENC's CTR modes. The lower half of the IV is +// split off and treated as an unsigned 64-bit integer, then incremented by the +// number of complete crypto blocks decrypted. The resulting value is then +// copied back into the IV over the previous lower half. +inline void AdvanceIvCtr(uint8_t (*subsample_iv)[16], size_t bytes) { + constexpr size_t kAesBlockSize = 16; + constexpr size_t kIvSize = kAesBlockSize; + constexpr size_t kCounterIndex = kIvSize / 2; + constexpr size_t kCounterSize = kIvSize / 2; + + uint64_t counter; + + static_assert( + sizeof(*subsample_iv) == kIvSize, + "The subsample_iv field is no longer the length of an AES-128 IV."); + static_assert(sizeof(counter) == kCounterSize, + "A uint64_t failed to be half the size of an AES-128 IV."); + + // Defensive copy because the elements of the array may not be properly + // aligned + memcpy(&counter, &(*subsample_iv)[kCounterIndex], kCounterSize); + + const size_t increment = + bytes / kAesBlockSize; // The truncation here is intentional + counter = htonll64(ntohll64(counter) + increment); + + memcpy(&(*subsample_iv)[kCounterIndex], &counter, kCounterSize); +} + +} // namespace wvutil + +#endif // WVCDM_UTIL_ADVANCE_IV_CTR_H_ diff --git a/wvutil/include/arraysize.h b/wvutil/include/arraysize.h new file mode 100644 index 0000000..0750fc0 --- /dev/null +++ b/wvutil/include/arraysize.h @@ -0,0 +1,20 @@ +// Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. + +#ifndef WVCDM_UTIL_ARRAYSIZE_H_ +#define WVCDM_UTIL_ARRAYSIZE_H_ + +#include + +namespace wvutil { + +// Returns the size of a fixed-length array. +template +constexpr size_t ArraySize(const T (&)[N]) { + return N; +} + +} // namespace wvutil + +#endif // WVCDM_UTIL_ARRAYSIZE_H_ diff --git a/wvutil/include/cas_properties.h b/wvutil/include/cas_properties.h new file mode 100644 index 0000000..202f199 --- /dev/null +++ b/wvutil/include/cas_properties.h @@ -0,0 +1,46 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#ifndef CAS_PROPERTIES_H +#define CAS_PROPERTIES_H + +#include + +namespace wvcas { + +// Properties methods must be implemented for a platform. The values returned +// describe the capabilities and configuration of a device using Widevine CAS. +class Properties { + private: + Properties(); // Not implemented + ~Properties(); // NotImplemented + public: + // Sets the |company_name| field value to be populated in and EMM license + // request. Returns false if unable to set the value. + static bool GetCompanyName(std::string* company_name); + // Sets the |model_name| field value to be populated in and EMM license + // request. Returns false if unable to set the value. + static bool GetModelName(std::string* model_name); + // Sets the |product_name| field value to be populated in and EMM license + // request. Returns false if unable to set the value. + static bool GetProductName(std::string* product_name); + // Sets the |arch_name| field value to be populated in and EMM license + // request. Returns false if unable to set the value. + static bool GetArchitectureName(std::string* arch_name); + // Sets the |device_name| field value to be populated in and EMM license + // request. Returns false if unable to set the value. + static bool GetDeviceName(std::string* device_name); + // Returns a path to CAS oemcrypto library, either default, + // or overridden through system property. + // Returned path could be either absolute or relative. + // Returns false if unable to set the value. + static bool GetOEMCryptoPath(std::string* path); + // Sets |version| to Widevine CAS plugin version. Returns false if unable to + // set the value. + static bool GetWvCasPluginVersion(std::string& version); +}; + +} // namespace wvcas + +#endif // CAS_PROPERTIES_H diff --git a/wvutil/include/cdm_random.h b/wvutil/include/cdm_random.h new file mode 100644 index 0000000..ba2f2f5 --- /dev/null +++ b/wvutil/include/cdm_random.h @@ -0,0 +1,117 @@ +// Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +#ifndef WVCDM_CORE_CDM_RANDOM_H_ +#define WVCDM_CORE_CDM_RANDOM_H_ + +#include +#include +#include + +namespace wvutil { + +// CdmRandomGenerator is a thread safe, pseudo-random number generator. +// It's purpose is to simplified interface for C++11's library. +// Some of the methods use a "device specific" random seed, if the +// compiler/device does not support device specific randomizers, then the +// actual value supplied may not be random. The generator is designed to +// meet the C++ named requirement UniformRandomBitGenerator to allow it to +// be used with standard library functions / class which are designed to +// work with the standard library generators. +class CdmRandomGenerator { + public: + // Result type of operator(). + using result_type = unsigned int; + // Inclusive boundaries of operator(). + static constexpr unsigned int min() { return 0; } + static constexpr unsigned int max() { return RAND_MAX; } + + // The maximum number of bytes that can be generated at once for + // `RandomData()`. + static constexpr size_t kMaxRandomDataLength = 8192; // 8 kB + + // Initializes the pseudo-random generator with a value from a device + // specific random number generator. + CdmRandomGenerator(); + + // Initializes the pseudo-random generator with the specified seed value. + explicit CdmRandomGenerator(unsigned int seed) : generator_(seed) {} + + // All of these methods are thread-safe. + + // Seeds the pseudo-random generator with a value from a device specific + // random number generator. + void Seed(); + + // Seeds the pseudo-random generator with the specified seed value. + // This is somewhat similar to `srand()` from the C standard library; + // except that the sequence generated from successive calls to `Rand()` + // will not necessarily be the same as they would be from the + // standard library `rand()`. This is due to the underlying pseudo-random + // generator that is used. + void Seed(unsigned int seed); + + // Returns a pseudo-random integer. + // This is similar to `rand()` from the C standard library. The integer + // returned is in the range of [min(), max()]. + unsigned int Rand(); + + // Allows for RNG to be callable. + unsigned int operator()() { return Rand(); } + + // Returns a pseudo-random integer within the provided inclusive range. + uint64_t RandomInRange(uint64_t lower, uint64_t upper); + uint64_t RandomInRange(uint64_t upper) { return RandomInRange(0, upper); } + + // Returns a byte string containing randomized bytes of the specified + // length. + // If |length| is greater than |CdmRandomGenerator::kMaxRandomDataLength|, + // then an error is logged and an empty string is returned. + std::string RandomData(size_t length); + + // Random true/false using Bernoulli distribution of equal probability. + bool RandomBool(); + + private: + // Mutex is used to lock the object, and allowing it to be used + // concurrently in different threads. + std::mutex generator_lock_; + + // The `default_random_engine` depends on the compiler used and + // potentially its version. This is important to know if you need to + // create reproducible tests between platforms. + std::default_random_engine generator_; +}; + +// Provides a static interface to a process-wide instance of +// CdmRandomGenerator. +class CdmRandom { + public: + static unsigned int Rand() { return GetInstance()->Rand(); } + static uint64_t RandomInRange(uint64_t lower, uint64_t upper) { + return GetInstance()->RandomInRange(lower, upper); + } + static uint64_t RandomInRange(uint64_t upper) { + return GetInstance()->RandomInRange(upper); + } + + static std::string RandomData(size_t length) { + return GetInstance()->RandomData(length); + } + + static bool RandomBool() { return GetInstance()->RandomBool(); } + + private: + // These are intended to be used by tests if needed. + static void Seed(unsigned int seed) { GetInstance()->Seed(seed); } + static void Seed() { GetInstance()->Seed(); } + + // Returns the process-wide instance of CdmRandomGenerator. + // It the global instance has not yet been created, then a new instance + // is created using a device-specific random seed. + static CdmRandomGenerator* GetInstance(); +}; + +} // namespace wvutil + +#endif // WVCDM_CORE_CDM_RANDOM_H_ diff --git a/wvutil/include/clock.h b/wvutil/include/clock.h new file mode 100644 index 0000000..98e7ab6 --- /dev/null +++ b/wvutil/include/clock.h @@ -0,0 +1,26 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +// +// Clock - Platform independent interface for a time library +// +#ifndef WVCDM_UTIL_CLOCK_H_ +#define WVCDM_UTIL_CLOCK_H_ + +#include + +namespace wvutil { + +// Provides time related information. The implementation is platform dependent. +class Clock { + public: + Clock() {} + virtual ~Clock() {} + + // Provides the number of seconds since an epoch - 01/01/1970 00:00 UTC + virtual int64_t GetCurrentTime(); +}; + +} // namespace wvutil + +#endif // WVCDM_UTIL_CLOCK_H_ diff --git a/wvutil/include/disallow_copy_and_assign.h b/wvutil/include/disallow_copy_and_assign.h new file mode 100644 index 0000000..451b72a --- /dev/null +++ b/wvutil/include/disallow_copy_and_assign.h @@ -0,0 +1,16 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. + +#ifndef WVCDM_UTIL_DISALLOW_COPY_AND_ASSIGN_H_ +#define WVCDM_UTIL_DISALLOW_COPY_AND_ASSIGN_H_ + +namespace wvutil { + +#define CORE_DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName&); \ + void operator=(const TypeName&) + +} // namespace wvutil + +#endif // WVCDM_UTIL_DISALLOW_COPY_AND_ASSIGN_H_ diff --git a/wvutil/include/file_store.h b/wvutil/include/file_store.h new file mode 100644 index 0000000..9563b5e --- /dev/null +++ b/wvutil/include/file_store.h @@ -0,0 +1,87 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +// +// File - Platform independent interface for a File class +// +#ifndef WVCDM_UTIL_FILE_STORE_H_ +#define WVCDM_UTIL_FILE_STORE_H_ + +#include +#include +#include +#include + +#include "disallow_copy_and_assign.h" +#include "platform.h" +#include "util_common.h" + +namespace wvutil { + +static const std::string kAtscCertificateFileName = "atsccert.bin"; +static const std::string kCertificateFileName = "cert1.bin"; +static const std::string kCertificateFileNameExt = ".bin"; +static const std::string kCertificateFileNamePrefix = "cert1_"; +static const std::string kLegacyCertificateFileName = "cert.bin"; +static const std::string kLegacyCertificateFileNamePrefix = "cert"; +static const std::string kOemCertificateFileName = "oemcert.bin"; +static const std::string kOemCertificateFileNamePrefix = "oemcert_"; + +// File class. The implementation is platform dependent. +class File { + public: + File() {} + virtual ~File() {} + virtual ssize_t Read(char* buffer, size_t bytes) = 0; + virtual ssize_t Write(const char* buffer, size_t bytes) = 0; + + friend class FileSystem; + CORE_DISALLOW_COPY_AND_ASSIGN(File); +}; + +class FileSystem { + public: + FileSystem(); + FileSystem(const std::string& origin, void* extra_data); + virtual ~FileSystem(); + + class Impl; + + // defines as bit flag + enum OpenFlags { + kNoFlags = 0, + kCreate = 1, + kReadOnly = 2, // defaults to read and write access + kTruncate = 4 + }; + + virtual std::unique_ptr Open(const std::string& file_path, int flags); + + virtual bool Exists(const std::string& file_path); + virtual bool Exists(const std::string& file_path, int* errno_value); + virtual bool Remove(const std::string& file_path); + virtual ssize_t FileSize(const std::string& file_path); + + // Return the filenames stored at dir_path. + // dir_path will be stripped from the returned names. + virtual bool List(const std::string& dir_path, + std::vector* names); + + const std::string& origin() const { return origin_; } + void set_origin(const std::string& origin); + + const std::string& identifier() const { return identifier_; } + void set_identifier(const std::string& identifier); + bool IsGlobal() const { return identifier_.empty(); } + + private: + std::unique_ptr impl_; + std::string origin_; + std::string identifier_; + + CORE_DISALLOW_COPY_AND_ASSIGN(FileSystem); +}; + +} // namespace wvutil + +#endif // WVCDM_UTIL_FILE_STORE_H_ diff --git a/wvutil/include/file_utils.h b/wvutil/include/file_utils.h new file mode 100644 index 0000000..1f75f97 --- /dev/null +++ b/wvutil/include/file_utils.h @@ -0,0 +1,29 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. + +#include +#include + +namespace wvutil { + +const char kCurrentDirectory[] = "."; +const char kParentDirectory[] = ".."; +const char kDirectoryDelimiter = '/'; +const char kWildcard[] = "*"; +bool IsCurrentOrParentDirectory(const char* dir); + +class FileUtils { + public: + static bool Exists(const std::string& src); + static bool Exists(const std::string& src, int* errno_value); + // The caller may only specifying a single wildcard + static bool Remove(const std::string& src); + static bool Copy(const std::string& src, const std::string& dest); + static bool List(const std::string& path, std::vector* files); + static bool IsRegularFile(const std::string& path); + static bool IsDirectory(const std::string& path); + static bool CreateDirectory(const std::string& path); +}; + +} // namespace wvutil diff --git a/wvutil/include/log.h b/wvutil/include/log.h new file mode 100644 index 0000000..2854956 --- /dev/null +++ b/wvutil/include/log.h @@ -0,0 +1,111 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +// +// Log - Platform independent interface for a Logging class +// +#ifndef WVCDM_UTIL_LOG_H_ +#define WVCDM_UTIL_LOG_H_ + +#include +#include +#include +#include +#include + +#include "util_common.h" + +namespace wvutil { + +// Simple logging class. The implementation is platform dependent. + +typedef enum { + // This log level should only be used for |g_cutoff|, in order to silence all + // logging. It should never be passed to |Log()| as a log level. + CDM_LOG_SILENT = -1, + + CDM_LOG_ERROR = 0, + CDM_LOG_WARN = 1, + CDM_LOG_INFO = 2, + CDM_LOG_DEBUG = 3, + CDM_LOG_VERBOSE = 4, +} LogPriority; + +extern LogPriority g_cutoff; + +struct LogMessage { + uint32_t uid_; + int64_t time_ms_; + LogPriority priority_; + std::string message_; +}; + +class LogBuffer { + public: + static const int MAX_CAPACITY = 100; + void addLog(const LogMessage& log); + std::vector getLogs(); + + private: + std::deque buffer_; + std::mutex mutex_; +}; + +extern LogBuffer g_logbuf; + +static const uint32_t UNKNOWN_UID = std::numeric_limits::max(); + +#ifdef __ANDROID__ +void SetLoggingUid(const uint32_t); +void ClearLoggingUid(); +uint32_t GetLoggingUid(); +uint32_t GetIpcCallingUid(); +#else +static inline void SetLoggingUid(const uint32_t) {} +static inline void ClearLoggingUid() {} +static inline uint32_t GetLoggingUid() { return UNKNOWN_UID; } +static inline uint32_t GetIpcCallingUid() { return UNKNOWN_UID; } +#endif + +struct LoggingUidSetter { + LoggingUidSetter() {} + LoggingUidSetter(uint32_t uid) { SetLoggingUid(uid); } + virtual ~LoggingUidSetter() { ClearLoggingUid(); } +}; + +// Enable/disable verbose logging (LOGV). +// This function is supplied for cases where the system layer does not +// initialize logging. This is also needed to initialize logging in +// unit tests. +void InitLogging(); + +#ifdef __GNUC__ +[[gnu::format(printf, 5, 6)]] +#endif +void Log(const char* file, const char* function, int line, + LogPriority level, const char* fmt, ...); + +// Log APIs +#ifdef CDM_DISABLE_LOGGING +# define LOGE(...) (void)0 +# define LOGW(...) (void)0 +# define LOGI(...) (void)0 +# define LOGD(...) (void)0 +# define LOGV(...) (void)0 +#else +# ifndef LOGE +# define LOGE(...) \ + Log(__FILE__, __func__, __LINE__, wvutil::CDM_LOG_ERROR, __VA_ARGS__) +# define LOGW(...) \ + Log(__FILE__, __func__, __LINE__, wvutil::CDM_LOG_WARN, __VA_ARGS__) +# define LOGI(...) \ + Log(__FILE__, __func__, __LINE__, wvutil::CDM_LOG_INFO, __VA_ARGS__) +# define LOGD(...) \ + Log(__FILE__, __func__, __LINE__, wvutil::CDM_LOG_DEBUG, __VA_ARGS__) +# define LOGV(...) \ + Log(__FILE__, __func__, __LINE__, wvutil::CDM_LOG_VERBOSE, __VA_ARGS__) +# endif +#endif +} // namespace wvutil + +#endif // WVCDM_UTIL_LOG_H_ diff --git a/wvutil/include/platform.h b/wvutil/include/platform.h new file mode 100644 index 0000000..d101dd6 --- /dev/null +++ b/wvutil/include/platform.h @@ -0,0 +1,31 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +// +// Platform - Abstracts some utilities between platforms. +// +#ifndef WVCDM_UTIL_PLATFORM_H_ +#define WVCDM_UTIL_PLATFORM_H_ + +#include "util_common.h" + +#ifdef _WIN32 +# include +# include // For htonl and ntohl. +# include +# define __PRETTY_FUNCTION__ __FUNCTION__ +# undef NO_ERROR +# undef GetCurrentTime +# undef DeleteFile + +using ssize_t = SSIZE_T; + +inline void sleep(int seconds) { Sleep(seconds * 1000); } +int setenv(const char* key, const char* value, int overwrite); +#else +# include +# include +# include +#endif + +#endif // WVCDM_UTIL_PLATFORM_H_ diff --git a/wvutil/include/rw_lock.h b/wvutil/include/rw_lock.h new file mode 100644 index 0000000..40d7902 --- /dev/null +++ b/wvutil/include/rw_lock.h @@ -0,0 +1,65 @@ +// Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. + +#ifndef WVCDM_UTIL_RW_LOCK_H_ +#define WVCDM_UTIL_RW_LOCK_H_ + +#include + +#include +#include + +#include "disallow_copy_and_assign.h" +#include "util_common.h" + +namespace wvutil { + +// A simple reader-writer mutex implementation that mimics the one from C++17 +class shared_mutex { + public: + shared_mutex() : reader_count_(0), has_writer_(false) {} + ~shared_mutex(); + + // These methods take the mutex as a reader. They do not fulfill the + // SharedMutex requirement from the C++14 STL, but they fulfill enough of it + // to be used with |shared_lock| below. + void lock_shared(); + void unlock_shared(); + + // These methods take the mutex as a writer. They fulfill the Mutex + // requirement from the C++11 STL so that this mutex can be used with + // |std::unique_lock|. + void lock() { lock_implementation(false); } + bool try_lock() { return lock_implementation(true); } + void unlock(); + + private: + bool lock_implementation(bool abort_if_unavailable); + + uint32_t reader_count_; + bool has_writer_; + + std::mutex mutex_; + std::condition_variable condition_variable_; + + CORE_DISALLOW_COPY_AND_ASSIGN(shared_mutex); +}; + +// A simple reader lock implementation that mimics the one from C++14 +template +class shared_lock { + public: + explicit shared_lock(Mutex& lock) : lock_(&lock) { lock_->lock_shared(); } + explicit shared_lock(Mutex* lock) : lock_(lock) { lock_->lock_shared(); } + ~shared_lock() { lock_->unlock_shared(); } + + private: + Mutex* lock_; + + CORE_DISALLOW_COPY_AND_ASSIGN(shared_lock); +}; + +} // namespace wvutil + +#endif // WVCDM_UTIL_RW_LOCK_H_ diff --git a/wvutil/include/string_conversions.h b/wvutil/include/string_conversions.h new file mode 100644 index 0000000..461ef41 --- /dev/null +++ b/wvutil/include/string_conversions.h @@ -0,0 +1,61 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +#ifndef WVCDM_UTIL_STRING_CONVERSIONS_H_ +#define WVCDM_UTIL_STRING_CONVERSIONS_H_ + +#include +#include + +#include +#include + +#include "util_common.h" + +namespace wvutil { + +// ASCII hex to Binary conversion. +std::vector a2b_hex(const std::string& b); +std::vector a2b_hex(const std::string& label, + const std::string& b); +std::string a2bs_hex(const std::string& b); + +// Binary to ASCII hex conversion. The default versions limit output to 2k to +// protect us from log spam. The unlimited version has no length limit. +std::string b2a_hex(const std::vector& b); +std::string unlimited_b2a_hex(const std::vector& b); +std::string b2a_hex(const std::string& b); +std::string unlimited_b2a_hex(const std::string& b); +std::string HexEncode(const uint8_t* bytes, size_t size); +std::string UnlimitedHexEncode(const uint8_t* bytes, size_t size); + +// Base64 encoding/decoding. +// Converts binary data into the ASCII Base64 character set and vice +// versa using the encoding rules defined in RFC4648 section 4. +std::string Base64Encode(const std::vector& bin_input); +std::string Base64Encode(const std::string& bin_input); +std::vector Base64Decode(const std::string& bin_input); + +// URL-Safe Base64 encoding/decoding. +// Converts binary data into the URL/Filename safe ASCII Base64 +// character set and vice versa using the encoding rules defined in +// RFC4648 section 5. +std::string Base64SafeEncode(const std::vector& bin_input); +std::string Base64SafeEncode(const std::string& bin_input); +std::vector Base64SafeDecode(const std::string& bin_input); +// URL-Safe Base64 encoding without padding. +// Similar to Base64SafeEncode(), without any padding character '=' +// at the end. +std::string Base64SafeEncodeNoPad(const std::vector& bin_input); +std::string Base64SafeEncodeNoPad(const std::string& bin_input); + +// Host to Network/Network to Host conversion. +int64_t htonll64(int64_t x); +inline int64_t ntohll64(int64_t x) { return htonll64(x); } + +// Encode unsigned integer into a big endian formatted string. +std::string EncodeUint32(uint32_t u); + +} // namespace wvutil + +#endif // WVCDM_UTIL_STRING_CONVERSIONS_H_ diff --git a/wvutil/include/string_format.h b/wvutil/include/string_format.h new file mode 100644 index 0000000..49ac381 --- /dev/null +++ b/wvutil/include/string_format.h @@ -0,0 +1,23 @@ +// Copyright 2022 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +#ifndef WVCDM_UTIL_STRING_FORMAT_H_ +#define WVCDM_UTIL_STRING_FORMAT_H_ + +#include + +namespace wvutil { + +#ifdef __GNUC__ +[[gnu::format(printf, 2, 3)]] +#endif +bool FormatString(std::string* out, const char* fmt, ...); + +#ifdef __GNUC__ +[[gnu::format(printf, 2, 0)]] +#endif +bool VFormatString(std::string* out, const char* fmt, va_list vlist); + +} // namespace wvutil + +#endif // WVCDM_UTIL_STRING_FORMAT_H_ diff --git a/wvutil/include/timer.h b/wvutil/include/timer.h new file mode 100644 index 0000000..a885d70 --- /dev/null +++ b/wvutil/include/timer.h @@ -0,0 +1,55 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. +// +// Timer - Platform independent interface for a Timer class +// +#ifndef TIMER_H_ +#define TIMER_H_ + +#include + +#include "disallow_copy_and_assign.h" + +namespace wvutil { + +// Timer Handler class. +// +// Derive from this class if you wish to receive events when the timer +// expires. Provide the handler when setting up a new Timer. + +class TimerHandler { + public: + TimerHandler(){}; + virtual ~TimerHandler(){}; + + virtual void OnTimerEvent() = 0; +}; + +// Timer class. The implementation is platform dependent. +// +// This class provides a simple recurring timer API. The class receiving +// timer expiry events should derive from TimerHandler. +// Specify the receiver class and the periodicty of timer events when +// the timer is initiated by calling Start. + +class Timer { + public: + class Impl; + + Timer(); + ~Timer(); + + bool Start(TimerHandler *handler, uint32_t time_in_secs); + void Stop(); + bool IsRunning(); + + private: + Impl *impl_; + + CORE_DISALLOW_COPY_AND_ASSIGN(Timer); +}; + +} // namespace wvutil + +#endif // TIMER_H_ diff --git a/wvutil/include/util_common.h b/wvutil/include/util_common.h new file mode 100644 index 0000000..b1ddc16 --- /dev/null +++ b/wvutil/include/util_common.h @@ -0,0 +1,45 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. + +#ifndef WVCDM_UTIL_UTIL_COMMON_H_ +#define WVCDM_UTIL_UTIL_COMMON_H_ + +// This section deals with defines that are platform-specific. + +#ifdef _WIN32 + +# define CORE_UTIL_IGNORE_DEPRECATED +# define CORE_UTIL_RESTORE_WARNINGS + +#else + +# ifdef __GNUC__ +# define CORE_UTIL_IGNORE_DEPRECATED \ + _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") +# define CORE_UTIL_RESTORE_WARNINGS _Pragma("GCC diagnostic pop") +# else +# define CORE_UTIL_IGNORE_DEPRECATED +# define CORE_UTIL_RESTORE_WARNINGS +# endif + +#endif + +// This section deals with attribute-detection and is platform-agnostic. + +#if !defined(__has_cpp_attribute) +# define __has_cpp_attribute(x) 0 +#endif + +#if __has_cpp_attribute(fallthrough) +# define CORE_UTIL_FALLTHROUGH [[fallthrough]] +#elif __has_cpp_attribute(clang::fallthrough) +# define CORE_UTIL_FALLTHROUGH [[clang::fallthrough]] +#elif __has_cpp_attribute(gnu::fallthrough) +# define CORE_UTIL_FALLTHROUGH [[gnu::fallthrough]] +#else +# define CORE_UTIL_FALLTHROUGH +#endif + +#endif // WVCDM_UTIL_UTIL_COMMON_H_ diff --git a/wvutil/include/wv_attributes.h b/wvutil/include/wv_attributes.h new file mode 100644 index 0000000..c817f1c --- /dev/null +++ b/wvutil/include/wv_attributes.h @@ -0,0 +1,16 @@ +// Copyright 2021 Google LLC. All rights reserved. This file and proprietary +// source code may only be used and distributed under the Widevine +// License Agreement. + +#ifndef WVCDM_UTIL_WV_ATTRIBUTES_H_ +#define WVCDM_UTIL_WV_ATTRIBUTES_H_ + +#ifndef UNUSED +# if defined(__GNUC__) || defined(__clang__) +# define UNUSED __attribute__((__unused__)) +# else +# define UNUSED +# endif +#endif + +#endif // WVCDM_UTIL_WV_ATTRIBUTES_H_ diff --git a/wvutil/src/android_properties.cpp b/wvutil/src/android_properties.cpp new file mode 100644 index 0000000..68de0c5 --- /dev/null +++ b/wvutil/src/android_properties.cpp @@ -0,0 +1,88 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#include + +#include "cas_properties.h" +#include "log.h" + +namespace { + +// Version format: OEMCrypto_major.OEMCrypto_minor.Plugin_version +constexpr char kWvCasPluginVersion[] = "18.4.0"; + +bool GetAndroidProperty(const char* key, std::string* value) { + char val[PROPERTY_VALUE_MAX]; + if (!key) { + LOGW("GetAndroidProperty: Invalid property key parameter"); + return false; + } + if (!value) { + LOGW("GetAndroidProperty: Invalid property value parameter"); + return false; + } + if (property_get(key, val, "Unknown") <= 0) return false; + *value = val; + return true; +} + +} // namespace + +namespace wvcas { + +bool Properties::GetCompanyName(std::string* company_name) { + if (!company_name) { + LOGW("Properties::GetCompanyName: Invalid parameter"); + return false; + } + return GetAndroidProperty("ro.product.manufacturer", company_name); +} + +bool Properties::GetModelName(std::string* model_name) { + if (!model_name) { + LOGW("Properties::GetModelName: Invalid parameter"); + return false; + } + return GetAndroidProperty("ro.product.model", model_name); +} + +bool Properties::GetArchitectureName(std::string* arch_name) { + if (!arch_name) { + LOGW("Properties::GetArchitectureName: Invalid parameter"); + return false; + } + return GetAndroidProperty("ro.product.cpu.abi", arch_name); +} + +bool Properties::GetDeviceName(std::string* device_name) { + if (!device_name) { + LOGW("Properties::GetDeviceName: Invalid parameter"); + return false; + } + return GetAndroidProperty("ro.product.device", device_name); +} + +bool Properties::GetProductName(std::string* product_name) { + if (!product_name) { + LOGW("Properties::GetProductName: Invalid parameter"); + return false; + } + return GetAndroidProperty("ro.product.name", product_name); +} + +bool Properties::GetOEMCryptoPath(std::string* path) { + if (path == nullptr) { + LOGW("Properties::GetOEMCryptoPath: Invalid parameter"); + return false; + } + *path = "liboemcrypto.so"; + return true; +} + +bool Properties::GetWvCasPluginVersion(std::string& version) { + version = kWvCasPluginVersion; + return true; +} + +} // namespace wvcas diff --git a/wvutil/src/clock.cpp b/wvutil/src/clock.cpp new file mode 100644 index 0000000..1eda0cb --- /dev/null +++ b/wvutil/src/clock.cpp @@ -0,0 +1,20 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +// +// Clock - implemented using the standard linux time library + +#include "clock.h" + +#include + +namespace wvutil { + +int64_t Clock::GetCurrentTime() { + struct timeval tv; + tv.tv_sec = tv.tv_usec = 0; + gettimeofday(&tv, nullptr); + return tv.tv_sec; +} + +} // namespace wvutil diff --git a/wvutil/src/file_store.cpp b/wvutil/src/file_store.cpp new file mode 100644 index 0000000..d401887 --- /dev/null +++ b/wvutil/src/file_store.cpp @@ -0,0 +1,537 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +// +// File class - provides a simple android specific file implementation + +#include "file_store.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "file_utils.h" +#include "log.h" +#include "string_conversions.h" + +#include + +// Size of the thread-local error string buffer. Used for calls +// to strerror_r(3). +#define ERRORSTR_BUF_SIZE 1024 + +namespace wvutil { +namespace { +// Maximum number of attempts to read or write on a file. +constexpr size_t kMaxIoAttempts = 5; + +// Stand in constant for a closed file descriptor. +constexpr int kClosedFd = -1; + +// A reset value of |errno|. Although unlikely, it is possible that a +// system call could fail and not set |errno| to a new value. This +// would technically be a bug with glibc, but it could cause our error +// handling code to enter a bad state. +constexpr int kNoError = 0; + +constexpr char kEmptyOrigin[] = ""; + +// Reads from file specified by |fd| into the provided |buffer| up to +// the number of bytes specified by |count|. +// This is an internal function and assumes that all parameters are +// valid. +// +// Returns: +// 0 to |count| - Number of bytes successfully read from file. +// -1 - Error occurred, check |errno| for read(2). +ssize_t SafeRead(int fd, char* buffer, size_t count) { + size_t attempts = 0; + size_t total_bytes_read = 0; + while (total_bytes_read < count && attempts < kMaxIoAttempts) { + const size_t to_read = count - total_bytes_read; + errno = kNoError; + const ssize_t res = read(fd, buffer, to_read); + if (res > 0) { + attempts = 0; // Clearing |attempts| on success. + // It is possible that fewer bytes than |to_read| were read. + // In this case, try reading again. Non-critical errors will + // likely result in success or |errno| being set to EAGAIN on + // the second call. Critical errors will result in a different + // error that the caller will need to handle. + total_bytes_read += static_cast(res); + continue; + } + if (res == 0) return total_bytes_read; // EOF. + attempts++; + if ((errno != EINTR && errno != EAGAIN) || attempts >= kMaxIoAttempts) { + // Caller must handle all other errors, or if max attempts + // have been reached. + return -1; + } + // read() was interrupted by signal, safe to try again. + } + return total_bytes_read; +} + +// Writes to the file specified by |fd| from the provided |buffer|. +// Function will attempt to write all bytes specified by |count|. +// This is an internal function and assumes that all parameters are +// valid. +// +// Returns: +// |count| - Successfully wrote all bytes to file. +// -1 - Error occurred, check |errno| for write(2). +ssize_t SafeWrite(int fd, const char* buffer, size_t count) { + size_t attempts = 0; + size_t total_bytes_written = 0; + while (total_bytes_written < count && attempts < kMaxIoAttempts) { + const size_t to_write = count - total_bytes_written; + errno = kNoError; + const ssize_t res = write(fd, &buffer[total_bytes_written], to_write); + if (res > 0) { + attempts = 0; // Clearing |attempts| on success. + // It is possible that fewer bytes than |to_write| were written. + // In this case, try writing again. Non-critical errors will + // likely result in success or |errno| being set to EAGAIN on + // the second call. Critical errors will result in a different + // error that the caller will need to handle. + total_bytes_written += static_cast(res); + continue; + } + if (res == 0) return total_bytes_written; // Possible EOF. + attempts++; + if ((errno != EINTR && errno != EAGAIN) || attempts >= kMaxIoAttempts) { + // Caller must handle all other errors, or if max attempts + // have been reached. + return -1; + } + // write() was interrupted by signal, safe to try again. + } + return total_bytes_written; +} + +// Converts the provided error number to its string representation. +// Supports a subset of error numbers expected from the system calls +// used in this module. +// TODO(b/183653374): Replace this with strerrorname_np(). +const char* ErrnoToString(int num) { + switch (num) { + case kNoError: + return "ZERO"; + case EACCES: + return "EACCES"; + case EAGAIN: + return "EAGAIN"; + case EBADF: + return "EBADF"; + case EBUSY: + return "EBUSY"; + case EDESTADDRREQ: + return "EDESTADDRREQ"; + case EDQUOT: + return "EDQUOT"; + case EEXIST: + return "EEXIST"; + case EFAULT: + return "EFAULT"; + case EFBIG: + return "EFBIG"; + case EINTR: + return "EINTR"; + case EINVAL: + return "EINVAL"; + case EIO: + return "EIO"; + case EISDIR: + return "EISDIR"; + case ELOOP: + return "ELOOP"; + case EMFILE: + return "EMFILE"; + case ENAMETOOLONG: + return "ENAMETOOLONG"; + case ENFILE: + return "ENFILE"; + case ENODEV: + return "ENODEV"; + case ENOENT: + return "ENOENT"; + case ENOMEM: + return "ENOMEM"; + case ENOSPC: + return "ENOSPC"; + case ENOTDIR: + return "ENOTDIR"; + case ENXIO: + return "ENXIO"; + case EOPNOTSUPP: + return "EOPNOTSUPP"; + case EOVERFLOW: + return "EOVERFLOW"; + case EPERM: + return "EPERM"; + case EPIPE: + return "EPIPE"; + case EROFS: + return "EROFS"; + case ETXTBSY: + return "ETXTBSY"; +#if EWOULDBLOCK != EAGAIN + case EWOULDBLOCK: + return "EWOULDBLOCK"; +#endif + } + return "UNKNOWN"; +} + +// Safely converts the provided error number to its standard +// description string as provided by strerror_r(). +// This function is guaranteed to return null-terminated string, +// and is thread safe. +const char* ErrnoToDescription(int num) { + static thread_local char error_buf[ERRORSTR_BUF_SIZE]; + if (num == kNoError) { + return "Unspecified error"; + } + // Always ensure there is a null term. + error_buf[sizeof(error_buf) - 1] = 0; + // See strerror_l(3) manual page for details. +#if (_POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600) && !_GNU_SOURCE + // Portable version: + // int strerror_r(int num, char* buf, size_t buflen) + // Returns: + // 0 on success + // -1 or a positive value on error (depends on glibc version) + const int res = strerror_r(num, error_buf, sizeof(error_buf)); + return res != 0 ? "Unknown" : error_buf; +#else + // GNU specific version: + // char* strerror_r(int num, char* buf, size_t buflen) + // Returns: + // Pointer to |buf| or internal string on success + // Null on error + const char* res = strerror_r(num, error_buf, sizeof(error_buf)); + return res == nullptr ? "Unknown" : res; +#endif +} + +// Converts the provided file |mode| to a "ls" style file mode. +std::string StatModeToString(unsigned int mode) { + std::string mode_rep; + mode_rep.reserve(11); // 1 file type + 9 permissions + 1 term. + // File type. + if (S_ISREG(mode)) { + mode_rep.append("-"); + } else if (S_ISDIR(mode)) { + mode_rep.append("d"); + } else if (S_ISLNK(mode)) { + mode_rep.append("l"); + } else if (S_ISCHR(mode)) { + mode_rep.append("c"); + } else if (S_ISBLK(mode)) { + mode_rep.append("b"); + } else if (S_ISSOCK(mode)) { + mode_rep.append("s"); + } else { + mode_rep.append("?"); + } + // User owner permission. + mode_rep.append((mode & S_IRUSR) ? "r" : "-"); + mode_rep.append((mode & S_IWUSR) ? "w" : "-"); + mode_rep.append((mode & S_IXUSR) ? "x" : "-"); + // Group owner permission. + mode_rep.append((mode & S_IRGRP) ? "r" : "-"); + mode_rep.append((mode & S_IWGRP) ? "w" : "-"); + mode_rep.append((mode & S_IXGRP) ? "x" : "-"); + // Others permission. + mode_rep.append((mode & S_IROTH) ? "r" : "-"); + mode_rep.append((mode & S_IWOTH) ? "w" : "-"); + mode_rep.append((mode & S_IXOTH) ? "x" : "-"); + return mode_rep; +} + +// Coverts the provided system time in seconds to a string representation +// in the system's local time. +// Time format is a modified ISO8601. +// Example: 2021-03-01 12:32:53 +// If the time cannot be converted into this format, then the decimal +// representation of seconds are returned. +std::string PosixTimeToString(time_t seconds) { + struct tm timestamp; + if (localtime_r(&seconds, ×tamp) == nullptr) { + // Only possible failure is an overflow. + return std::to_string(seconds); + } + char buffer[32]; + memset(buffer, 0, sizeof(buffer)); + const size_t length = strftime(buffer, sizeof(buffer), "%F %T", ×tamp); + if (length == 0) { + // Unexpected error. Just return seconds. + return std::to_string(seconds); + } + return std::string(buffer, length); +} + +std::string GetFileNameSafeHash(const std::string& input) { + std::vector hash(MD5_DIGEST_LENGTH); + MD5(reinterpret_cast(input.data()), input.size(), + hash.data()); + return wvutil::Base64SafeEncode(hash); +} + +std::string GetFileNameForIdentifier(const std::string path, + const std::string identifier) { + std::string file_name = path; + std::string dir_path; + const size_t delimiter_pos = path.rfind(kDirectoryDelimiter); + if (delimiter_pos != std::string::npos) { + dir_path = file_name.substr(0, delimiter_pos); + file_name = path.substr(delimiter_pos + 1); + } + + if (file_name == kCertificateFileName && !identifier.empty()) { + const std::string hash = GetFileNameSafeHash(identifier); + file_name = kCertificateFileNamePrefix + hash + kCertificateFileNameExt; + } else if (file_name == kLegacyCertificateFileName && !identifier.empty()) { + const std::string hash = GetFileNameSafeHash(identifier); + file_name = + kLegacyCertificateFileNamePrefix + hash + kCertificateFileNameExt; + } + + if (dir_path.empty()) + return file_name; + return dir_path + kDirectoryDelimiter + file_name; +} +} // namespace + +class AndroidFile : public File { + public: + // Parameters: + // |fd| - Open file descriptor for a regular file. + // |flags| - Bit field of flags originally passed to FileSystem::Open() + // |file_path| - Path used to open file. + AndroidFile(int fd, int flags, const std::string& file_path) + : fd_(fd), flags_(flags), file_path_(file_path) {} + + ~AndroidFile() { Close(); } + + bool IsOpen() const { return fd_ != kClosedFd; } + + bool CanWrite() const { return !(flags_ & FileSystem::kReadOnly); } + + // Used for logging. + const char* file_path() const { return file_path_.c_str(); } + + ssize_t Read(char* buffer, size_t bytes) override { + if (!buffer) { + LOGE("Output |buffer| is null"); + return -1; + } + if (!IsOpen()) { + LOGE("File not open: path = %s", file_path()); + return -1; + } + const ssize_t res = SafeRead(fd_, buffer, bytes); + if (res < 0) { + const int saved_errno = errno; + LOGE("Read failed: errno = %s (%d), desc = %s", + ErrnoToString(saved_errno), saved_errno, + ErrnoToDescription(saved_errno)); + return -1; + } else if (res < bytes) { + LOGD("Read output truncated: expected = %zu, actual = %zd", bytes, res); + } + return res; + } + + ssize_t Write(const char* buffer, size_t bytes) override { + if (!buffer) { + LOGE("Input |buffer| is null"); + return -1; + } + if (!IsOpen()) { + LOGE("File not open: path = %s", file_path()); + return -1; + } + if (!CanWrite()) { + LOGE("File is read only: path = %s", file_path()); + return -1; + } + const ssize_t res = SafeWrite(fd_, buffer, bytes); + if (res < 0) { + const int saved_errno = errno; + LOGE("Write failed: errno = %s (%d), desc = %s", + ErrnoToString(saved_errno), saved_errno, + ErrnoToDescription(saved_errno)); + return -1; + } + FlushFile(); + return res; + } + + private: + void FlushFile() { fsync(fd_); } + + void Close() { + if (IsOpen()) { + FlushFile(); + close(fd_); + fd_ = kClosedFd; + } + } + + // Logs the contents of the file's stat info. + void LogStat() const { + if (!IsOpen()) { + LOGD("No stat info available"); + return; + } + struct stat st; + errno = kNoError; + if (fstat(fd_, &st) != 0) { + const int saved_errno = errno; + if (errno != EBADF) { + // No logs if an issue with FD, caller would have indicated the problem. + LOGE("Stat failed: errno = %s (%d), desc = %s", + ErrnoToString(saved_errno), saved_errno, + ErrnoToDescription(saved_errno)); + } + return; + } + LOGD( + "Stat: path = %s, st_dev = %lu, st_ino = %lu, st_mode = 0%o (%s), " + "st_uid = %u, st_gid = %u, st_size = %ld, st_atime = %s, " + "st_mtime = %s, st_ctime = %s", + file_path(), st.st_dev, st.st_ino, st.st_mode, + StatModeToString(st.st_mode).c_str(), st.st_uid, st.st_gid, st.st_size, + PosixTimeToString(st.st_atime).c_str(), + PosixTimeToString(st.st_mtime).c_str(), + PosixTimeToString(st.st_ctime).c_str()); + } + + // File descriptor of the opened file. Set to -1 when closed. + int fd_ = kClosedFd; + // Bit field of OpenFlags. + int flags_ = 0; + // Path used to open the file descriptor. + std::string file_path_; +}; + +class FileSystem::Impl {}; + +FileSystem::FileSystem() : FileSystem(kEmptyOrigin, nullptr) {} +FileSystem::FileSystem(const std::string& origin, void* /* extra_data */) + : origin_(origin) {} + +FileSystem::~FileSystem() {} + +std::unique_ptr FileSystem::Open(const std::string& file_name, + int flags) { + const std::string file_path = + GetFileNameForIdentifier(file_name, identifier_); + // Verify flags. + if ((flags & kReadOnly) && (flags & kTruncate)) { + LOGE( + "Cannot be both truncated and be read-only: " + "file = %s, identifier = %s", + file_name.c_str(), identifier_.c_str()); + return nullptr; + } + + // Create the enclosing directory if it does not exist. + const size_t delimiter_pos = file_path.rfind(kDirectoryDelimiter); + if (delimiter_pos != std::string::npos) { + const std::string dir_path = file_path.substr(0, delimiter_pos); + if ((flags & FileSystem::kCreate) && !Exists(dir_path)) + FileUtils::CreateDirectory(dir_path); + } + + const bool exists = Exists(file_path); + if (!(flags & kCreate) && !exists) { + LOGD("File does not exist: file = %s, identifier = %s", file_name.c_str(), + identifier_.c_str()); + return nullptr; + } + + const int open_flags = ((flags & kCreate) ? O_CREAT : 0) | + ((flags & kReadOnly) ? O_RDONLY : O_RDWR) | + ((flags & kTruncate) ? O_TRUNC : 0) | + O_CLOEXEC; // Never share on calls to exec(). + constexpr mode_t kDefaultMode = S_IRUSR | S_IWUSR; + errno = kNoError; + const int fd = open(file_path.c_str(), open_flags, kDefaultMode); + if (fd < 0) { + const int saved_errno = errno; + LOGE("%s failed: errno = %s (%d), desc = %s, path = %s", + exists ? "Open" : "Create", ErrnoToString(saved_errno), saved_errno, + ErrnoToDescription(saved_errno), file_path.c_str()); + return nullptr; + } + // Check that the opened file is a regular file. + struct stat st; + errno = kNoError; + if (fstat(fd, &st) != 0) { + const int saved_errno = errno; + LOGE("Stat failed: errno = %s (%d), desc = %s", ErrnoToString(saved_errno), + saved_errno, ErrnoToDescription(saved_errno)); + close(fd); + return nullptr; + } + if (!S_ISREG(st.st_mode)) { + LOGE("Not a file: path = %s, st_mode = 0%o (%s)", file_path.c_str(), + st.st_mode, StatModeToString(st.st_mode).c_str()); + close(fd); + return nullptr; + } + return std::unique_ptr(new AndroidFile(fd, flags, file_path)); +} + +bool FileSystem::Exists(const std::string& path) { + return FileUtils::Exists(GetFileNameForIdentifier(path, identifier_)); +} + +bool FileSystem::Exists(const std::string& path, int* errno_value) { + return FileUtils::Exists(GetFileNameForIdentifier(path, identifier_), + errno_value); +} + +bool FileSystem::Remove(const std::string& path) { + return FileUtils::Remove(GetFileNameForIdentifier(path, identifier_)); +} + +ssize_t FileSystem::FileSize(const std::string& file_name) { + const std::string file_path = + GetFileNameForIdentifier(file_name, identifier_); + struct stat st; + errno = kNoError; + if (stat(file_path.c_str(), &st) == 0) { + if (st.st_size == 0) { + LOGW("File is empty: name = %s", file_name.c_str()); + } + return st.st_size; + } + // Else, error occurred. + const int saved_errno = errno; + LOGE("Stat failed: errno = %s (%d), desc = %s", ErrnoToString(saved_errno), + saved_errno, ErrnoToDescription(saved_errno)); + return -1; +} + +bool FileSystem::List(const std::string& path, + std::vector* filenames) { + return FileUtils::List(GetFileNameForIdentifier(path, origin_), filenames); +} + +void FileSystem::set_origin(const std::string& origin) { origin_ = origin; } + +void FileSystem::set_identifier(const std::string& identifier) { + identifier_ = identifier; +} +} // namespace wvutil diff --git a/wvutil/src/file_utils.cpp b/wvutil/src/file_utils.cpp new file mode 100644 index 0000000..56c3106 --- /dev/null +++ b/wvutil/src/file_utils.cpp @@ -0,0 +1,240 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. + +#include "file_utils.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "log.h" + +namespace wvutil { + +bool IsCurrentOrParentDirectory(const char* dir) { + return strcmp(dir, kCurrentDirectory) == 0 || + strcmp(dir, kParentDirectory) == 0; +} + +bool FileUtils::Exists(const std::string& path) { + return Exists(path, nullptr); +} + +bool FileUtils::Exists(const std::string& path, int* errno_value) { + struct stat buf; + int error = 0; + int res = stat(path.c_str(), &buf) == 0; + if (!res) { + error = errno; + if (error == ENOENT) { + LOGI("stat failed: ENOENT"); + } else { + LOGE("stat failed: %d, %s", error, strerror(error)); + } + } + if (errno_value != nullptr) *errno_value = error; + return res; +} + +bool FileUtils::Remove(const std::string& path) { + if (FileUtils::IsDirectory(path)) { + // Handle directory deletion + DIR* dir; + if ((dir = opendir(path.c_str())) != nullptr) { + // first remove files and dir within it + struct dirent* entry; + while ((entry = readdir(dir)) != nullptr) { + if (!IsCurrentOrParentDirectory(entry->d_name)) { + std::string path_to_remove = path + kDirectoryDelimiter; + path_to_remove += entry->d_name; + if (!Remove(path_to_remove)) { + closedir(dir); + LOGW("Failed to remove directory entry: dir_path = %s, entry = %s", + path.c_str(), entry->d_name); + return false; + } + } + } + closedir(dir); + } + if (rmdir(path.c_str())) { + LOGW("File::Remove: rmdir failed: %d, %s", errno, strerror(errno)); + return false; + } + return true; + } else { + size_t wildcard_pos = path.find(kWildcard); + if (wildcard_pos == std::string::npos) { + // Handle file deletion + if (unlink(path.c_str()) && (errno != ENOENT)) { + LOGW("File::Remove: unlink failed: %d, %s", errno, strerror(errno)); + return false; + } + } else { + // Handle wildcard specified file deletion + size_t delimiter_pos = path.rfind(kDirectoryDelimiter, wildcard_pos); + if (delimiter_pos == std::string::npos) { + LOGW("File::Remove: unable to find path delimiter before wildcard"); + return false; + } + + DIR* dir; + std::string dir_path = path.substr(0, delimiter_pos); + std::string prepend = + path.substr(delimiter_pos + 1, wildcard_pos - delimiter_pos - 1); + if ((dir = opendir(dir_path.c_str())) == nullptr) { + LOGW("File::Remove: directory open failed for wildcard: %d, %s", errno, + strerror(errno)); + return false; + } + + struct dirent* entry; + std::string ext = path.substr(wildcard_pos + 1); + + while ((entry = readdir(dir)) != nullptr) { + size_t filename_len = strlen(entry->d_name); + if (filename_len > ext.size()) { + if (strcmp(entry->d_name + filename_len - ext.size(), ext.c_str()) == + 0 && + !IsCurrentOrParentDirectory(entry->d_name) && + strncmp(entry->d_name, prepend.c_str(), prepend.size()) == 0) { + std::string file_path_to_remove = + dir_path + kDirectoryDelimiter + entry->d_name; + if (!Remove(file_path_to_remove)) { + closedir(dir); + return false; + } + } + } + } + closedir(dir); + } + return true; + } +} + +bool FileUtils::Copy(const std::string& src, const std::string& dest) { + struct stat stat_buf; + if (stat(src.c_str(), &stat_buf)) { + LOGV("File::Copy: file %s stat error: %d, %s", src.c_str(), errno, + strerror(errno)); + return false; + } + + int fd_src = open(src.c_str(), O_RDONLY); + if (fd_src < 0) { + LOGW("File::Copy: unable to open file %s: %d, %s", src.c_str(), errno, + strerror(errno)); + return false; + } + + int fd_dest = open(dest.c_str(), O_WRONLY | O_CREAT, stat_buf.st_mode); + if (fd_dest < 0) { + LOGW("File::Copy: unable to open file %s: %d, %s", dest.c_str(), errno, + strerror(errno)); + close(fd_src); + return false; + } + + off_t offset = 0; + bool status = true; + if (sendfile(fd_dest, fd_src, &offset, stat_buf.st_size) < 0) { + LOGV("File::Copy: unable to copy %s to %s: %d, %s", src.c_str(), + dest.c_str(), errno, strerror(errno)); + status = false; + } + + close(fd_src); + close(fd_dest); + return status; +} + +bool FileUtils::List(const std::string& path, std::vector* files) { + if (nullptr == files) { + LOGV("File::List: files destination not provided"); + return false; + } + + if (!FileUtils::Exists(path)) { + LOGV("File::List: path %s does not exist: %d, %s", path.c_str(), errno, + strerror(errno)); + return false; + } + + DIR* dir = opendir(path.c_str()); + if (dir == nullptr) { + LOGW("File::List: unable to open directory %s: %d, %s", path.c_str(), errno, + strerror(errno)); + return false; + } + + files->clear(); + struct dirent* entry; + while ((entry = readdir(dir)) != nullptr) { + if (!IsCurrentOrParentDirectory(entry->d_name)) { + files->push_back(entry->d_name); + } + } + closedir(dir); + + return true; +} + +bool FileUtils::IsRegularFile(const std::string& path) { + struct stat buf; + if (stat(path.c_str(), &buf) == 0) + return buf.st_mode & S_IFREG; + else + return false; +} + +bool FileUtils::IsDirectory(const std::string& path) { + struct stat buf; + if (stat(path.c_str(), &buf) == 0) + return buf.st_mode & S_IFDIR; + else + return false; +} + +bool FileUtils::CreateDirectory(const std::string& path_in) { + std::string path = path_in; + size_t size = path.size(); + if ((size == 1) && (path[0] == kDirectoryDelimiter)) return true; + + if (size <= 1) return false; + + size_t pos = path.find(kDirectoryDelimiter, 1); + while (pos < size) { + path[pos] = '\0'; + if (mkdir(path.c_str(), 0700) != 0) { + if (errno != EEXIST) { + LOGW("File::CreateDirectory: mkdir failed: %d, %s\n", errno, + strerror(errno)); + return false; + } + } + path[pos] = kDirectoryDelimiter; + pos = path.find(kDirectoryDelimiter, pos + 1); + } + + if (path[size - 1] != kDirectoryDelimiter) { + if (mkdir(path.c_str(), 0700) != 0) { + if (errno != EEXIST) { + LOGW("File::CreateDirectory: mkdir failed: %d, %s\n", errno, + strerror(errno)); + return false; + } + } + } + return true; +} + +} // namespace wvutil diff --git a/wvutil/src/log.cpp b/wvutil/src/log.cpp new file mode 100644 index 0000000..718d608 --- /dev/null +++ b/wvutil/src/log.cpp @@ -0,0 +1,144 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +// +// Log - implemented using the standard Android logging mechanism + +/* + * Qutoing from system/core/include/log/log.h: + * Normally we strip ALOGV (VERBOSE messages) from release builds. + * You can modify this (for example with "#define LOG_NDEBUG 0" + * at the top of your source file) to change that behavior. + */ +#ifndef LOG_NDEBUG +# ifdef NDEBUG +# define LOG_NDEBUG 1 +# else +# define LOG_NDEBUG 0 +# endif +#endif + +#define LOG_TAG "WVCas" +#define LOG_BUF_SIZE 5120 + +#include "log.h" +#include +#include + +#include +#include +#include + +#include +#include + +/* + * Uncomment the line below if you want to have the LOGV messages to print + * IMPORTANT : this will affect all of CDM + */ + +// #define LOG_NDEBUG 0 + +namespace wvutil { + +namespace { +int64_t GetCurrentTimeMs() { + struct timeval tv {}; + gettimeofday(&tv, NULL); + auto msec1 = static_cast(tv.tv_sec) * 1000; + auto msec2 = static_cast(tv.tv_usec) / 1000; + return msec1 + msec2; +} +} // namespace + +LogPriority g_cutoff = CDM_LOG_INFO; + +LogBuffer g_logbuf; + +thread_local bool tl_logging_uid_set_ = false; + +thread_local uint32_t tl_logging_uid_ = UNKNOWN_UID; + +void SetLoggingUid(const uint32_t uid) { + tl_logging_uid_set_ = true; + tl_logging_uid_ = uid; +} + +void ClearLoggingUid() { + tl_logging_uid_set_ = false; + tl_logging_uid_ = UNKNOWN_UID; +} + +uint32_t GetLoggingUid() { return tl_logging_uid_; } + +uint32_t GetIpcCallingUid() { + const auto self = android::hardware::IPCThreadState::selfOrNull(); + return self ? self->getCallingUid() : UNKNOWN_UID; +} + +void InitLogging() {} + +void Log(const char* file, const char* function, int line, LogPriority level, + const char* format, ...) { + const char* filename = strrchr(file, '/'); + filename = filename == nullptr ? file : filename + 1; + + static thread_local char buf[LOG_BUF_SIZE]; + int len = + snprintf(buf, LOG_BUF_SIZE, "[%s(%d):%s] ", filename, line, function); + if (len < 0) len = 0; + if (static_cast(len) < sizeof(buf)) { + va_list ap; + va_start(ap, format); + vsnprintf(buf + len, LOG_BUF_SIZE - len, format, ap); + va_end(ap); + } + + android_LogPriority prio = ANDROID_LOG_VERBOSE; + + switch (level) { + case CDM_LOG_SILENT: + return; // It is nonsensical to pass LOG_SILENT. + case CDM_LOG_ERROR: + prio = ANDROID_LOG_ERROR; + break; + case CDM_LOG_WARN: + prio = ANDROID_LOG_WARN; + break; + case CDM_LOG_INFO: + prio = ANDROID_LOG_INFO; + break; + case CDM_LOG_DEBUG: + prio = ANDROID_LOG_DEBUG; + break; +#if LOG_NDEBUG + case CDM_LOG_VERBOSE: + return; +#else + case CDM_LOG_VERBOSE: + prio = ANDROID_LOG_VERBOSE; + break; +#endif + } + + __android_log_write(prio, LOG_TAG, buf); + if (level <= CDM_LOG_INFO) { + uint32_t uid = tl_logging_uid_set_ ? tl_logging_uid_ : GetIpcCallingUid(); + g_logbuf.addLog({uid, GetCurrentTimeMs(), level, buf}); + } +} + +void LogBuffer::addLog(const LogMessage& log) { + std::unique_lock lock(mutex_); + buffer_.push_back(log); + while (buffer_.size() > MAX_CAPACITY) { + buffer_.pop_front(); + } +} + +std::vector LogBuffer::getLogs() { + std::unique_lock lock(mutex_); + return {buffer_.begin(), buffer_.end()}; +} + +} // namespace wvutil \ No newline at end of file diff --git a/wvutil/src/rw_lock.cpp b/wvutil/src/rw_lock.cpp new file mode 100644 index 0000000..96218a2 --- /dev/null +++ b/wvutil/src/rw_lock.cpp @@ -0,0 +1,60 @@ +// Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. + +#include "rw_lock.h" + +#include "log.h" + +namespace wvutil { + +shared_mutex::~shared_mutex() { + if (reader_count_ > 0) { + LOGE("shared_mutex destroyed with active readers!"); + } + if (has_writer_) { + LOGE("shared_mutex destroyed with an active writer!"); + } +} + +void shared_mutex::lock_shared() { + std::unique_lock lock(mutex_); + + while (has_writer_) { + condition_variable_.wait(lock); + } + + ++reader_count_; +} + +void shared_mutex::unlock_shared() { + std::unique_lock lock(mutex_); + + --reader_count_; + + if (reader_count_ == 0) { + condition_variable_.notify_all(); + } +} + +bool shared_mutex::lock_implementation(bool abort_if_unavailable) { + std::unique_lock lock(mutex_); + + while (reader_count_ > 0 || has_writer_) { + if (abort_if_unavailable) return false; + condition_variable_.wait(lock); + } + + has_writer_ = true; + return true; +} + +void shared_mutex::unlock() { + std::unique_lock lock(mutex_); + + has_writer_ = false; + + condition_variable_.notify_all(); +} + +} // namespace wvutil diff --git a/wvutil/src/string_conversions.cpp b/wvutil/src/string_conversions.cpp new file mode 100644 index 0000000..37ac2ef --- /dev/null +++ b/wvutil/src/string_conversions.cpp @@ -0,0 +1,343 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. + +#include "string_conversions.h" + +#include +#include +#include +#include + +#include + +#include "log.h" +#include "platform.h" + +namespace wvutil { +namespace { +// Base64 character set, indexed for their 6-bit mapping, plus '='. +const char kBase64Codes[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; +// URL safe Base64 character set. +const char kBase64SafeCodes[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_="; + +// Gets the low |n| bits of |in|. +#define GET_LOW_BITS(in, n) ((in) & ((1 << (n)) - 1)) +// Gets the given (zero-indexed) bits [a, b) of |in|. +#define GET_BITS(in, a, b) GET_LOW_BITS((in) >> (a), (b) - (a)) +// Calculates a/b using round-up division (only works for positive numbers). +#define CEIL_DIVIDE(a, b) ((((a)-1) / (b)) + 1) + +// Decodes a single Base64 encoded character into its 6-bit value. +// The provided |codes| must be a Base64 character map. +int DecodeBase64Char(char c, const char* codes) { + const char* c_in_codes = strchr(codes, c); + if (c_in_codes == nullptr) return -1; + const uintptr_t c_in_codes_int = reinterpret_cast(c_in_codes); + const uintptr_t codes_int = reinterpret_cast(codes); + return static_cast(c_in_codes_int - codes_int); +} + +bool DecodeHexChar(char ch, uint8_t* digit) { + if (ch >= '0' && ch <= '9') { + *digit = ch - '0'; + return true; + } + ch = tolower(ch); + if ((ch >= 'a') && (ch <= 'f')) { + *digit = ch - 'a' + 10; + return true; + } + return false; +} + +// Encode for standard base64 encoding (RFC4648). +// https://en.wikipedia.org/wiki/Base64 +// Text | M | a | n | +// ASCI | 77 (0x4d) | 97 (0x61) | 110 (0x6e) | +// Bits | 0 1 0 0 1 1 0 1 0 1 1 0 0 0 0 1 0 1 1 0 1 1 1 0 | +// Index | 19 | 22 | 5 | 46 | +// Base64 | T | W | F | u | +// | <----------------- 24-bits -----------------> | + +// The provided |codes| must be a Base64 character map. +std::string Base64EncodeInternal(const uint8_t* data, size_t length, + const char* codes) { + // |temp| stores a 24-bit block that is treated as an array where insertions + // occur from high to low. + uint32_t temp = 0; + size_t out_index = 0; + const size_t out_size = CEIL_DIVIDE(length, 3) * 4; + std::string result(out_size, '\0'); + for (size_t i = 0; i < length; i++) { + // "insert" 8-bits of data + temp |= (data[i] << ((2 - (i % 3)) * 8)); + if (i % 3 == 2) { + result[out_index++] = codes[GET_BITS(temp, 18, 24)]; + result[out_index++] = codes[GET_BITS(temp, 12, 18)]; + result[out_index++] = codes[GET_BITS(temp, 6, 12)]; + result[out_index++] = codes[GET_BITS(temp, 0, 6)]; + temp = 0; + } + } + if (length % 3 == 1) { + result[out_index++] = codes[GET_BITS(temp, 18, 24)]; + result[out_index++] = codes[GET_BITS(temp, 12, 18)]; + result[out_index++] = '='; + result[out_index++] = '='; + } else if (length % 3 == 2) { + result[out_index++] = codes[GET_BITS(temp, 18, 24)]; + result[out_index++] = codes[GET_BITS(temp, 12, 18)]; + result[out_index++] = codes[GET_BITS(temp, 6, 12)]; + result[out_index++] = '='; + } + return result; +} + +std::vector Base64DecodeInternal(const char* encoded, size_t length, + const char* codes) { + const size_t out_size_max = CEIL_DIVIDE(length * 3, 4); + std::vector result(out_size_max, '\0'); + // |temp| stores 24-bits of data that is treated as an array where insertions + // occur from high to low. + uint32_t temp = 0; + size_t out_index = 0; + size_t i; + for (i = 0; i < length; i++) { + if (encoded[i] == '=') { + // Verify an '=' only appears at the end. We want i to remain at the + // first '=', so we need an inner loop. + for (size_t j = i; j < length; j++) { + if (encoded[j] != '=') { + LOGE("base64Decode failed"); + return std::vector(); + } + } + if (length % 4 != 0) { + // If padded, then the length must be a multiple of 4. + // Unpadded messages are OK. + LOGE("base64Decode failed"); + return std::vector(); + } + break; + } + + const int decoded = DecodeBase64Char(encoded[i], codes); + if (decoded < 0) { + LOGE("base64Decode failed"); + return std::vector(); + } + // "insert" 6-bits of data + temp |= (decoded << ((3 - (i % 4)) * 6)); + + if (i % 4 == 3) { + result[out_index++] = GET_BITS(temp, 16, 24); + result[out_index++] = GET_BITS(temp, 8, 16); + result[out_index++] = GET_BITS(temp, 0, 8); + temp = 0; + } + } + + switch (i % 4) { + case 1: + LOGE("base64Decode failed"); + return std::vector(); + case 2: + result[out_index++] = GET_BITS(temp, 16, 24); + break; + case 3: + result[out_index++] = GET_BITS(temp, 16, 24); + result[out_index++] = GET_BITS(temp, 8, 16); + break; + } + result.resize(out_index); + return result; +} +} // namespace + +// converts an ascii hex string(2 bytes per digit) into a decimal byte string +std::vector a2b_hex(const std::string& byte) { + std::vector array; + size_t count = byte.size(); + if (count == 0 || (count % 2) != 0) { + LOGE("Invalid input size %zu for string %s", count, byte.c_str()); + return array; + } + + for (size_t i = 0; i < count / 2; ++i) { + unsigned char msb = 0; // most significant 4 bits + unsigned char lsb = 0; // least significant 4 bits + if (!DecodeHexChar(byte[i * 2], &msb) || + !DecodeHexChar(byte[i * 2 + 1], &lsb)) { + LOGE("Invalid hex value %c%c at index %zu", byte[i * 2], byte[i * 2 + 1], + i); + return array; + } + array.push_back((msb << 4) | lsb); + } + return array; +} + +// converts an ascii hex string(2 bytes per digit) into a decimal byte string +// dump the string with the label. +std::vector a2b_hex(const std::string& label, + const std::string& byte) { + std::cout << std::endl + << "[[DUMP: " << label << " ]= \"" << byte << "\"]" << std::endl + << std::endl; + + return a2b_hex(byte); +} + +std::string a2bs_hex(const std::string& byte) { + std::vector array = a2b_hex(byte); + return std::string(array.begin(), array.end()); +} + +std::string b2a_hex(const std::vector& byte) { + if (byte.empty()) return ""; + return HexEncode(byte.data(), byte.size()); +} + +std::string unlimited_b2a_hex(const std::vector& byte) { + if (byte.empty()) return ""; + return UnlimitedHexEncode(byte.data(), byte.size()); +} + +std::string b2a_hex(const std::string& byte) { + if (byte.empty()) return ""; + return HexEncode(reinterpret_cast(byte.data()), + byte.length()); +} + +std::string unlimited_b2a_hex(const std::string& byte) { + if (byte.empty()) return ""; + return UnlimitedHexEncode(reinterpret_cast(byte.data()), + byte.length()); +} + +std::string HexEncode(const uint8_t* in_buffer, size_t size) { + constexpr unsigned int kMaxSafeSize = 2048; + if (size > kMaxSafeSize) size = kMaxSafeSize; + return UnlimitedHexEncode(in_buffer, size); +} + +std::string UnlimitedHexEncode(const uint8_t* in_buffer, size_t size) { + static const char kHexChars[] = "0123456789ABCDEF"; + if (size == 0) return ""; + // Each input byte creates two output hex characters. + std::string out_buffer(size * 2, '\0'); + for (unsigned int i = 0; i < size; ++i) { + char byte = in_buffer[i]; + out_buffer[(i << 1)] = kHexChars[(byte >> 4) & 0xf]; + out_buffer[(i << 1) + 1] = kHexChars[byte & 0xf]; + } + return out_buffer; +} + +// Standard Base64 encoding and decoding. + +std::string Base64Encode(const std::vector& bin_input) { + if (bin_input.empty()) { + return std::string(); + } + return Base64EncodeInternal(bin_input.data(), bin_input.size(), kBase64Codes); +} + +std::string Base64Encode(const std::string& bin_input) { + if (bin_input.empty()) { + return std::string(); + } + return Base64EncodeInternal( + reinterpret_cast(bin_input.data()), bin_input.size(), + kBase64Codes); +} + +// Decode for standard base64 encoding (RFC4648). +std::vector Base64Decode(const std::string& b64_input) { + if (b64_input.empty()) { + return std::vector(); + } + return Base64DecodeInternal(b64_input.data(), b64_input.size(), kBase64Codes); +} + +// URL/Filename Safe Base64 encoding and decoding. + +// This is the encoding required to interface with the provisioning server, as +// well as for certain license server transactions. It is also used for logging +// certain strings. The difference between web safe encoding vs regular encoding +// is that the web safe version replaces '+' with '-' and '/' with '_'. +std::string Base64SafeEncode(const std::vector& bin_input) { + if (bin_input.empty()) { + return std::string(); + } + return Base64EncodeInternal(bin_input.data(), bin_input.size(), + kBase64SafeCodes); +} + +std::string Base64SafeEncode(const std::string& bin_input) { + if (bin_input.empty()) { + return std::string(); + } + return Base64EncodeInternal( + reinterpret_cast(bin_input.data()), bin_input.size(), + kBase64SafeCodes); +} + +std::vector Base64SafeDecode(const std::string& b64_input) { + if (b64_input.empty()) { + return std::vector(); + } + return Base64DecodeInternal(b64_input.data(), b64_input.size(), + kBase64SafeCodes); +} + +// URL/Filename Safe Base64 encoding without padding. + +std::string Base64SafeEncodeNoPad(const std::vector& bin_input) { + std::string b64_output = Base64SafeEncode(bin_input); + // Output size: ceiling [ bin_input.size() * 4 / 3 ]. + b64_output.resize((bin_input.size() * 4 + 2) / 3); + return b64_output; +} + +std::string Base64SafeEncodeNoPad(const std::string& bin_input) { + std::string b64_output = Base64SafeEncode(bin_input); + // Output size: ceiling [ bin_input.size() * 4 / 3 ]. + b64_output.resize((bin_input.size() * 4 + 2) / 3); + return b64_output; +} + +// Host to Network/Network to Host conversion. + +// Convert to big endian (network-byte-order) +int64_t htonll64(int64_t x) { + union { + uint32_t array[2]; + int64_t number; + } mixed; + mixed.number = 1; + if (mixed.array[0] == 1) { // Little Endian. + mixed.number = x; + uint32_t temp = mixed.array[0]; + mixed.array[0] = htonl(mixed.array[1]); + mixed.array[1] = htonl(temp); + return mixed.number; + } else { // Big Endian. + return x; + } +} + +// Encode unsigned integer into a big endian formatted string +std::string EncodeUint32(unsigned int u) { + std::string s; + s.push_back((u >> 24) & 0xFF); + s.push_back((u >> 16) & 0xFF); + s.push_back((u >> 8) & 0xFF); + s.push_back(u & 0xFF); + return s; +} + +} // namespace wvutil diff --git a/wvutil/src/timer.cpp b/wvutil/src/timer.cpp new file mode 100644 index 0000000..7383f55 --- /dev/null +++ b/wvutil/src/timer.cpp @@ -0,0 +1,104 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. +// +// Timer class - provides a simple Android specific timer implementation + +#include "timer.h" + +#include + +#include +#include +#include +#include + +namespace wvutil { + +class Timer::Impl : virtual public android::RefBase { + private: + class ImplThread : public android::Thread { + public: + ImplThread() : Thread(false), handler_(NULL), period_ns_(0) {} + virtual ~ImplThread() {}; + + bool Start(TimerHandler *handler, uint32_t time_in_secs) { + handler_ = handler; + period_ns_ = time_in_secs * 1000000000ll; + return run("wvutil::Timer::Impl") == android::NO_ERROR; + } + + void Stop() { + { + android::Mutex::Autolock autoLock(lock_); + stop_condition_.signal(); + } + requestExitAndWait(); + } + + private: + virtual bool threadLoop() { + android::Mutex::Autolock autoLock(lock_); + stop_condition_.waitRelative(lock_, period_ns_); + handler_->OnTimerEvent(); + return true; + } + + TimerHandler *handler_; + uint64_t period_ns_; + android::Mutex lock_; + android::Condition stop_condition_; + + CORE_DISALLOW_COPY_AND_ASSIGN(ImplThread); + }; + + android::sp impl_thread_; + + public: + Impl() {} + virtual ~Impl() {}; + + bool Start(TimerHandler *handler, uint32_t time_in_secs) { + impl_thread_ = new ImplThread(); + return impl_thread_->Start(handler, time_in_secs); + } + + void Stop() { + impl_thread_->Stop(); + impl_thread_.clear(); + } + + bool IsRunning() { + return (impl_thread_ != NULL) && (impl_thread_->isRunning()); + } + + CORE_DISALLOW_COPY_AND_ASSIGN(Impl); +}; + +Timer::Timer() : impl_(new Timer::Impl()) { +} + +Timer::~Timer() { + if (IsRunning()) + Stop(); + + delete impl_; + impl_ = NULL; +} + +bool Timer::Start(TimerHandler *handler, uint32_t time_in_secs) { + if (!handler || time_in_secs == 0) + return false; + + return impl_->Start(handler, time_in_secs); +} + +void Timer::Stop() { + impl_->Stop(); +} + +bool Timer::IsRunning() { + return impl_->IsRunning(); +} + +} // namespace wvutil