diff --git a/Android.bp b/Android.bp new file mode 100644 index 0000000..6506e8b --- /dev/null +++ b/Android.bp @@ -0,0 +1,40 @@ +// ----------------------------------------------------------------------------- +// 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", + ], + + 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..0e642f8 100644 --- a/README +++ b/README @@ -0,0 +1 @@ +This repo contains Widevine MediaCas client code that works with Android R. diff --git a/oemcrypto/Android.bp b/oemcrypto/Android.bp new file mode 100644 index 0000000..a9f9fe8 --- /dev/null +++ b/oemcrypto/Android.bp @@ -0,0 +1,10 @@ +// Remove two lines below to build reference +// implementation of CAS oemcrypto. +soong_namespace { +} + +cc_library_headers { + name: "oemcastroheaders", + export_include_dirs: ["include"], + proprietary: true, +} diff --git a/oemcrypto/include/OEMCryptoCAS.h b/oemcrypto/include/OEMCryptoCAS.h new file mode 100644 index 0000000..7d1852e --- /dev/null +++ b/oemcrypto/include/OEMCryptoCAS.h @@ -0,0 +1,5138 @@ +// 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. + +/********************************************************************* + * OEMCryptoCAS.h + * + * Reference APIs needed to support Widevine's CAS-related crypto algorithms. + * + * See the document "Widevine Security Integration Guide for Conditional + * Access Systems" for a description of this API. + * + *********************************************************************/ + +#ifndef OEMCRYPTO_CAS_H_ +#define OEMCRYPTO_CAS_H_ + +#include +#include +#include + +#include "OEMCryptoCENCCommon.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef uint32_t OEMCrypto_SESSION; + +/* + * 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; + +/* + * OEMCrypto_DestBufferDesc Structure + * + * Description: + * 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. + * + * Fields: + * [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. + * [in] address: A pointer to the address in memory to begin writing output. + * [in] address_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. + * [in] handle: An opaque handle to a secure buffer. The meaning of this + * handle is platform-specific. + * [in] handle_length: The length of the data contained in the secure buffer. + * [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. + * [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 enum OEMCryptoBufferType { + OEMCrypto_BufferType_Clear, + OEMCrypto_BufferType_Secure, + OEMCrypto_BufferType_Direct +} OEMCryptoBufferType; + +typedef struct { + OEMCryptoBufferType type; + union { + struct { // type == OEMCrypto_BufferType_Clear + OEMCrypto_SharedMemory* address; + size_t address_length; + } clear; + struct { // type == OEMCrypto_BufferType_Secure + void* handle; + size_t handle_length; + size_t offset; + } secure; + struct { // type == OEMCrypto_BufferType_Direct + bool is_video; + } direct; + } buffer; +} OEMCrypto_DestBufferDesc; + +/* + * OEMCrypto_InputOutputPair Structure + * + * Description: + * This structure is used as parameters in the OEMCrypto_DecryptCENC function. + * + * Fields: + * [in] input_data: An unaligned pointer to this sample from the stream. + * [in] input_data_length: The length of this sample in the stream, in bytes. + * [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; + +/* + * OEMCrypto_SubSampleDescription Structure + * + * Description: + * 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. + * + * Fields: + * [in] num_bytes_clear: The number of unprotected bytes in this subsample. + * The clear bytes come before the encrypted bytes. + * [in] num_bytes_encrypted: The number of protected bytes in this subsample. + * The protected bytes come after the clear bytes. + * [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. + * [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 + +/* + * OEMCrypto_SampleDescription Structure + * + * Description: + * This structure is used as parameters in the OEMCrypto_DecryptCENC function. + * + * Fields: + * [in] buffers: A structure containing information about the input and + * output buffers. + * [in] iv: A 16-byte array containing the IV for the initial subsample of + * the sample. + * [in] subsamples: A caller-owned array of OEMCrypto_SubSampleDescription + * structures. Each entry in this array describes one subsample in the sample. + * [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; + +/* + * OEMCrypto_CENCEncryptPatternDesc Structure + * + * Description: + * This structure is used as parameters in the OEMCrypto_DecryptCENC function. + * + * Fields: + * [in] encrypt: The number of 16-byte crypto blocks to encrypt. + * [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 SelectKey to prepare a key for either CTR + * decryption or CBC decryption. + */ +typedef enum OEMCryptoCipherMode { + OEMCrypto_CipherMode_CTR, + OEMCrypto_CipherMode_CBC, + OEMCrypto_CipherMode_CSA2, + OEMCrypto_CipherMode_CSA3, + OEMCrypto_CipherMode_OFB, + OEMCrypto_CipherMode_SCTE, +} OEMCryptoCipherMode; + +/* + * OEMCrypto_EntitledContentKeyObject + * 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. + * entitlement_key_id - entitlement key id to be matched to key table. + * entitlement_key_id_length - length of entitlment_key_id in bytes (1 to 16). + * content_key_id - content key id to be loaded into key table. + * content_key_id_length - length of content key id in bytes (1 to 16). + * key_data_iv - the IV for performing AES-256-CBC decryption of the key data. + * key_data - encrypted content key data. + * key_data_length - length of key_data - 16 or 32 depending on intended use. + */ +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; + +/* + * OEMCrypto_EntitledCasKeyObject + * 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. + * entitlement_key_id - entitlement key id to be matched to key table. + * content_key_id - content key id to be loaded into key table. + * content_key_data_iv - the IV for performing AES-256-CBC decryption of the key data. + * content_key_data - encrypted content key data. + * content_iv - the 16 byte iv used to decrypt content. + * cipher_mode - the cipher mode to be used to decrypt the content. + */ +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_EntitledCasKeyObject; + +/* + * OEMCrypto_KeyRefreshObject + * This structure is being deprecated. It is only used for legacy licenses. + * Points to the relevant fields for renewing a content key. The fields are + * extracted from the License Renewal Response message offered to + * OEMCrypto_RefreshKeys(). Each field points to one of the components of + * the key. + * key_id - the unique id of this key. + * key_control_iv - the IV for performing AES-128-CBC decryption of the + * key_control field. 16 bytes. + * key_control - the key control block. It is encrypted (AES-128-CBC) with + * the content key from the key_data field. 16 bytes. + * + * The key_data is unchanged from the original OEMCrypto_LoadKeys() call. Some + * Key Control Block fields, especially those related to key lifetime, may + * change. + * + * The memory for the OEMCrypto_KeyRefreshObject fields is allocated and freed + * by the caller of OEMCrypto_RefreshKeys(). + */ +typedef struct { + OEMCrypto_Substring key_id; + OEMCrypto_Substring key_control_iv; + OEMCrypto_Substring key_control; +} OEMCrypto_KeyRefreshObject; + +/* + * OEMCrypto_Algorithm + * 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; + +/* + * 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. + */ +#if 0 // If your compiler supports __attribute__((packed)). +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 + +/* + * OEMCrypto_Clock_Security_Level. + * Valid values for clock_security_level in OEMCrypto_PST_Report. + */ +typedef enum OEMCrypto_Clock_Security_Level { + kInsecureClock = 0, + kSecureTimer = 1, + 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) + +/* + * 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.0 + 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. + HDCP_NO_DIGITAL_OUTPUT = 0xff // No digital output. +} OEMCrypto_HDCP_Capability; + +/* Return value for OEMCrypto_GetProvisioningMethod(). */ +typedef enum OEMCrypto_ProvisioningMethod { + OEMCrypto_ProvisioningError = 0, // Device cannot be provisioned. + OEMCrypto_DrmCertificate = 1, // Device has baked in DRM certificate + // (level 3 only) + OEMCrypto_Keybox = 2, // Device has factory installed unique keybox. + OEMCrypto_OEMCertificate = 3 // Device has factory installed OEM certificate. +} OEMCrypto_ProvisioningMethod; + +/* + * 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. + */ +// 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 _oecc23 +#define OEMCrypto_Generic_Encrypt _oecc24 +#define OEMCrypto_Generic_Decrypt _oecc25 +#define OEMCrypto_Generic_Sign _oecc26 +#define OEMCrypto_Generic_Verify _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 _oecc90 +#define OEMCrypto_RefreshKeys _oecc91 +#define OEMCrypto_LoadEntitledContentKeys _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 _oecc105 +#define OEMCrypto_LoadDRMPrivateKey _oecc107 +#define OEMCrypto_MinorAPIVersion _oecc108 +#define OEMCrypto_CreateEntitledKeySession _oecc109 +#define OEMCrypto_RemoveEntitledKeySession _oecc110 +#define OEMCrypto_LoadCasECMKeys _ocas100 + +// clang-format on + +/* + * OEMCrypto_SetSandbox + * + * Description: + * 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" above 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. + * + * Parameters: + * [in] sandbox_id: a short string unique to the current sandbox. + * [in] sandbox_id_length: length of sandbox_id. + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_INIT_FAILED failed to initialize crypto hardware + * 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); + +/* + * OEMCrypto_Initialize + * + * Description: + * Initialize the crypto firmware/hardware. + * + * Parameters: + * None + * + * Returns: + * OEMCrypto_SUCCESS success + * 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); + +/* + * OEMCrypto_Terminate + * + * Description: + * Closes the crypto operation and releases all related resources. + * + * Parameters: + * None + * + * Returns: + * OEMCrypto_SUCCESS success + * 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); + +/* + * OEMCrypto_OpenSession + * + * Description: + * 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. + * + * Parameters: + * [out] session: an opaque handle that the crypto firmware uses to identify + * the session. + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_TOO_MANY_SESSIONS failed because too many sessions are open + * OEMCrypto_ERROR_OPEN_SESSION_FAILED there is a resource issue or the + * security engine is not properly initialized. + * 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); + +/* + * OEMCrypto_CloseSession + * + * Description: + * 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. + * + * Parameters: + * [in] session: handle for the session to be closed. + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_INVALID_SESSION no open session with that id. + * OEMCrypto_ERROR_CLOSE_SESSION_FAILED illegal/unrecognized handle or the + * security engine is not properly initialized. + * 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); + +/* + * OEMCrypto_GenerateDerivedKeys + * + * Description: + * 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_LoadKeys(). The device key from the keybox is used as the key + * for the AES-128-CMAC. + * + * Parameters: + * [in] session: handle for the session to be used. + * [in] mac_key_context: pointer to memory containing context data for + * computing the HMAC generation key. + * [in] mac_key_context_length: length of the HMAC key context data, in bytes. + * [in] enc_key_context: pointer to memory containing context data for + * computing the encryption key. + * [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. + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_NO_DEVICE_KEY + * OEMCrypto_ERROR_INVALID_SESSION + * OEMCrypto_ERROR_INVALID_CONTEXT + * OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * OEMCrypto_ERROR_UNKNOWN_FAILURE + * OEMCrypto_ERROR_BUFFER_TOO_LARGE + * OEMCrypto_ERROR_SESSION_LOST_STATE + * OEMCrypto_ERROR_SYSTEM_INVALIDATED + * + * Buffer Sizes: + * OEMCrypto shall support mac_key_context and enc_key_context sizes as + * described in the section OEMCrypto_ResourceRatingTier. + * 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); + +/* + * OEMCrypto_DeriveKeysFromSessionKey + * + * Description: + * 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 LoadKeys + * or LoadLicense proceed in the same manner for license requests using RSA + * or using a Widevine keybox token. + * + * Verification: + * If the RSA key's allowed_schemes is not kSign_RSASSA_PSS, then no keys are + * derived and the error OEMCrypto_ERROR_INVALID_RSA_KEY is returned. An RSA + * key cannot be used for both deriving session keys and also for PKCS1 + * signatures. + * + * Parameters: + * [in] session: handle for the session to be used. + * [in] derivation_key: session key, encrypted with the public RSA key (from + * the DRM certifcate) using RSA-OAEP. + * [in] derivation_key_length: length of derivation_key, in bytes. + * [in] mac_key_context: pointer to memory containing context data for + * computing the HMAC generation key. + * [in] mac_key_context_length: length of the HMAC key context data, in bytes. + * [in] enc_key_context: pointer to memory containing context data for + * computing the encryption key. + * [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. + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_DEVICE_NOT_RSA_PROVISIONED + * OEMCrypto_ERROR_INVALID_SESSION + * OEMCrypto_ERROR_INVALID_CONTEXT + * OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * OEMCrypto_ERROR_UNKNOWN_FAILURE + * OEMCrypto_ERROR_BUFFER_TOO_LARGE + * OEMCrypto_ERROR_SESSION_LOST_STATE + * OEMCrypto_ERROR_SYSTEM_INVALIDATED + * + * Buffer Sizes: + * OEMCrypto shall support mac_key_context and enc_key_context sizes as + * described in the section OEMCrypto_ResourceRatingTier. + * 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); + +/* + * OEMCrypto_GenerateNonce + * + * Description: + * 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. + * + * Parameters: + * [in] session: handle for the session to be used. + * [out] nonce: pointer to memory to receive the computed nonce. + * + * Results: + * nonce: the nonce is also stored in secure memory. Each session should + * store 4 nonces. + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_INVALID_SESSION + * OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * OEMCrypto_ERROR_UNKNOWN_FAILURE + * OEMCrypto_ERROR_SESSION_LOST_STATE + * 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_PrepAndSignLicenseRequest + * + * Description: + * OEMCrypto will use ODK_PrepareCoreLicenseRequest to prepare the core + * message. If it returns OEMCrypto_SUCCESS, then OEMCrypto shall sign the + * 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 compute a hash of the core license request. The core + * license request is the buffer starting at message and with length + * core_message_size. The hash will be saved with the session and verified + * that it matches a hash in the license response. + * + * 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. + * + * Parameters: + * [in] session: handle for the session to be used. + * [in/out] message: Pointer to memory for the entire message. Modified by + * OEMCrypto via the ODK library. + * [in] message_length: length of the entire message buffer. + * [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. + * [out] signature: pointer to memory to receive the computed signature. + * [in/out] signature_length: (in) length of the signature buffer, in bytes. + * (out) actual length of the signature, in bytes. + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_INVALID_SESSION + * OEMCrypto_ERROR_SHORT_BUFFER if signature buffer is not large enough to + * hold the signature. + * OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * OEMCrypto_ERROR_UNKNOWN_FAILURE + * OEMCrypto_ERROR_SIGNATURE_FAILURE + * OEMCrypto_ERROR_BUFFER_TOO_LARGE + * OEMCrypto_ERROR_SESSION_LOST_STATE + * OEMCrypto_ERROR_SYSTEM_INVALIDATED + * + * Buffer Sizes: + * 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_PrepAndSignRenewalRequest + * + * Description: + * 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_LoadKeys, + * 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_LoadKeys, 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. + * + * Parameters: + * [in] session: handle for the session to be used. + * [in/out] message: Pointer to memory for the entire message. Modified by + * OEMCrypto via the ODK library. + * [in] message_length: length of the entire message buffer. + * [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. + * [out] signature: pointer to memory to receive the computed signature. + * [in/out] signature_length: (in) length of the signature buffer, in bytes. + * (out) actual length of the signature, in bytes. + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_INVALID_SESSION + * OEMCrypto_ERROR_SHORT_BUFFER if signature buffer is not large enough to + * hold the signature. + * OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * OEMCrypto_ERROR_UNKNOWN_FAILURE + * OEMCrypto_ERROR_BUFFER_TOO_LARGE + * OEMCrypto_ERROR_SESSION_LOST_STATE + * OEMCrypto_ERROR_SYSTEM_INVALIDATED + * + * Buffer Sizes: + * 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); + +/* + * OEMCrypto_PrepAndSignProvisioningRequest + * + * Description: + * OEMCrypto will use OEMCrypto_PrepAndSignProvisioningRequest, 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 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 a device that has an OEM Certificate, i.e. Provisioning 3.0, 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. + * + * 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. + * + * Parameters: + * [in] session: handle for the session to be used. + * [in/out] message: Pointer to memory for the entire message. Modified by + * OEMCrypto via the ODK library. + * [in] message_length: length of the entire message buffer. + * [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. + * [out] signature: pointer to memory to receive the computed signature. + * [in/out] signature_length: (in) length of the signature buffer, in bytes. + * (out) actual length of the signature, in bytes. + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_INVALID_SESSION + * OEMCrypto_ERROR_SHORT_BUFFER if signature buffer is not large enough to + * hold the signature. + * OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * OEMCrypto_ERROR_UNKNOWN_FAILURE + * OEMCrypto_ERROR_BUFFER_TOO_LARGE + * OEMCrypto_ERROR_SESSION_LOST_STATE + * OEMCrypto_ERROR_SYSTEM_INVALIDATED + * + * Buffer Sizes: + * 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); + +/* + * OEMCrypto_LoadSRM + * + * Description: + * Verify and install a new SRM file. The device shall install the new file + * only if verification passes. If verification fails, the existing SRM will + * be left in place. Verification is defined by DCP, and includes + * verification of the SRM's signature and verification that the SRM version + * number will not be decreased. See the section HDCP SRM Update above for + * more details about the SRM. This function is for devices that support HDCP + * v2.2 or higher and wish to receive 4k content. + * + * Parameters: + * [in] bufer: buffer containing the SRM + * [in] buffer_length: length of the SRM, in bytes. + * + * Returns: + * OEMCrypto_SUCCESS - if the file was valid and was installed. + * OEMCrypto_ERROR_INVALID_CONTEXT - if the SRM version is too low, or the + * file is corrupted. + * OEMCrypto_ERROR_SIGNATURE_FAILURE - If the signature is invalid. + * OEMCrypto_ERROR_BUFFER_TOO_LARGE - if the buffer is too large for the + * device. + * OEMCrypto_ERROR_NOT_IMPLEMENTED + * OEMCrypto_ERROR_SYSTEM_INVALIDATED + * + * Buffer Sizes: + * The size of the buffer is determined by the HDCP specification. + * + * 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 changed in API version 13. + */ +OEMCryptoResult OEMCrypto_LoadSRM(const uint8_t* buffer, size_t buffer_length); + +/* + * OEMCrypto_LoadKeys + * + * Description: + * Install a set of keys for performing decryption in the current session. + * This function will be deprecated and will only be used for legacy license + * from a license server that does not yet support the v16 interface. + * + * The relevant fields have been extracted from the License Response protocol + * message, but the entire message and associated signature are provided so + * the message can be verified (using HMAC-SHA256 with the derived + * mac_key[server]). If the signature verification fails, ignore all other + * arguments and return OEMCrypto_ERROR_SIGNATURE_FAILURE. Otherwise, add the + * keys to the session context. + * + * The keys will be decrypted using the current encrypt_key (AES-128-CBC) and + * the IV given in the KeyObject. 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. + * + * 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 calls to + * OEMCrypto_RefreshKeys(). 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]. + * + * The mac_key and encrypt_key were generated and stored by the previous call + * to OEMCrypto_GenerateDerivedKeys() or + * OEMCrypto_DeriveKeysFromSessionKey(). The nonce was generated and stored + * in the session's nonce_values by the previous call to + * OEMCrypto_GenerateNonce(). + * + * This session's elapsed time clock is started at 0. The clock will be used + * in OEMCrypto_DecryptCENC(). + * + * 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. + * + * If the parameter 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 parameter 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 at least 20 keys per session. This allows a single + * license to contain separate keys for 3 key rotations (previous interval, + * current interval, next interval) times 4 content keys (audio, SD, HD, UHD) + * plus up to 8 keys for watermarks. + * + * After a call to OEMCrypto_LoadKeys, 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. + * 1. 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). + * 2. If there already is a license loaded into this session, return + * OEMCrypto_ERROR_LICENSE_RELOAD. + * 3. 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. + * 4. 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. + * 5. Each key's control block, after decryption, shall have a valid + * verification field. If not, return OEMCrypto_ERROR_INVALID_CONTEXT. + * 6. 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. + * 7. 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. + * 8. If the key control block has a nonzero Replay_Control, then the + * verification described below is also performed. + * 9. 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. + * 10. If key_array_length == 0, then return + * OEMCrypto_ERROR_INVALID_CONTEXT. + * 11. 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. + * 12. 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. + * Usage Table and Provider Session Token (pst) + * 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. 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 LoadKeys 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 LoadKeys 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 + * LoadKeys 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. + * + * Timer Update + * After verification, the session's clock and timer values are updated by + * calling the function ODK_InitializeV15Values as described in the document + * "Widevine Core Message Serialization". + * + * SRM Restriction Data + * + * If any key control block has the flag SRMVersionRequired set, then the + * following verification is also performed. + * + * 1. The substring srm_restriction_data must have nonzero length and must + * satisfy the range check described above. If not, return + * OEMCrypto_ERROR_INVALID_CONTEXT. + * 2. The first 8 bytes of srm_restriction_data must match the string + * "HDCPDATA". If not, return OEMCrypto_ERROR_INVALID_CONTEXT. + * 3. 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, LoadKeys 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 SelectKey for these keys while + * there is an external display will return OEMCrypto_ERROR_INSUFFICIENT_HDCP + * at that time. + * + * Parameters: + * [in] session: crypto session identifier. + * [in] message: pointer to memory containing message to be verified. + * [in] message_length: length of the message, in bytes. + * [in] signature: pointer to memory containing the signature. + * [in] signature_length: length of the signature, in bytes. + * [in] enc_mac_keys_iv: IV for decrypting new mac_key. Size is 128 bits. + * [in] enc_mac_keys: encrypted mac_keys for generating new mac_keys. Size is + * 512 bits. + * [in] key_array_length: number of keys present. + * [in] key_array: set of keys to be installed. + * [in] pst: the Provider Session Token. + * [in] srm_restriction_data: optional data specifying the minimum SRM + * version. + * [in] license_type: specifies if the license contains content keys or + * entitlement keys. + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_NO_DEVICE_KEY + * OEMCrypto_ERROR_INVALID_SESSION + * OEMCrypto_ERROR_UNKNOWN_FAILURE + * OEMCrypto_ERROR_INVALID_CONTEXT + * OEMCrypto_ERROR_SIGNATURE_FAILURE + * OEMCrypto_ERROR_INVALID_NONCE + * OEMCrypto_ERROR_TOO_MANY_KEYS + * OEMCrypto_ERROR_NOT_IMPLEMENTED + * OEMCrypto_ERROR_BUFFER_TOO_LARGE + * OEMCrypto_ERROR_SESSION_LOST_STATE + * OEMCrypto_ERROR_SYSTEM_INVALIDATED + * OEMCrypto_ERROR_LICENSE_RELOAD + * + * Buffer Sizes: + * 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_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); + +/* + * OEMCrypto_LoadLicense + * + * Description: + * 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. 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. + * + * 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 section on + * Resource Rating Tiers in this document. + * + * 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. + * + * 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. 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 LoadKeys 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 LoadKeys 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 + * LoadKeys 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, LoadKeys 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 SelectKey for these keys while + * there is an external display will return OEMCrypto_ERROR_INSUFFICIENT_HDCP + * at that time. + * + * Parameters: + * [in] session: crypto session identifier. + * [in] message: pointer to memory containing data. + * [in] message_length: length of the message, in bytes. + * [in] core_message_length: length of the core submessage, in bytes. + * [in] signature: pointer to memory containing the signature. + * [in] signature_length: length of the signature, in bytes. + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_NO_DEVICE_KEY + * OEMCrypto_ERROR_INVALID_SESSION + * OEMCrypto_ERROR_UNKNOWN_FAILURE + * OEMCrypto_ERROR_INVALID_CONTEXT + * OEMCrypto_ERROR_SIGNATURE_FAILURE + * OEMCrypto_ERROR_INVALID_NONCE + * OEMCrypto_ERROR_TOO_MANY_KEYS + * OEMCrypto_ERROR_NOT_IMPLEMENTED + * OEMCrypto_ERROR_BUFFER_TOO_LARGE + * OEMCrypto_ERROR_SESSION_LOST_STATE + * OEMCrypto_ERROR_SYSTEM_INVALIDATED + * OEMCrypto_ERROR_LICENSE_RELOAD + * + * Buffer Sizes: + * 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); + +/* + * OEMCrypto_LoadEntitledContentKeys + * + * Description: + * Load content keys into a session which already has entitlement keys + * loaded. This function will only be called for a session after a call to + * OEMCrypto_LoadKeys with the parameter type license_type equal to + * OEMCrypto_EntitlementLicense. This function may be called multiple times + * for the same session. + * + * If the session does not have license_type equal to + * OEMCrypto_EntitlementLicense, 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 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. 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. + * 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 SelectKey is called. + * + * Parameters: + * [in] session: handle for the entitled key session to be used. + * [in] message: pointer to memory containing message to be verified. + * [in] message_length: length of the message, in bytes. + * [in] key_array_length: number of keys present. + * [in] key_array: set of key updates. + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_INVALID_SESSION + * OEMCrypto_ERROR_INVALID_CONTEXT + * OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * OEMCrypto_ERROR_UNKNOWN_FAILURE + * OEMCrypto_KEY_NOT_ENTITLED + * OEMCrypto_ERROR_SESSION_LOST_STATE + * OEMCrypto_ERROR_SYSTEM_INVALIDATED + * 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_LoadEntitledContentKeys( + OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, + size_t key_array_length, + const OEMCrypto_EntitledContentKeyObject* key_array); + +/* + * OEMCrypto_LoadCasECMKeys + * + * Description: + * Load content keys into a session which already has entitlement + * keys loaded. This function will only be called for a session after a call + * to OEMCrypto_LoadKeys with the parameter type license_type equal to + * OEMCrypto_EntitlementLicense. This function may be called multiple times + * for the same session. + * + * If the session does not have license_type equal to + * OEMCrypto_EntitlementLicense, 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 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, and 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 descrambler + * if present. + * + * 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 appropriate to + * store the encrypted content key data in the key table, and defer decrypting + * it until the function SelectKey is called. + * + * Parameters: + * session (in) - handle for the session to be used. + * even_key (in) - key update for the even ecm key. + * odd_key (in) - key update for the odd ecm key. + * + * Returns + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_INVALID_SESSION + * OEMCrypto_ERROR_INVALID_CONTEXT + * OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * OEMCrypto_ERROR_UNKNOWN_FAILURE + * OEMCrypto_KEY_NOT_ENTITLED + * Threading + * + * This function may be called simultaneously with functions on other + * sessions, but not with other functions on this session. + * + * Version + * This method is changed in API version 16. + */ +OEMCryptoResult OEMCrypto_LoadCasECMKeys( + OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, + const OEMCrypto_EntitledCasKeyObject* even_key, + const OEMCrypto_EntitledCasKeyObject* odd_key); + +/* + * OEMCrypto_RefreshKeys + * + * Description: + * Updates the license clock values to allow playback to continue. This + * function is being deprecated and is only used for version v15 licenses -- + * i.e. offline license saved before an update or licenses from a server that + * has not update to the v16 license server SDK. + * + * OEMCrypto shall compute the signature of the message using + * mac_key[server], and 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). + * + * The key control from the first OEMCrypto_KeyRefreshObject in the key_array + * shall be extracted. If it is encrypted, as described below, it shall be + * decrypted. The duration from the key control shall be extracted and + * converted to host byte order. This duration shall be passed to the + * function ODK_RefreshV15Values as the parameter new_key_duration. + * + * If the KeyRefreshObject's key_control_iv has zero length, then the + * key_control is not encrypted. If the key_control_iv is specified, then + * key_control is encrypted with the first 128 bits of the corresponding + * content key. + * + * If the KeyRefreshObject's key_id has zero length, then it is an error for + * the key_control_iv to have nonzero length. OEMCrypto shall return an error + * of OEMCrypto_ERROR_INVALID_CONTEXT. + * + * If the session's license_type is OEMCrypto_ContentLicense, and the + * KeyRefreshObject's key_id is not null, then the entry in the keytable with + * the matching content_key_id is used. + * + * If the session's license_type is OEMCrypto_EntitlementLicense, and the + * KeyRefreshObject's key_id is not null, then the entry in the keytable with + * the matching entitlment_key_id is used. + * + * The function ODK_RefreshV15Values shall be called to update the clock + * values. See the document "Widevine Core Message Serialization" for the + * documentation of the ODK library functions. + * + * If ODK_RefreshV15Values returns + * + * - ODK_SET_TIMER: Success. The timer should be reset to the specified + * timer value. + * - ODK_DISABLE_TIMER: Success, but disable timer. Unlimited playback is + * allowed. + * - ODK_TIMER_EXPIRED: Set timer as disabled. Playback is not allowed. + * - ODK_STALE_RENEWAL: This renewal is not the most recently signed. It is + * rejected. Return this error + * - Any other error - OEMCrypto shall pass any other error up to the + * caller of OEMCrypto_RefreshKeys. + * + * NOTE: OEMCrypto_LoadKeys() must be called first to load the keys into the + * session. + * + * Parameters: + * [in] session: handle for the session to be used. + * [in] message: pointer to memory containing message to be verified. + * [in] message_length: length of the message, in bytes. + * [in] signature: pointer to memory containing the signature. + * [in] signature_length: length of the signature, in bytes. + * [in] num_keys: number of keys present. + * [in] key_array: set of key updates. + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_NO_DEVICE_KEY + * OEMCrypto_ERROR_INVALID_SESSION + * OEMCrypto_ERROR_INVALID_CONTEXT + * OEMCrypto_ERROR_SIGNATURE_FAILURE + * OEMCrypto_ERROR_INVALID_NONCE + * OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * OEMCrypto_ERROR_UNKNOWN_FAILURE + * OEMCrypto_ERROR_BUFFER_TOO_LARGE + * OEMCrypto_ERROR_NO_CONTENT_KEY + * OEMCrypto_ERROR_SESSION_LOST_STATE + * OEMCrypto_ERROR_SYSTEM_INVALIDATED + * + * Buffer Sizes: + * 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_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_LoadRenewal + * + * Description: + * 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. Legacy licenses will use + * the function OEMCrypto_RefreshKeys instead of OEMCrypto_LoadRenewal. + * + * 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). + * + * Parameters: + * [in] session: handle for the session to be used. + * [in] message: pointer to memory containing message to be verified. + * [in] message_length: length of the message, in bytes. + * [in] core_message_length: length of the core submessage, in bytes. + * [in] signature: pointer to memory containing the signature. + * [in] signature_length: length of the signature, in bytes. + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_NO_DEVICE_KEY + * OEMCrypto_ERROR_INVALID_SESSION + * OEMCrypto_ERROR_INVALID_CONTEXT + * OEMCrypto_ERROR_SIGNATURE_FAILURE + * OEMCrypto_ERROR_INVALID_NONCE + * OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * OEMCrypto_ERROR_UNKNOWN_FAILURE + * OEMCrypto_ERROR_BUFFER_TOO_LARGE + * OEMCrypto_ERROR_SESSION_LOST_STATE + * OEMCrypto_ERROR_SYSTEM_INVALIDATED + * ODK_STALE_RENEWAL + * + * Buffer Sizes: + * 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); + +/* + * OEMCrypto_QueryKeyControl + * + * Description: + * 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 LoadKeys 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. + * + * Parameters: + * [in] session: handle for the crypto or entitled key session to be used. + * [in] content_key_id: The unique id of the key of interest. + * [in] content_key_id_length: The length of key_id, in bytes. From 1 to 16, + * inclusive. + * [out] key_control_block: A caller-owned buffer. + * [in/out] key_control_block_length. The length of key_control_block buffer. + * + * Returns: + * OEMCrypto_SUCCESS + * OEMCrypto_ERROR_INVALID_CONTEXT + * OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * OEMCrypto_ERROR_UNKNOWN_FAILURE + * OEMCrypto_ERROR_SESSION_LOST_STATE + * OEMCrypto_ERROR_SYSTEM_INVALIDATED + * 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); + +/* + * OEMCrypto_SelectKey + * + * Description: + * Select a content key and install it in the hardware key ladder for + * subsequent decryption operations (OEMCrypto_DecryptCENC()) for this + * session. The specified key must have been previously "installed" via + * OEMCrypto_LoadKeys(), OEMCrypto_LoadLicense, or + * OEMCrypto_LoadEntitledContentKeys(). + * + * 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_CBC. Continue to + * use this key for this session until OEMCrypto_SelectKey() is called again, + * or until OEMCrypto_CloseSession() is called. + * + * 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 -- SelectKey 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 + * -- SelectKey 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. + * + * Parameters: + * [in] session: crypto session or entitled key session identifier. + * [in] content_key_id: pointer to the content Key ID. + * [in] content_key_id_length: length of the content Key ID, in bytes. From + * 1 to 16, inclusive. + * [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. + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_KEY_EXPIRED - if the session's timer has expired + * OEMCrypto_ERROR_INVALID_SESSION crypto session ID invalid or not open + * OEMCrypto_ERROR_NO_DEVICE_KEY failed to decrypt device key + * OEMCrypto_ERROR_NO_CONTENT_KEY failed to decrypt content key + * OEMCrypto_ERROR_CONTROL_INVALID invalid or unsupported control input + * OEMCrypto_ERROR_KEYBOX_INVALID cannot decrypt and read from Keybox + * OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * OEMCrypto_ERROR_UNKNOWN_FAILURE + * OEMCrypto_ERROR_KEY_EXPIRED + * OEMCrypto_ERROR_ANALOG_OUTPUT + * OEMCrypto_ERROR_INSUFFICIENT_HDCP + * OEMCrypto_ERROR_NO_CONTENT_KEY + * OEMCrypto_ERROR_SESSION_LOST_STATE + * OEMCrypto_ERROR_SYSTEM_INVALIDATED + * 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 changed in API version 17. + */ +OEMCryptoResult OEMCrypto_SelectKey(OEMCrypto_SESSION session, + const uint8_t* content_key_id, + size_t content_key_id_length, + OEMCryptoCipherMode cipher_mode); + +/* + * OEMCrypto_DecryptCENC + * + * Description: + * Decrypts or copies a series of input payloads into output buffers using + * the session context indicated by the session 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_SelectKey. For the encrypted + * portion of subsamples, the content key associated with the session 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_Decrypt 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_CTR or + * OEMCrypto_CipherMode_CBC, was already specified in the call to + * OEMCrypto_SelectKey. 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_CTR 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: + * + * (See drawing in "Widevine Modular DRM Security Integration Guide") + * + * 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_CBC 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: + * + * (See drawing in "Widevine Modular DRM Security Integration Guide") + * + * 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: + * + * (See drawing in "Widevine Modular DRM Security Integration Guide") + * + * 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. + * + * If the session has an entry in the Usage Table, then OEMCrypto must update + * the time_of_last_decrypt. If the status of the entry is "unused", then + * change the status to "active" and set the time_of_first_decrypt. + * + * 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. + * + * (See drawing in "Widevine Modular DRM Security Integration Guide") + * + * 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. + * + * (See drawing in "Widevine Modular DRM Security Integration Guide") + * + * 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. This call shall copy clear data even when there + * are no keys loaded, or there is no selected key. + * 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 the + * document "License Duration and Renewal" for handling the return value of + * these ODK functions. + * 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 session 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. + * + * Parameters: + * [in] session: Crypto or entitled key session identifier. The session in + * which decrypt is to be performed. + * [in] samples: A caller-owned array of OEMCrypto_SampleDescription + * structures. Each entry in this array contains one sample of the + * content. + * [in] samples_length: The length of the array pointed to by the samples + * parameter. + * [in] pattern: A caller-owned structure indicating the encrypt/skip pattern + * as specified in the ISO-CENC standard. + * + * Returns: + * OEMCrypto_SUCCESS + * OEMCrypto_ERROR_NO_DEVICE_KEY + * OEMCrypto_ERROR_INVALID_SESSION + * OEMCrypto_ERROR_INVALID_CONTEXT + * OEMCrypto_ERROR_DECRYPT_FAILED + * OEMCrypto_ERROR_KEY_EXPIRED + * OEMCrypto_ERROR_INSUFFICIENT_HDCP + * OEMCrypto_ERROR_ANALOG_OUTPUT + * OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * OEMCrypto_ERROR_UNKNOWN_FAILURE + * OEMCrypto_ERROR_BUFFER_TOO_LARGE + * OEMCrypto_ERROR_OUTPUT_TOO_LARGE + * OEMCrypto_ERROR_SESSION_LOST_STATE + * OEMCrypto_ERROR_SYSTEM_INVALIDATED + * OEMCrypto_ERROR_INVALID_ENTITLED_KEY_SESSION + * + * Buffer Sizes: + * 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 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 17. This method changed its name in API + * version 11. + */ +OEMCryptoResult OEMCrypto_DecryptCENC( + OEMCrypto_SESSION session, + const OEMCrypto_SampleDescription* samples, // an array of samples. + size_t samples_length, // the number of samples. + const OEMCrypto_CENCEncryptPatternDesc* pattern); + +/* + * OEMCrypto_CopyBuffer + * + * Description: + * 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 + * does not need an open session, and it may be called concurrently with + * other functions on a multithreaded system. 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. + * + * Parameters: + * [in] session: crypto session identifier. + * [in] data_addr: An unaligned pointer to the buffer to be copied. + * [in] data_addr_length: The length of the buffer, in bytes. + * [in] out_buffer_descriptor: A caller-owned descriptor that specifies the + * handling of the byte stream. See OEMCrypto_DestbufferDesc for + * details. + * [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. + * + * Returns: + * OEMCrypto_SUCCESS + * OEMCrypto_ERROR_INVALID_CONTEXT + * OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * OEMCrypto_ERROR_UNKNOWN_FAILURE + * OEMCrypto_ERROR_BUFFER_TOO_LARGE + * OEMCrypto_ERROR_OUTPUT_TOO_LARGE + * OEMCrypto_ERROR_SESSION_LOST_STATE + * OEMCrypto_ERROR_SYSTEM_INVALIDATED + * + * Buffer Sizes: + * 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 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); + +/* + * OEMCrypto_Generic_Encrypt + * + * Description: + * This function encrypts a generic buffer of data using the current key. + * + * If the session has an entry in the Usage Table, then OEMCrypto will update + * the time_of_last_decrypt. If the status of the entry is "unused", then + * change the status to "active" and set the time_of_first_decrypt. + * + * OEMCrypto shall be able to handle buffers at least 100 KiB long. + * + * 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 current key shall have the Allow_Encrypt set. + * If not, return OEMCrypto_ERROR_UNKNOWN_FAILURE. + * 2. 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 the document "License Duration and + * Renewal" for handling the return value of these ODK functions. + * 3. If the current session 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. + * + * Parameters: + * [in] session: crypto or entitled key session identifier. + * [in] in_buffer: pointer to memory containing data to be encrypted. + * [in] in_buffer_length: length of the buffer, in bytes. The algorithm may + * restrict in_buffer_length to be a multiple of block size. + * [in] iv: IV for encrypting data. Size is 128 bits. + * [in] algorithm: Specifies which encryption algorithm to use. Currently, + * only CBC 128 mode is allowed for encryption. + * [out] out_buffer: pointer to buffer in which encrypted data should be + * stored. + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_KEY_EXPIRED + * OEMCrypto_ERROR_NO_DEVICE_KEY + * OEMCrypto_ERROR_INVALID_SESSION + * OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * OEMCrypto_ERROR_UNKNOWN_FAILURE + * OEMCrypto_ERROR_BUFFER_TOO_LARGE + * OEMCrypto_ERROR_SESSION_LOST_STATE + * OEMCrypto_ERROR_SYSTEM_INVALIDATED + * OEMCrypto_ERROR_NOT_IMPLEMENTED + * OEMCrypto_ERROR_INVALID_ENTITLED_KEY_SESSION + * + * Buffer Sizes: + * OEMCrypto shall support buffers 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 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 17. + */ +OEMCryptoResult OEMCrypto_Generic_Encrypt( + 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 + * + * Description: + * This function decrypts a generic buffer of data using the current key. + * + * If the session has an entry in the Usage Table, then OEMCrypto will update + * the time_of_last_decrypt. If the status of the entry is "unused", then + * change the status to "active" and set the time_of_first_decrypt. + * + * OEMCrypto should be able to handle buffers at least 100 KiB long. + * + * 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 current key shall have the Allow_Decrypt set. + * If not, return OEMCrypto_ERROR_DECRYPT_FAILED. + * 2. If the current key's control block has the Data_Path_Type bit set, + * then return OEMCrypto_ERROR_DECRYPT_FAILED. + * 3. 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 the document "License Duration and + * Renewal" for handling the return value of these ODK functions. + * 4. If the current session 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. + * + * Parameters: + * [in] session: crypto or entitled key session identifier. + * [in] in_buffer: pointer to memory containing data to be encrypted. + * [in] in_buffer_length: length of the buffer, in bytes. The algorithm may + * restrict in_buffer_length to be a multiple of block size. + * [in] iv: IV for encrypting data. Size is 128 bits. + * [in] algorithm: Specifies which encryption algorithm to use. Currently, + * only CBC 128 mode is allowed for decryption. + * [out] out_buffer: pointer to buffer in which decrypted data should be + * stored. + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_KEY_EXPIRED + * OEMCrypto_ERROR_DECRYPT_FAILED + * OEMCrypto_ERROR_NO_DEVICE_KEY + * OEMCrypto_ERROR_INVALID_SESSION + * OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * OEMCrypto_ERROR_UNKNOWN_FAILURE + * OEMCrypto_ERROR_BUFFER_TOO_LARGE + * OEMCrypto_ERROR_SESSION_LOST_STATE + * OEMCrypto_ERROR_SYSTEM_INVALIDATED + * OEMCrypto_ERROR_NOT_IMPLEMENTED + * OEMCrypto_ERROR_INVALID_ENTITLED_KEY_SESSION + * + * Buffer Sizes: + * OEMCrypto shall support buffers 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 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 17. + */ +OEMCryptoResult OEMCrypto_Generic_Decrypt( + 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 + * + * Description: + * This function signs a generic buffer of data using the current key. + * + * If the session has an entry in the Usage Table, then OEMCrypto will update + * the time_of_last_decrypt. If the status of the entry is "unused", then + * change the status to "active" and set the time_of_first_decrypt. + * + * 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 current key shall have the Allow_Sign set. + * 2. 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 the document "License Duration and + * Renewal" for handling the return value of these ODK functions. + * 3. If the current session 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. + * + * Parameters: + * [in] session: crypto or entitled key session identifier. + * [in] buffer: pointer to memory containing data to be encrypted. + * [in] buffer_length: length of the buffer, in bytes. + * [in] algorithm: Specifies which algorithm to use. + * [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. + * [in/out] signature_length: (in) length of the signature buffer, in bytes. + * (out) actual length of the signature + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_KEY_EXPIRED + * OEMCrypto_ERROR_SHORT_BUFFER if signature buffer is not large enough to + * hold the output signature. + * OEMCrypto_ERROR_NO_DEVICE_KEY + * OEMCrypto_ERROR_INVALID_SESSION + * OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * OEMCrypto_ERROR_UNKNOWN_FAILURE + * OEMCrypto_ERROR_BUFFER_TOO_LARGE + * OEMCrypto_ERROR_SESSION_LOST_STATE + * OEMCrypto_ERROR_SYSTEM_INVALIDATED + * OEMCrypto_ERROR_NOT_IMPLEMENTED + * OEMCrypto_ERROR_INVALID_ENTITLED_KEY_SESSION + * + * Buffer Sizes: + * OEMCrypto shall support buffers 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 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 17. + */ +OEMCryptoResult OEMCrypto_Generic_Sign(OEMCrypto_SESSION session, + const OEMCrypto_SharedMemory* buffer, + size_t buffer_length, + OEMCrypto_Algorithm algorithm, + OEMCrypto_SharedMemory* signature, + size_t* signature_length); + +/* + * OEMCrypto_Generic_Verify + * + * Description: + * This function verifies the signature of a generic buffer of data using the + * current key. + * + * If the session has an entry in the Usage Table, then OEMCrypto will update + * the time_of_last_decrypt. If the status of the entry is "unused", then + * change the status to "active" and set the time_of_first_decrypt. + * + * Verification: + * The following checks should be performed. If any check fails, an error is + * returned. + * 1. The control bit for the current 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 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 the document "License Duration and + * Renewal" for handling the return value of these ODK functions. + * 5. If the current session 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. + * + * Parameters: + * [in] session: crypto or entitled key session identifier. + * [in] buffer: pointer to memory containing data to be encrypted. + * [in] buffer_length: length of the buffer, in bytes. + * [in] algorithm: Specifies which algorithm to use. + * [in] signature: pointer to buffer in which signature resides. + * [in] signature_length: length of the signature buffer, in bytes. + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_KEY_EXPIRED + * OEMCrypto_ERROR_SIGNATURE_FAILURE + * OEMCrypto_ERROR_NO_DEVICE_KEY + * OEMCrypto_ERROR_INVALID_SESSION + * OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * OEMCrypto_ERROR_UNKNOWN_FAILURE + * OEMCrypto_ERROR_BUFFER_TOO_LARGE + * OEMCrypto_ERROR_SESSION_LOST_STATE + * OEMCrypto_ERROR_SYSTEM_INVALIDATED + * OEMCrypto_ERROR_NOT_IMPLEMENTED + * OEMCrypto_ERROR_INVALID_ENTITLED_KEY_SESSION + * + * Buffer Sizes: + * OEMCrypto shall support buffers 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 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 17. + */ +OEMCryptoResult OEMCrypto_Generic_Verify( + OEMCrypto_SESSION session, const OEMCrypto_SharedMemory* buffer, + size_t buffer_length, OEMCrypto_Algorithm algorithm, + const OEMCrypto_SharedMemory* signature, size_t signature_length); + +/* + * OEMCrypto_WrapKeyboxOrOEMCert + * + * Description: + * 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(). + * + * Figure 10. OEMCrypto_WrapKeyboxOrOEMCert Operation + * + * 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. + * + * Parameters: + * [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 size of wrapped keybox. The keybox may either + * be clear or previously encrypted. + * [in] keybox_or_cert_length - length the keybox or cert data in bytes + * [out] wrapped_keybox_or_cert – Pointer to wrapped keybox or cert + * [out] wrapped_keybox_or_cert_length – Pointer to the length of the wrapped + * keybox or certificate key in bytes + * [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. + * [in] transport_key_length – Optional. Number of bytes in the + * transport_key, if used. + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_WRITE_KEYBOX failed to encrypt the keybox + * OEMCrypto_ERROR_SHORT_BUFFER if keybox is provided as NULL, to determine + * the size of the wrapped keybox + * OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * OEMCrypto_ERROR_NOT_IMPLEMENTED + * 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); + +/* + * OEMCrypto_InstallKeyboxOrOEMCert + * + * Description: + * 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. + * + * Parameters: + * [in] keybox_or_cert - pointer to encrypted data as input + * [in] keybox_or_cert_length - length of the data in bytes + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_BAD_MAGIC + * OEMCrypto_ERROR_BAD_CRC + * OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * OEMCrypto_ERROR_NOT_IMPLEMENTED + * 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); + +/* + * OEMCrypto_GetProvisioningMethod + * + * Description: + * This function is for OEMCrypto to tell the layer above what provisioning + * method it uses: keybox or OEM certificate. + * + * Parameters: + * none + * + * Returns: + * - 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. + * - Keybox means the device has a unique keybox. For level 1 devices this + * keybox must be securely installed by the device manufacturer. + * - OEMCertificate means the device has a factory installed OEM + * certificate. This is also called Provisioning 3.0. + * - 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); + +/* + * OEMCrypto_IsKeyboxOrOEMCertValid + * + * Description: + * 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. + * + * If the device has an OEM Certificate, this validates the certificate + * private key. + * + * Parameters: + * none + * + * Returns: + * OEMCrypto_SUCCESS + * OEMCrypto_ERROR_BAD_MAGIC + * OEMCrypto_ERROR_BAD_CRC + * OEMCrypto_ERROR_KEYBOX_INVALID + * OEMCrypto_ERROR_INVALID_RSA_KEY + * 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_IsKeyboxOrOEMCertValid(void); + +/* + * OEMCrypto_GetDeviceID + * + * Description: + * Return a device unique id. For devices with a keybox, retrieve the + * DeviceID from the Keybox. For devices that have an OEM Certificate instead + * of a keybox, it should set the device ID to a device-unique string, such + * as the device serial number. 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. + * + * Parameters: + * [out] device_id - pointer to the buffer that receives the Device ID. + * [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. + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_SHORT_BUFFER if the buffer is too small to return device ID + * OEMCrypto_ERROR_NO_DEVICEID failed to return Device Id + * 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); + +/* + * OEMCrypto_GetKeyData + * + * Description: + * Return the Key Data field from the Keybox. + * + * Parameters: + * [out] keyData - pointer to the buffer to hold the Key Data field from the + * Keybox + * [in/out] keyDataLength – on input, the allocated buffer size. On output, + * the number of bytes in Key Data + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_SHORT_BUFFER if the buffer is too small to return KeyData + * OEMCrypto_ERROR_NO_KEYDATA + * OEMCrypto_ERROR_NOT_IMPLEMENTED - this function is for Provisioning 2.0 + * only. + * 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); + +/* + * OEMCrypto_LoadTestKeybox + * + * Description: + * 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. + * + * Parameters: + * [in] buffer: pointer to memory containing test keybox, in binary form. + * [in] buffer_length: length of the buffer, in bytes. + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * OEMCrypto_ERROR_NOT_IMPLEMENTED - this function is for Provisioning 2.0 + * only. + * 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); + +/* + * OEMCrypto_LoadOEMPrivateKey + * + * Description: + * 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 above + * discussing Provisioning 3.0. + * + * Parameters: + * - [in] session: this function affects the specified session only. + * + * Returns: + * OEMCrypto_SUCCESS + * OEMCrypto_ERROR_NOT_IMPLEMENTED - this function is for Provisioning 3.0 + * only. + * 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); + +/* + * OEMCrypto_GetOEMPublicCertificate + * + * Description: + * This function should place the OEM public certificate in the buffer + * public_cert. See the section above discussing Provisioning 3.0. + * + * If the buffer is not large enough, OEMCrypto should update + * public_cert_length and return OEMCrypto_ERROR_SHORT_BUFFER. + * + * Parameters: + * - [out] public_cert: the buffer where the public certificate is stored. + * - [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. + * + * Returns: + * OEMCrypto_SUCCESS + * OEMCrypto_ERROR_NOT_IMPLEMENTED - this function is for Provisioning 3.0 + * only. + * OEMCrypto_ERROR_SHORT_BUFFER + * 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); + +/* + * OEMCrypto_GetRandom + * + * Description: + * Returns a buffer filled with hardware-generated random bytes, if supported + * by the hardware. If the hardware feature does not exist, return + * OEMCrypto_ERROR_RNG_NOT_SUPPORTED. + * + * Parameters: + * [out] random_data- pointer to the buffer that receives random data + * [in] random_data_length- length of the random data buffer in bytes + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_RNG_FAILED failed to generate random number + * OEMCrypto_ERROR_RNG_NOT_SUPPORTED function not supported + * OEMCrypto_ERROR_BUFFER_TOO_LARGE + * OEMCrypto_ERROR_SYSTEM_INVALIDATED + * + * Buffer Sizes: + * OEMCrypto shall support random_data_length- sizes of at least 32 bytes + * for random number generation. + * OEMCrypto shall return OEMCrypto_ERROR_BUFFER_TOO_LARGE if the buffer is + * larger than the supported size. + * + * 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_GetRandom(uint8_t* random_data, + size_t random_data_length); + +/* + * OEMCrypto_APIVersion + * + * Description: + * 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. + * + * Parameters: + * none + * + * Returns: + * 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); + +/* + * OEMCrypto_MinorAPIVersion + * + * Description: + * 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. + * + * Parameters: + * none + * + * Returns: + * 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); + +/* + * OEMCrypto_BuildInformation + * + * Description: + * Report the build information of the OEMCrypto library as a short null + * terminated C string. The string should be at most 128 characters long. + * This string should be updated with each release or OEMCrypto build. + * + * Some SOC vendors deliver a binary OEMCrypto library to a device + * manufacturer. This means the OEMCrypto version may not be exactly in sync + * with the system's versions. This string can be used to help track which + * version is installed on a device. + * + * 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. + * + * Since the OEMCrypto API also changes its minor version number when there + * are minor corrections, it would be useful to include the API version + * number in this string, e.g. "15.1" or "15.2" if those minor versions are + * released. + * + * Parameters: + * none + * + * Returns: + * A printable null terminated C string, suitable for a single line in a log. + * + * 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. + */ +const char* OEMCrypto_BuildInformation(void); + +/* + * OEMCrypto_Security_Patch_Level + * + * Description: + * 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 above for more details. + * + * Parameters: + * none + * + * Returns: + * 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); + +/* + * OEMCrypto_SecurityLevel + * + * Description: + * 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. + * + * Parameters: + * none + * + * Returns: + * A null terminated string. Useful value are "L1", "L2" and "L3". + * + * 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 6. + */ +const char* OEMCrypto_SecurityLevel(void); + +/* + * OEMCrypto_GetHDCPCapability + * + * Description: + * 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. + * + * Parameters: + * [out] current - this is the current HDCP version, based on the device + * itself, and the display to which it is connected. + * [out] maximum - this is the maximum supported HDCP version for the device, + * ignoring any attached device. + * + * Returns: + * OEMCrypto_SUCCESS + * OEMCrypto_ERROR_UNKNOWN_FAILURE + * 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); + +/* + * OEMCrypto_SupportsUsageTable + * + * Description: + * 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. + * + * Parameters: + * none + * + * Returns: + * 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); + +/* + * OEMCrypto_MaximumUsageTableHeaderSize + * + * Description: + * 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. + * + * Parameters: + * none + * + * Returns: + * 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); + +/* + * OEMCrypto_IsAntiRollbackHwPresent + * + * Description: + * 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. + * + * Parameters: + * none + * + * Returns: + * 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); + +/* + * OEMCrypto_GetNumberOfOpenSessions + * + * Description: + * Returns the current number of open sessions. The CDM and OEMCrypto + * consumers can query this value so they can use resources more effectively. + * + * Parameters: + * [out] count - this is the current number of opened sessions. + * + * Returns: + * OEMCrypto_SUCCESS + * OEMCrypto_ERROR_UNKNOWN_FAILURE + * 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); + +/* + * OEMCrypto_GetMaxNumberOfSessions + * + * Description: + * 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. + * + * Parameters: + * [out] max - this is the max number of supported sessions. + * + * Returns: + * OEMCrypto_SUCCESS + * OEMCrypto_ERROR_UNKNOWN_FAILURE + * 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); + +/* + * OEMCrypto_SupportedCertificates + * + * Description: + * 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. + * + * Parameters: + * none + * + * Returns: + * 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); + +/* + * OEMCrypto_IsSRMUpdateSupported + * + * Description: + * Returns true if the device supports SRM files and the file can be updated + * via the function OEMCrypto_LoadSRM. This also returns false for devices + * that do not support an SRM file, devices that do not support HDCP, and + * devices that have no external display support. + * + * Parameters: + * none + * + * Returns: + * true - if LoadSRM is supported. + * 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 13. + */ +bool OEMCrypto_IsSRMUpdateSupported(void); + +/* + * OEMCrypto_GetCurrentSRMVersion + * + * Description: + * 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. + * + * Parameters: + * [out] version: current SRM version number. + * + * Returns: + * OEMCrypto_ERROR_NOT_IMPLEMENTED + * OEMCrypto_SUCCESS + * OEMCrypto_LOCAL_DISPLAY_ONLY - to indicate version was not set, and is not + * needed. + * 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); + +/* + * OEMCrypto_GetAnalogOutputFlags + * + * Description: + * 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. + * + * Parameters: + * none. + * + * Returns: + * Returns a bitwise OR of the following flags. + * - 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); + +/* + * OEMCrypto_ResourceRatingTier + * + * Description: + * 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 below to be minimum + * values. + * + * These performance parameters are for OEMCrypto only. In particular, + * bandwidth and codec resolution are determined by the platform. + * + * Some parameters need more explanation. The Sample size is typically the + * size of one encoded frame, but might be several frames for AV1. Converting + * this to resolution depends on the Codec, which is not specified by + * OEMCrypto. Some content has the sample broken into several subsamples. The + * "number of subsamples" restriction requires that any content can be broken + * into at least that many subsamples. However, this number may be larger if + * DecryptCENC returns OEMCrypto_ERROR_BUFFER_TOO_LARGE. In that case, the + * layer above OEMCrypto will break the sample into subsamples of size + * "Decrypt Buffer Size" as specified in the table below. The "Decrypt Buffer + * Size" means the size of one subsample that may be passed into DecryptCENC + * or CopyBuffer without returning error OEMCrypto_ERROR_BUFFER_TOO_LARGE. + * + * 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 number of keys per session is an indication of how many different + * track types there can be for a piece of content. Typically, content will + * have several keys corresponding to audio and video at different + * resolutions. If the content uses key rotation, there could be three keys + * -- previous interval, current interval, and next interval -- for each + * resolution. + * + * Concurrent playback sessions versus concurrent sessions: some applications + * will preload multiple licenses before the user picks which content to + * play. Each of these licenses corresponds to an open session. Once playback + * starts, some platforms support picture-in-picture or multiple displays. + * Each of these pictures would correspond to a separate playback session + * with active decryption. + * + * The total number of keys for all sessions indicates that the device may + * share key memory over multiple sessions. For example, on a Tier 3 device, + * the device must support four sessions with 20 keys each (80 total), or 20 + * sessions with 4 keys each (80 total), but it does not need to support 20 + * sessions with 20 keys each. + * + * The message size that is needed for a license with a large number of keys + * is larger than in previous versions. The message size limit applies to all + * functions that sign or verify messages. It also applies to the size of + * context buffers in the derive key functions. + * + * Decrypted frames per second -- strictly speaking, OEMCrypto only controls + * the decryption part of playback and cannot control the decoding and + * display part. However, devices that support the higher resource tiers + * should also support a higher frame rate. Platforms may enforce these + * values. For example Android will enforce a frame rate via a GTS test. + * + * Note on units: We will use KiB to mean 1024 bytes and MiB to mean 1024 + * KiB, as described at https://en.wikipedia.org/wiki/Kibibyte. + * + * +--------------------------------+---------+----------+---------+---------+ + * |Resource Rating Tier |1 - Low |2 - Medium|3 - High |4 - Very | + * | | | | | High | + * +--------------------------------+---------+----------+---------+---------+ + * |Minimum Sample size |1 MiB |2 MiB |4 MiB |16 MiB | + * +--------------------------------+---------+----------+---------+---------+ + * |Minimum Number of Subsamples |10 |16 |32 |64 | + * | (H264 or HEVC) | | | | | + * +--------------------------------+---------+----------+---------+---------+ + * |Minimum Number of Subsamples |9 |9 |9 |9 | + * |(VP9) | | | | | + * +--------------------------------+---------+----------+---------+---------+ + * |Minimum Number of Subsamples |72 |144 |288 |576 | + * |(AV1) | | | | | + * +--------------------------------+---------+----------+---------+---------+ + * |Minimum subsample buffer size |100 KiB |500 KiB |1 MiB |4 MiB | + * +--------------------------------+---------+----------+---------+---------+ + * |Minimum Generic crypto buffer |10 KiB |100 KiB |500 KiB |1 MiB | + * |size | | | | | + * +--------------------------------+---------+----------+---------+---------+ + * |Minimum number of concurrent |10 |20 |30 |40 | + * |sessions | | | | | + * +--------------------------------+---------+----------+---------+---------+ + * |Minimum number of keys per |4 |20 |20 |30 | + * |session | | | | | + * +--------------------------------+---------+----------+---------+---------+ + * |Minimum total number of keys |16 |40 |80 |90 | + * | (all sessions) | | | | | + * +--------------------------------+---------+----------+---------+---------+ + * |Minimum Message Size |8 KiB |8 KiB |16 KiB |32 KiB | + * +--------------------------------+---------+----------+---------+---------+ + * |Decrypted Frames per Second |30 fps SD|30 fps HD |60 fps HD|60 fps 8k| + * +--------------------------------+---------+----------+---------+---------+ + * + * Parameters: + * none. + * + * Returns: + * 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); + +/* + * OEMCrypto_LoadProvisioning + * + * Description: + * 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 + * 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. + * + * 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 enc_rsa_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_rsa_key are not the string + * "SIGN", then the default value of allowed_schemes = 1 (kSign_RSASSA_PSS) + * will be used. + * + * 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_rsa_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_rsa_key in the buffer rsa_key using the session's derived + * encryption key (enc_key). Use enc_rsa_key_iv as the initial vector + * for AES_128-CBC mode, with PKCS#5 padding. The rsa_key should be kept + * in secure memory and protected from the user. + * 6. If the first four bytes of the buffer rsa_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 rsa_key are not the string + * "SIGN", then the default value of allowed_schemes = 1 + * (kSign_RSASSA_PSS) will be used. + * 8. After possibly skipping past the first 8 bytes signifying the allowed + * signing algorithm, the rest of the buffer rsa_key contains an RSA + * device key in PKCS#8 binary DER encoded format. The OEMCrypto library + * shall verify that this RSA key is valid. + * 9. Re-encrypt the device RSA key with an internal key (such as the OEM + * key or Widevine Keybox key) and the generated IV using AES-128-CBC + * with PKCS#5 padding. + * 10. Copy the rewrapped key to the buffer specified by wrapped_rsa_key + * and the size of the wrapped key to wrapped_rsa_key_length. + * + * Parameters: + * [in] session: crypto session identifier. + * [in] message: pointer to memory containing data. + * [in] message_length: length of the message, in bytes. + * [in] core_message_length: length of the core submessage, in bytes. + * [in] signature: pointer to memory containing the signature. + * [in] signature_length: length of the signature, in bytes. + * [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. + * [in/out] wrapped_private_key_length: (in) length of the encrypted private + * key, in bytes. + * (out) actual length of the encrypted private key + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_NO_DEVICE_KEY + * OEMCrypto_ERROR_INVALID_SESSION + * OEMCrypto_ERROR_INVALID_RSA_KEY + * OEMCrypto_ERROR_SIGNATURE_FAILURE + * OEMCrypto_ERROR_INVALID_NONCE + * OEMCrypto_ERROR_SHORT_BUFFER + * OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * OEMCrypto_ERROR_UNKNOWN_FAILURE + * OEMCrypto_ERROR_BUFFER_TOO_LARGE + * OEMCrypto_ERROR_SESSION_LOST_STATE + * OEMCrypto_ERROR_SYSTEM_INVALIDATED + * + * Buffer Sizes: + * 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); + +/* + * OEMCrypto_LoadDRMPrivateKey + * + * Description: + * 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 + * RewrapDeviceRSAKey. + * 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. + * + * Parameters: + * [in] session: crypto session identifier. + * [in] key_type: indicates either an RSA or ECC key for devices that support + * both. + * [in] wrapped_rsa_key: wrapped device RSA key stored on the device. Format + * is PKCS#8, binary DER encoded, and encrypted with a key internal to + * the OEMCrypto instance, using AES-128-CBC with PKCS#5 padding. This + * is the wrapped key generated by OEMCrypto_RewrapDeviceRSAKey. + * [in] wrapped_rsa_key_length: length of the wrapped key buffer, in bytes. + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_NO_DEVICE_KEY + * OEMCrypto_ERROR_INVALID_SESSION + * OEMCrypto_ERROR_INVALID_RSA_KEY + * OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * OEMCrypto_ERROR_UNKNOWN_FAILURE + * OEMCrypto_ERROR_SESSION_LOST_STATE + * 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_rsa_key, + size_t wrapped_rsa_key_length); + +/* + * OEMCrypto_LoadTestRSAKey + * + * Description: + * 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 unit test code, oemcrypto_test.cpp, in + * PKCS8 form as the constant kTestRSAPKCS8PrivateKeyInfo2_2048. + * + * Parameters: + * none + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * OEMCrypto_ERROR_NOT_IMPLEMENTED - devices that use a keybox should not + * implement this function + * 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); + +/* + * OEMCrypto_GenerateRSASignature + * + * Description: + * 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_RSA_KEY is returned. + * + * Parameters: + * [in] session: crypto session identifier. + * [in] message: pointer to memory containing message to be signed. + * [in] message_length: length of the message, in bytes. + * [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. + * [in/out] signature_length: (in) length of the signature buffer, in bytes. + * (out) actual length of the signature + * [in] padding_scheme: specify which scheme to use for the signature. + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_SHORT_BUFFER if the signature buffer is too small. + * OEMCrypto_ERROR_INVALID_SESSION + * OEMCrypto_ERROR_INVALID_CONTEXT + * OEMCrypto_ERROR_INVALID_RSA_KEY + * OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * OEMCrypto_ERROR_UNKNOWN_FAILURE + * OEMCrypto_ERROR_NOT_IMPLEMENTED - if algorithm > 0, and the device does + * not support that algorithm. + * OEMCrypto_ERROR_BUFFER_TOO_LARGE + * OEMCrypto_ERROR_SESSION_LOST_STATE + * OEMCrypto_ERROR_SYSTEM_INVALIDATED + * + * Buffer Sizes: + * 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_CreateUsageTableHeader + * + * Description: + * 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. + * + * Parameters: + * [out] header_buffer: pointer to memory where encrypted usage table header + * is written. + * [in/out] header_buffer_length: (in) length of the header_buffer, in bytes. + * (out) actual length of the header_buffer + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_SHORT_BUFFER - if header_buffer_length is too small. + * OEMCrypto_ERROR_NOT_IMPLEMENTED + * OEMCrypto_ERROR_UNKNOWN_FAILURE + * 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); + +/* + * OEMCrypto_LoadUsageTableHeader + * + * Description: + * 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. + * + * Parameters: + * [in] buffer: pointer to memory containing encrypted usage table header. + * [in] buffer_length: length of the buffer, in bytes. + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_SHORT_BUFFER + * OEMCrypto_ERROR_NOT_IMPLEMENTED - some devices do not implement usage + * tables. + * OEMCrypto_ERROR_UNKNOWN_FAILURE + * OEMCrypto_WARNING_GENERATION_SKEW - if the generation number is off by + * exactly 1. + * OEMCrypto_ERROR_GENERATION_SKEW - if the generation number is off by more + * than 1. + * OEMCrypto_ERROR_SIGNATURE_FAILURE - if the signature failed. + * OEMCrypto_ERROR_BAD_MAGIC - verification string does not match. + * 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); + +/* + * OEMCrypto_CreateNewUsageEntry + * + * Description: + * 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. + * + * Parameters: + * [in] session: handle for the session to be used. + * [out] usage_entry_number: index of new usage entry. + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_NOT_IMPLEMENTED - some devices do not implement usage + * tables. + * 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. + * OEMCrypto_ERROR_UNKNOWN_FAILURE + * OEMCrypto_ERROR_SESSION_LOST_STATE + * OEMCrypto_ERROR_SYSTEM_INVALIDATED + * 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); + +/* + * OEMCrypto_LoadUsageEntry + * + * Description: + * 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. + * + * 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. + * + * Parameters: + * [in] session: handle for the session to be used. + * [in] usage_entry_number: index of existing usage entry. + * [in] buffer: pointer to memory containing encrypted usage table entry. + * [in] buffer_length: length of the buffer, in bytes. + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_SHORT_BUFFER + * OEMCrypto_ERROR_NOT_IMPLEMENTED - some devices do not implement usage + * tables. + * OEMCrypto_ERROR_UNKNOWN_FAILURE - index beyond end of table. + * OEMCrypto_ERROR_INVALID_SESSION - entry associated with another session or + * the index is wrong. + * OEMCrypto_WARNING_GENERATION_SKEW - if the generation number is off by + * exactly 1. + * OEMCrypto_ERROR_GENERATION_SKEW - if the generation number is off by more + * than 1. + * OEMCrypto_ERROR_SIGNATURE_FAILURE - if the signature failed. + * OEMCrypto_ERROR_BAD_MAGIC - verification string does not match. + * OEMCrypto_ERROR_SESSION_LOST_STATE + * OEMCrypto_ERROR_SYSTEM_INVALIDATED + * 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); + +/* + * OEMCrypto_UpdateUsageEntry + * + * Description: + * 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 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. + * + * Parameters: + * [in] session: handle for the session to be used. + * [out] header_buffer: pointer to memory where encrypted usage table header + * is written. + * [in/out] header_buffer_length: (in) length of the header_buffer, in bytes. + * (out) actual length of the header_buffer + * [out] entry_buffer: pointer to memory where encrypted usage table entry is + * written. + * [in/out] entry_buffer_length: (in) length of the entry_buffer, in bytes. + * (out) actual length of the entry_buffer + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_SHORT_BUFFER + * OEMCrypto_ERROR_NOT_IMPLEMENTED - some devices do not implement usage + * tables. + * OEMCrypto_ERROR_UNKNOWN_FAILURE + * OEMCrypto_ERROR_SESSION_LOST_STATE + * 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); + +/* + * OEMCrypto_DeactivateUsageEntry + * + * Description: + * 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. + * + * Parameters: + * [in] session: handle for the session to be used. + * [in] pst: pointer to memory containing Provider Session Token. + * [in] pst_length: length of the pst, in bytes. + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_INVALID_CONTEXT - an entry was not created or loaded, or + * the pst does not match. + * OEMCrypto_ERROR_NOT_IMPLEMENTED + * OEMCrypto_ERROR_UNKNOWN_FAILURE + * OEMCrypto_ERROR_BUFFER_TOO_LARGE + * OEMCrypto_ERROR_SESSION_LOST_STATE + * OEMCrypto_ERROR_SYSTEM_INVALIDATED + * + * Buffer Sizes: + * 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); + +/* + * OEMCrypto_ReportUsage + * + * Description: + * 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, or if the pst does not match that in the + * entry, return the error OEMCrypto_ERROR_INVALID_CONTEXT. + * + * 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. + * + * (See drawing in "Widevine Modular DRM Security Integration Guide") + * + * 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. + * (See drawing in "Widevine Modular DRM Security Integration Guide") + * + * 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. + * + * Parameters: + * [in] session: handle for the session to be used. + * [in] pst: pointer to memory containing Provider Session Token. + * [in] pst_length: length of the pst, in bytes. + * [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. + * [in/out] buffer_length: (in) length of the report buffer, in bytes. + * (out) actual length of the report + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_SHORT_BUFFER - if report buffer is not large enough to + * hold the output report. + * OEMCrypto_ERROR_INVALID_SESSION - no open session with that id. + * OEMCrypto_ERROR_INVALID_CONTEXT + * OEMCrypto_ERROR_NOT_IMPLEMENTED + * OEMCrypto_ERROR_UNKNOWN_FAILURE + * OEMCrypto_ERROR_BUFFER_TOO_LARGE + * OEMCrypto_ERROR_ENTRY_NEEDS_UPDATE - if no call to UpdateUsageEntry since + * last call to Deactivate or since key use. + * OEMCrypto_ERROR_WRONG_PST - report asked for wrong pst. + * OEMCrypto_ERROR_SESSION_LOST_STATE + * OEMCrypto_ERROR_SYSTEM_INVALIDATED + * + * Buffer Sizes: + * 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); + +/* + * OEMCrypto_MoveEntry + * + * Description: + * 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. + * + * Parameters: + * [in] session: handle for the session to be used. + * [in] new_index: new index to be used for the session's usage entry + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_NOT_IMPLEMENTED + * OEMCrypto_ERROR_UNKNOWN_FAILURE + * OEMCrypto_ERROR_BUFFER_TOO_LARGE + * OEMCrypto_ERROR_ENTRY_IN_USE + * OEMCrypto_ERROR_SESSION_LOST_STATE + * 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); + +/* + * OEMCrypto_ShrinkUsageTableHeader + * + * Description: + * 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 an error 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. + * + * Parameters: + * [in] new_entry_count: number of entries in the to be in the header. + * [out] header_buffer: pointer to memory where encrypted usage table header + * is written. + * [in/out] header_buffer_length: (in) length of the header_buffer, in bytes. + * (out) actual length of the header_buffer + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_SHORT_BUFFER + * OEMCrypto_ERROR_NOT_IMPLEMENTED + * OEMCrypto_ERROR_UNKNOWN_FAILURE + * OEMCrypto_ERROR_ENTRY_IN_USE + * 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); + +/* + * OEMCrypto_RemoveSRM + * + * Description: + * Delete the current SRM. Any valid SRM, regardless of its version number, + * will be installable after this via OEMCrypto_LoadSRM. + * + * This function should not be implemented on production devices, and will + * only be used to verify unit tests on a test device. + * + * Parameters: + * none + * + * Returns: + * OEMCrypto_SUCCESS - if the SRM file was deleted. + * OEMCrypto_ERROR_NOT_IMPLEMENTED - always on production devices. + * + * 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 13. + */ +OEMCryptoResult OEMCrypto_RemoveSRM(void); + +/* + * OEMCrypto_SupportsDecryptHash + * + * Description: + * 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. + * + * Returns: + * OEMCrypto_Hash_Not_Supported = 0; + * OEMCrypto_CRC_Clear_Buffer = 1; + * 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); + +/* + * OEMCrypto_SetDecryptHash + * + * Description: + * 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 SelectKey. 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. + * + * Parameters: + * [in] session: session id for current decrypt operation + * [in] frame_number: frame number for the recent DecryptCENC sample. + * [in] hash: hash or CRC of previously decrypted frame. + * [in] hash_length: length of hash, in bytes. + * + * Returns: + * OEMCrypto_SUCCESS - if the hash was set + * OEMCrypto_ERROR_NOT_IMPLEMENTED - function not implemented + * OEMCrypto_ERROR_INVALID_SESSION - session not open + * OEMCrypto_ERROR_SHORT_BUFFER - hash_length too short for supported hash + * type + * OEMCrypto_ERROR_BUFFER_TOO_LARGE - hash_length too long for supported hash + * type + * OEMCrypto_ERROR_UNKNOWN_FAILURE - other error + * OEMCrypto_ERROR_SESSION_LOST_STATE + * 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); + +/* + * OEMCrypto_GetHashErrorCode + * + * Description: + * 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. + * + * Parameters: + * [in] session: session id for operation. + * [out] failed_frame_number: frame number for sample with incorrect hash. + * + * Returns: + * OEMCrypto_SUCCESS - if all frames have had a correct hash + * OEMCrypto_ERROR_NOT_IMPLEMENTED + * OEMCrypto_ERROR_BAD_HASH - if any frame had an incorrect hash + * OEMCrypto_ERROR_UNKNOWN_FAILURE - if the hash could not be computed + * OEMCrypto_ERROR_SESSION_LOST_STATE + * 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); + +/* + * OEMCrypto_AllocateSecureBuffer + * + * Description: + * 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. + * + * Parameters: + * [in] session: session id for operation. + * [in] buffer_size: the requested buffer size. + * [out] output_descriptor: the buffer descriptor for the created buffer. + * This will be passed into the OEMCrypto_DecryptCENC function. + * [out] secure_fd: a pointer to platform dependent file or buffer + * descriptor. This will be passed to OEMCrypto_FreeSecureBuffer. + * + * Returns: + * OEMCrypto_SUCCESS - if the buffer was created + * OEMCrypto_ERROR_NOT_IMPLEMENTED + * OEMCrypto_ERROR_OUTPUT_TOO_LARGE + * 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); + +/* + * OEMCrypto_FreeSecureBuffer + * + * Description: + * 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. + * + * Parameters: + * [in] session: session id for operation. + * [out] output_descriptor: the buffer descriptor modified by + * OEMCrypto_AllocateSecureBuffer + * [in] secure_fd: The integer returned by OEMCrypto_AllocateSecureBuffer + * + * Returns: + * OEMCrypto_SUCCESS - if the buffer was freed + * OEMCrypto_ERROR_NOT_IMPLEMENTED + * 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_CreateEntitledKeySession + * + * Description: + * This method creates an entitled key session. + * + * Parameters: + * [in] oec_session: handle for the OEMCrypto session to be associated with + * the created entitled key session. + * [out] key_session: id of the created entitled key session. + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_NOT_IMPLEMENTED + * OEMCrypto_ERROR_INVALID_SESSION + * + * Version: + * This method is new in API version 17. + */ +OEMCryptoResult OEMCrypto_CreateEntitledKeySession( + OEMCrypto_SESSION oec_session, OEMCrypto_SESSION* key_session); + +/* + * OEMCrypto_RemoveEntitledKeySession + * + * Description: + * This method which removes an entitled key session. + * + * Parameters: + * [in] key_session: id of the entitled key session to be removed. + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_NOT_IMPLEMENTED + * OEMCrypto_ERROR_INVALID_ENTITLED_KEY_SESSION + * + * Version: + * This method is new in API version 17. + */ +OEMCryptoResult OEMCrypto_RemoveEntitledKeySession( + OEMCrypto_SESSION key_session); + +/****************************************************************************/ +/****************************************************************************/ +/* 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. + */ +OEMCryptoResult OEMCrypto_GenerateSignature(OEMCrypto_SESSION session, + const uint8_t* message, + size_t message_length, + uint8_t* signature, + size_t* signature_length); + +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); + +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); + +OEMCryptoResult OEMCrypto_UpdateUsageTable(void); + +OEMCryptoResult OEMCrypto_DeleteUsageEntry(OEMCrypto_SESSION, const uint8_t*, + size_t, const uint8_t*, size_t, + const uint8_t*, size_t); + +OEMCryptoResult OEMCrypto_ForceDeleteUsageEntry(const uint8_t*, size_t); + +OEMCryptoResult OEMCrypto_CopyOldUsageEntry(OEMCrypto_SESSION session, + const uint8_t* pst, + size_t pst_length); + +OEMCryptoResult OEMCrypto_DeleteOldUsageTable(void); + +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); + +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; + +OEMCryptoResult OEMCrypto_DecryptCENC_V15( + OEMCrypto_SESSION session, const uint8_t* data_addr, size_t data_length, + bool is_encrypted, const uint8_t* iv, + size_t block_offset, // used for CTR "cenc" mode only. + OEMCrypto_DestBufferDesc* out_buffer_descriptor, + const OEMCrypto_CENCEncryptPatternDesc_V15* pattern, + uint8_t subsample_flags); + +OEMCryptoResult OEMCrypto_GetOEMPublicCertificate_V15( + OEMCrypto_SESSION session, uint8_t* public_cert, + size_t* public_cert_length); + +OEMCryptoResult OEMCrypto_LoadDeviceRSAKey(OEMCrypto_SESSION session, + const uint8_t* wrapped_rsa_key, + size_t wrapped_rsa_key_length); +/****************************************************************************/ +/****************************************************************************/ + +#ifdef __cplusplus +} +#endif + +#endif // OEMCRYPTO_CAS_H_ diff --git a/oemcrypto/include/OEMCryptoCENCCommon.h b/oemcrypto/include/OEMCryptoCENCCommon.h new file mode 120000 index 0000000..dd92964 --- /dev/null +++ b/oemcrypto/include/OEMCryptoCENCCommon.h @@ -0,0 +1 @@ +../../oemcrypto/odk/include/OEMCryptoCENCCommon.h \ No newline at end of file diff --git a/oemcrypto/include/level3.h b/oemcrypto/include/level3.h new file mode 100644 index 0000000..67e5b2d --- /dev/null +++ b/oemcrypto/include/level3.h @@ -0,0 +1,464 @@ +// 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. + +/********************************************************************* + * level3.h + * + * Reference APIs needed to support Widevine's crypto algorithms. + *********************************************************************/ + +#ifndef LEVEL3_OEMCRYPTO_H_ +#define LEVEL3_OEMCRYPTO_H_ + +#include +#include + +#include "level3_file_system.h" +#include "OEMCryptoCAS.h" +#include "oemcrypto_adapter.h" + +namespace wvoec3 { + +#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_GetRandom _lcc06 +#define Level3_GetDeviceID _lcc07 +#define Level3_WrapKeyboxOrOEMCert _lcc08 +#define Level3_OpenSession _lcc09 +#define Level3_CloseSession _lcc10 +#define Level3_DecryptCENC _lcc11 +#define Level3_GenerateDerivedKeys _lcc12 +#define Level3_GenerateSignature _lcc13 +#define Level3_GenerateNonce _lcc14 +#define Level3_RewrapDeviceRSAKey _lcc18 +#define Level3_LoadDeviceRSAKey _lcc19 +#define Level3_GenerateRSASignature _lcc20 +#define Level3_DeriveKeysFromSessionKey _lcc21 +#define Level3_APIVersion _lcc22 +#define Level3_SecurityLevel _lcc23 +#define Level3_Generic_Encrypt _lcc24 +#define Level3_Generic_Decrypt _lcc25 +#define Level3_Generic_Sign _lcc26 +#define Level3_Generic_Verify _lcc27 +#define Level3_GetHDCPCapability _lcc28 +#define Level3_SupportsUsageTable _lcc29 +#define Level3_UpdateUsageTable _lcc30 +#define Level3_DeactivateUsageEntry _lcc31 +#define Level3_ReportUsage _lcc32 +#define Level3_DeleteUsageEntry _lcc33 +#define Level3_DeleteOldUsageTable _lcc34 +#define Level3_GetMaxNumberOfSessions _lcc37 +#define Level3_GetNumberOfOpenSessions _lcc38 +#define Level3_IsAntiRollbackHwPresent _lcc39 +#define Level3_QueryKeyControl _lcc41 +#define Level3_ForceDeleteUsageEntry _lcc43 +#define Level3_LoadTestRSAKey _lcc45 +#define Level3_SecurityPatchLevel _lcc46 +#define Level3_GetProvisioningMethod _lcc49 +#define Level3_GetOEMPublicCertificate _lcc50 +#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_CopyOldUsageEntry _lcc69 +#define Level3_CreateOldUsageEntry _lcc70 +#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_BuildInformation _lcc90 +#define Level3_RefreshKeys _lcc91 +#define Level3_LoadEntitledContentKeys _lcc92 +#define Level3_CopyBuffer _lcc93 +#else +#define Level3_Initialize _oecc01 +#define Level3_Terminate _oecc02 +#define Level3_InstallKeyboxOrOEMCert _oecc03 +#define Level3_GetKeyData _oecc04 +#define Level3_IsKeyboxOrOEMCertValid _oecc05 +#define Level3_GetRandom _oecc06 +#define Level3_GetDeviceID _oecc07 +#define Level3_WrapKeyboxOrOEMCert _oecc08 +#define Level3_OpenSession _oecc09 +#define Level3_CloseSession _oecc10 +#define Level3_GenerateDerivedKeys _oecc12 +#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_SecurityLevel _oecc23 +#define Level3_Generic_Encrypt _oecc24 +#define Level3_Generic_Decrypt _oecc25 +#define Level3_Generic_Sign _oecc26 +#define Level3_Generic_Verify _oecc27 +#define Level3_SupportsUsageTable _oecc29 +#define Level3_UpdateUsageTable _oecc30 +#define Level3_ReportUsage _oecc32 +#define Level3_DeleteUsageEntry _oecc33 +#define Level3_DeleteOldUsageTable _oecc34 +#define Level3_GenerateRSASignature _oecc36 +#define Level3_GetMaxNumberOfSessions _oecc37 +#define Level3_GetNumberOfOpenSessions _oecc38 +#define Level3_IsAntiRollbackHwPresent _oecc39 +#define Level3_QueryKeyControl _oecc41 +#define Level3_ForceDeleteUsageEntry _oecc43 +#define Level3_GetHDCPCapability _oecc44 +#define Level3_LoadTestRSAKey _oecc45 +#define Level3_SecurityPatchLevel _oecc46 +#define Level3_DecryptCENC _oecc48 +#define Level3_GetProvisioningMethod _oecc49 +#define Level3_GetOEMPublicCertificate _oecc50 +#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_CopyOldUsageEntry _oecc69 +#define Level3_CreateOldUsageEntry _oecc70 +#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_BuildInformation _oecc90 +#define Level3_RefreshKeys _oecc91 +#define Level3_LoadEntitledContentKeys _oecc92 +#define Level3_CopyBuffer _oecc93 +#endif + +#define Level3_GetInitializationState _oecl3o01 + +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, + uint32_t mac_key_context_length, + const uint8_t *enc_key_context, + uint32_t enc_key_context_length); +OEMCryptoResult Level3_GenerateNonce(OEMCrypto_SESSION session, + uint32_t* nonce); +OEMCryptoResult Level3_GenerateSignature(OEMCrypto_SESSION session, + const uint8_t* message, + size_t message_length, + uint8_t* signature, + size_t* signature_length); +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( + OEMCrypto_SESSION session, const uint8_t* data_addr, size_t data_length, + bool is_encrypted, const uint8_t* iv, size_t block_offset, + OEMCrypto_DestBufferDesc* out_buffer_descriptor, + const OEMCrypto_CENCEncryptPatternDesc_V15* pattern, + uint8_t subsample_flags); +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(OEMCrypto_SESSION session, + 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_GetRandom(uint8_t* randomData, + size_t dataLength); +OEMCryptoResult Level3_RewrapDeviceRSAKey30(OEMCrypto_SESSION session, + const uint32_t *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); +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_LoadDeviceRSAKey(OEMCrypto_SESSION session, + const 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(); +uint8_t Level3_SecurityPatchLevel(); +const char* 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(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(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(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(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_UpdateUsageTable(); +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); +OEMCryptoResult Level3_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); +OEMCryptoResult Level3_ForceDeleteUsageEntry(const uint8_t* pst, + size_t pst_length); +OEMCryptoResult Level3_DeleteOldUsageTable(); +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); +OEMCryptoResult Level3_CopyOldUsageEntry(OEMCrypto_SESSION session, + const uint8_t*pst, + size_t pst_length); +OEMCryptoResult Level3_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); +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_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); +const char* Level3_BuildInformation(); +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); + +// 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, +}; + +/* + * 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" + +// 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..eceec5a --- /dev/null +++ b/oemcrypto/include/level3_file_system.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 Master +// License Agreement. + +/********************************************************************* + * level3_file_system.h + * + * File system for OEMCrypto Level3 file operations. + *********************************************************************/ + +#ifndef LEVEL3_FILE_SYSTEM_H_ +#define LEVEL3_FILE_SYSTEM_H_ + +#include + +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..bf2158f --- /dev/null +++ b/oemcrypto/include/oemcrypto_types.h @@ -0,0 +1,82 @@ +// 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 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; + +/* + * 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); +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..962bcf2 --- /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 Master +// 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 "OEMCryptoCAS.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 ntohll64(time); + } + + // Parameter time is in host byte order. + void set_seconds_since_license_received(int64_t time) const { + time = 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/oemcrypto/odk/Android.bp b/oemcrypto/odk/Android.bp new file mode 100644 index 0000000..8e52af6 --- /dev/null +++ b/oemcrypto/odk/Android.bp @@ -0,0 +1,88 @@ +// 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. + +// ---------------------------------------------------------------- +// Builds libwvcas_odk.a, The ODK Library (libwvcas_odk) is used by +// the CDM and by oemcrypto implementations. +cc_library_static { + name: "libwvcas_odk", + include_dirs: [ + "vendor/widevine/libwvmediacas/oemcrypto/include", + "vendor/widevine/libwvmediacas/oemcrypto/odk/include", + "vendor/widevine/libwvmediacas/oemcrypto/odk/src", + ], + + srcs: [ + "src/odk.c", + "src/odk_overflow.c", + "src/odk_serialize.c", + "src/odk_timer.c", + "src/odk_util.c", + "src/serialization_base.c", + ], + proprietary: true, + + owner: "widevine", +} + +// ---------------------------------------------------------------- +// Builds libwvcas_kdo.a, The ODK Library companion (libwvcas_kdo) is used by +// the CDM and by oemcrypto tests, but not by oemcrypto implementations. +cc_library_static { + name: "libwvcas_kdo", + include_dirs: [ + "vendor/widevine/libwvmediacas/oemcrypto/include", + "vendor/widevine/libwvmediacas/oemcrypto/odk/include", + "vendor/widevine/libwvmediacas/oemcrypto/odk/src", + ], + + srcs: [ + "src/core_message_deserialize.cpp", + "src/core_message_serialize.cpp", + "src/core_message_serialize_proto.cpp", + ], + + static_libs: [ + "libcas_protos", + "libwvcas_odk", + ], + + proprietary: true, + + owner: "widevine", +} + +// ---------------------------------------------------------------- +// Builds odk_cas_test executable, which tests the ODK library. +cc_test { + name: "odk_cas_test", + include_dirs: [ + "vendor/widevine/libwvmediacas/oemcrypto/include", + "vendor/widevine/libwvmediacas/oemcrypto/odk/include", + "vendor/widevine/libwvmediacas/oemcrypto/odk/src", + ], + + // WARNING: Module tags are not supported in Soong. + // For native test binaries, use the "cc_test" module type. Some differences: + // - If you don't use gtest, set "gtest: false" + // - Binaries will be installed into /data/nativetest[64]// + // - Both 32 & 64 bit versions will be built (as appropriate) + + owner: "widevine", + proprietary: true, + + static_libs: [ + "libcas_protos", + "libwvcasplugins", + "libwvcas_odk", + "libwvcas_kdo", + ], + + srcs: [ + "test/odk_test.cpp", + "test/odk_test_helper.cpp", + "test/odk_timer_test.cpp", + ], + +} diff --git a/oemcrypto/odk/README b/oemcrypto/odk/README new file mode 100644 index 0000000..ba8c8c7 --- /dev/null +++ b/oemcrypto/odk/README @@ -0,0 +1,10 @@ +This ODK Library is used to generate and parse core OEMCrypto messages for +OEMCrypto v16 and above. + +This library is used by both OEMCrypto on a device, and by Widevine license and +provisioning servers. + +The source of truth for these files is in the server code base on piper. Do not +edit these files in the Android directory tree or in the Widevine Git +repository. If you need to edit these files and are not sure how to procede, +please ask for help from an engineer on the Widevine server or device teams. diff --git a/oemcrypto/odk/include/OEMCryptoCENCCommon.h b/oemcrypto/odk/include/OEMCryptoCENCCommon.h new file mode 100644 index 0000000..80435c0 --- /dev/null +++ b/oemcrypto/odk/include/OEMCryptoCENCCommon.h @@ -0,0 +1,172 @@ +/* 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. */ + +/********************************************************************* + * 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 + +/* clang-format off */ +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, + 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_INCREMENT_IV = 100, + /* 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, +} OEMCryptoResult; +/* clang-format on */ + +/* + * OEMCrypto_Usage_Entry_Status. + * 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; + +/* + * 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; + +/* Private key type used in the provisioning response. */ +typedef enum OEMCrypto_PrivateKeyType { + OEMCrypto_RSA_Private_Key = 0, + OEMCrypto_ECC_Private_Key = 1, +} OEMCrypto_PrivateKeyType; + +/* + * OEMCrypto_Substring + * + * Used to indicate a substring of a signed message in OEMCrypto_LoadKeys 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; + +/* + * OEMCrypto_KeyObject + * Points to the relevant fields for a content key. The fields are extracted + * from the License Response message offered to OEMCrypto_LoadKeys(). Each + * field points to one of the components of the key. Key data, key control, + * and both IV fields are 128 bits (16 bytes): + * key_id - the unique id of this key. + * 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. + * key_data_iv - the IV for performing AES-128-CBC decryption of the + * key_data field. + * key_data - the key data. It is encrypted (AES-128-CBC) with the + * session's derived encrypt key and the key_data_iv. + * key_control_iv - the IV for performing AES-128-CBC decryption of the + * key_control field. + * 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 OEMCrypto_LoadKeys(). + */ +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/odk/include/core_message_deserialize.h b/oemcrypto/odk/include/core_message_deserialize.h new file mode 100644 index 0000000..c2967dd --- /dev/null +++ b/oemcrypto/odk/include/core_message_deserialize.h @@ -0,0 +1,59 @@ +/* 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. */ + +/********************************************************************* + * core_message_deserialize.h + * + * OEMCrypto v16 Core Message Serialization library counterpart (a.k.a. KDO) + * + * This file declares functions to deserialize request messages prepared by + * Widevine clients (OEMCrypto/ODK). + * + * Please refer to core_message_types.h for details. + * + *********************************************************************/ + +#ifndef WIDEVINE_ODK_INCLUDE_CORE_MESSAGE_DESERIALIZE_H_ +#define WIDEVINE_ODK_INCLUDE_CORE_MESSAGE_DESERIALIZE_H_ + +#include "core_message_types.h" + +namespace oemcrypto_core_message { +namespace deserialize { + +/** + * Counterpart (deserializer) of ODK_PrepareCoreLicenseRequest (serializer) + * + * Parameters: + * [in] oemcrypto_core_message + * [out] core_license_request + */ +bool CoreLicenseRequestFromMessage(const std::string& oemcrypto_core_message, + ODK_LicenseRequest* core_license_request); + +/** + * Counterpart (deserializer) of ODK_PrepareCoreRenewalRequest (serializer) + * + * Parameters: + * [in] oemcrypto_core_message + * [out] core_renewal_request + */ +bool CoreRenewalRequestFromMessage(const std::string& oemcrypto_core_message, + ODK_RenewalRequest* core_renewal_request); + +/** + * Counterpart (deserializer) of ODK_PrepareCoreProvisioningRequest (serializer) + * + * Parameters: + * [in] oemcrypto_core_message + * [out] core_provisioning_request + */ +bool CoreProvisioningRequestFromMessage( + const std::string& oemcrypto_core_message, + ODK_ProvisioningRequest* core_provisioning_request); + +} /* namespace deserialize */ +} /* namespace oemcrypto_core_message */ + +#endif /* WIDEVINE_ODK_INCLUDE_CORE_MESSAGE_DESERIALIZE_H_ */ diff --git a/oemcrypto/odk/include/core_message_serialize.h b/oemcrypto/odk/include/core_message_serialize.h new file mode 100644 index 0000000..ffa43f5 --- /dev/null +++ b/oemcrypto/odk/include/core_message_serialize.h @@ -0,0 +1,68 @@ +/* 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. */ + +/********************************************************************* + * core_message_serialize.h + * + * OEMCrypto v16 Core Message Serialization library counterpart (a.k.a. KDO) + * + * This file declares functions to serialize response messages that will be + * parsed by Widevine clients (OEMCrypto/ODK). + * + * Please refer to core_message_types.h for details. + * + *********************************************************************/ + +#ifndef WIDEVINE_ODK_INCLUDE_CORE_MESSAGE_SERIALIZE_H_ +#define WIDEVINE_ODK_INCLUDE_CORE_MESSAGE_SERIALIZE_H_ + +#include "core_message_types.h" +#include "odk_structs.h" + +namespace oemcrypto_core_message { +namespace serialize { + +/** + * Counterpart (serializer) of ODK_ParseLicense (deserializer) + * struct-input variant + * + * Parameters: + * [in] parsed_lic + * [in] core_request + * [in] core_request_sha256 + * [out] oemcrypto_core_message + */ +bool CreateCoreLicenseResponse(const ODK_ParsedLicense& parsed_lic, + const ODK_LicenseRequest& core_request, + const std::string& core_request_sha256, + std::string* oemcrypto_core_message); + +/** + * Counterpart (serializer) of ODK_ParseRenewal (deserializer) + * + * Parameters: + * [in] core_request + * [in] renewal_duration_seconds + * [out] oemcrypto_core_message + */ +bool CreateCoreRenewalResponse(const ODK_RenewalRequest& core_request, + uint64_t renewal_duration_seconds, + std::string* oemcrypto_core_message); + +/** + * Counterpart (serializer) of ODK_ParseProvisioning (deserializer) + * struct-input variant + * + * Parameters: + * [in] parsed_prov + * [in] core_request + * [out] oemcrypto_core_message + */ +bool CreateCoreProvisioningResponse(const ODK_ParsedProvisioning& parsed_prov, + const ODK_ProvisioningRequest& core_request, + std::string* oemcrypto_core_message); +} /* namespace serialize */ +} /* namespace oemcrypto_core_message */ + +#endif /* WIDEVINE_ODK_INCLUDE_CORE_MESSAGE_SERIALIZE_H_ */ diff --git a/oemcrypto/odk/include/core_message_serialize_proto.h b/oemcrypto/odk/include/core_message_serialize_proto.h new file mode 100644 index 0000000..0f494de --- /dev/null +++ b/oemcrypto/odk/include/core_message_serialize_proto.h @@ -0,0 +1,62 @@ +/* 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. */ + +/********************************************************************* + * core_message_serialize_proto.h + * + * These functions are an extension of those found in + * core_message_serialize.h. The difference is that these use the + * license and provisioning messages in protobuf format to create the core + * message. + *********************************************************************/ + +#ifndef WIDEVINE_ODK_INCLUDE_CORE_MESSAGE_SERIALIZE_PROTO_H_ +#define WIDEVINE_ODK_INCLUDE_CORE_MESSAGE_SERIALIZE_PROTO_H_ + +#include +#include + +#include "core_message_types.h" +#include "license_protocol.pb.h" + +namespace oemcrypto_core_message { +namespace serialize { + +/* @ public create response (serializer) functions accepting proto input */ + +/** + * Counterpart (serializer) of ODK_ParseLicense (deserializer) + * + * Parameters: + * [in] serialized_license + serialized video_widevine::License + * [in] core_request oemcrypto core message from request. + * [in] core_request_sha256 - hash of serialized core request. + * [in] nonce_required - if the device should require a nonce match. + * [out] oemcrypto_core_message - the serialized oemcrypto core response. + */ +bool CreateCoreLicenseResponseFromProto(const std::string& serialized_license, + const ODK_LicenseRequest& core_request, + const std::string& core_request_sha256, + const bool nonce_required, + std::string* oemcrypto_core_message); + +/** + * Counterpart (serializer) of ODK_ParseProvisioning (deserializer) + * + * Parameters: + * [in] serialized_provisioning_response + * serialized video_widevine::ProvisioningResponse + * [in] core_request + * [out] oemcrypto_core_message + */ +bool CreateCoreProvisioningResponseFromProto( + const std::string& serialized_provisioning_response, + const ODK_ProvisioningRequest& core_request, + std::string* oemcrypto_core_message); + +} /* namespace serialize */ +} /* namespace oemcrypto_core_message */ + +#endif /* WIDEVINE_ODK_INCLUDE_CORE_MESSAGE_SERIALIZE_PROTO_H_ */ diff --git a/oemcrypto/odk/include/core_message_types.h b/oemcrypto/odk/include/core_message_types.h new file mode 100644 index 0000000..488e5d2 --- /dev/null +++ b/oemcrypto/odk/include/core_message_types.h @@ -0,0 +1,99 @@ +/* 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. */ + +/* clang-format off */ +/********************************************************************* + * core_message_types.h + * + * OEMCrypto v16 Core Message Serialization library counterpart (a.k.a. KDO) + * + * For Widevine Modular DRM, there are six message types between a server and + * a client device: license request and response, provisioning request and + * response, and renewal request and response. + * + * In OEMCrypto v15 and earlier, messages from the server were parsed by the + * CDM layer above OEMCrypto; the CDM in turn gave OEMCrypto a collection of + * pointers to protected data within the message. However, the pointers + * themselves were not signed by the server. + * + * Starting from OEMCrypto v16, all fields used by OEMCrypto in each of these + * messages have been identified in the document "Widevine Core Message + * Serialization". These fields are called the core of the message. Core + * message fields are (de)serialized using the ODK, a C library provided by + * Widevine. OEMCrypto will parse and verify the core of the message with + * help from the ODK. + * + * The KDO library is the counterpart of ODK used in the CDM & Widevine + * servers. For each message type generated by the ODK, KDO provides a + * corresponding parser. For each message type to be parsed by the ODK, + * KDO provides a corresponding writer. + * + * Table: ODK vs KDO (s: serialize; d: deserialize) + * +----------------------------------------+---------------------------------------+ + * | ODK | KDO | + * +---+------------------------------------+---+-----------------------------------+ + * | s | ODK_PrepareCoreLicenseRequest | d | CoreLicenseRequestFromMessage | + * | +------------------------------------+ +-----------------------------------+ + * | | ODK_PrepareCoreRenewalRequest | | CoreRenewalRequestFromMessage | + * | +------------------------------------+ +-----------------------------------+ + * | | ODK_PrepareCoreProvisioningRequest | | CoreProvisioningRequestFromMessage| + * +---+------------------------------------+---+-----------------------------------+ + * | d | ODK_ParseLicense | s | CreateCoreLicenseResponse | + * | +------------------------------------+ +-----------------------------------+ + * | | ODK_ParseRenewal | | CreateCoreRenewalResponse | + * | +------------------------------------+ +-----------------------------------+ + * | | ODK_ParseProvisioning | | CreateCoreProvisioningResponse | + * +---+------------------------------------+---+-----------------------------------+ + * + *********************************************************************/ +/* clang-format on */ + +#ifndef WIDEVINE_ODK_INCLUDE_CORE_MESSAGE_TYPES_H_ +#define WIDEVINE_ODK_INCLUDE_CORE_MESSAGE_TYPES_H_ + +#include +#include + +namespace oemcrypto_core_message { + +/* @ input/output structs */ + +/** + * Output structure for CoreLicenseRequestFromMessage + * Input structure for CreateCoreLicenseResponse + */ +struct ODK_LicenseRequest { + uint16_t api_minor_version; + uint16_t api_major_version; + uint32_t nonce; + uint32_t session_id; +}; + +/** + * Output structure for CoreRenewalRequestFromMessage + * Input structure for CreateCoreRenewalResponse + */ +struct ODK_RenewalRequest { + uint16_t api_minor_version; + uint16_t api_major_version; + uint32_t nonce; + uint32_t session_id; + uint64_t playback_time_seconds; +}; + +/** + * Output structure for CoreProvisioningRequestFromMessage + * Input structure for CreateCoreProvisioningResponse + */ +struct ODK_ProvisioningRequest { + uint16_t api_minor_version; + uint16_t api_major_version; + uint32_t nonce; + uint32_t session_id; + std::string device_id; +}; + +} /* namespace oemcrypto_core_message */ + +#endif /* WIDEVINE_ODK_INCLUDE_CORE_MESSAGE_TYPES_H_ */ diff --git a/oemcrypto/odk/include/odk.h b/oemcrypto/odk/include/odk.h new file mode 100644 index 0000000..6bbf36f --- /dev/null +++ b/oemcrypto/odk/include/odk.h @@ -0,0 +1,637 @@ +/* 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. */ + +/********************************************************************* + * odk.h + * + * OEMCrypto v16 Core Message Serialization library + * + * For Widevine Modular DRM, there are six message types between a server and + * a client device: license request and response, provisioning request and + * response, and renewal request and response. + * + * In OEMCrypto v15 and earlier, messages from the server were parsed by the + * CDM layer above OEMCrypto; the CDM in turn gave OEMCrypto a collection of + * pointers to protected data within the message. However, the pointers + * themselves were not signed by the server. + * + * Starting from OEMCrypto v16, all fields used by OEMCrypto in each of these + * messages have been identified in the document "Widevine Core Message + * Serialization". These fields are called the core of the message. Core + * message fields are (de)serialized using the ODK, a C library provided by + * Widevine. OEMCrypto will parse and verify the core of the message with + * help from the ODK. + * + * The ODK functions that parse code will fill out structs that have similar + * formats to the function parameters of the OEMCrypto v15 functions being + * replaced. The ODK will be provided in source code and it is Widevine's + * intention that partners can build and link ODK with their implementation + * of OEMCrypto with no or few code changes. + * + * OEMCrypto implementers shall build the ODK library as part of the Trusted + * Application (TA) running in the TEE. All memory and buffers used by the + * ODK library shall be sanitized by the OEMCrypto implementer to prevent + * modification by any process running the REE. + * + * See the documents "Widevine Core Message Serialization" and "License + * Duration and Renewal" for a detailed description of the ODK API. You can + * find these documents in the widevine repository as + * docs/Widevine_Core_Message_Serialization.pdf and + * docs/License_Duration_and_Renewal.pdf + * + *********************************************************************/ + +#ifndef WIDEVINE_ODK_INCLUDE_ODK_H_ +#define WIDEVINE_ODK_INCLUDE_ODK_H_ + +#include + +#include "OEMCryptoCENCCommon.h" +#include "odk_structs.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * ODK_InitializeSessionValues + * + * Description: + * This function initializes the session's data structures. It shall be + * called from OEMCrypto_OpenSession. + * + * Parameters: + * [out] timer_limits: the session's timer limits. + * [out] clock_values: the session's clock values. + * [out] nonce_values: the session's ODK nonce values. + * [in] api_major_version: the API version of OEMCrypto. + * [in] session_id: the session id of the newly created session. + * + * Returns: + * OEMCrypto_SUCCESS + * OEMCrypto_ERROR_INVALID_CONTEXT + * + * Version: + * This method is new in version 16 of the API. + */ +OEMCryptoResult ODK_InitializeSessionValues(ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, + ODK_NonceValues* nonce_values, + uint32_t api_major_version, + uint32_t session_id); + +/* + * ODK_SetNonceValues + * + * Description: + * This function sets the nonce value in the session's nonce structure. It + * shall be called from OEMCrypto_GenerateNonce. + * + * Parameters: + * [in/out] nonce_values: the session's nonce data. + * [in] nonce: the new nonce that was just generated. + * + * Returns: + * true on success + * + * Version: + * This method is new in version 16 of the API. + */ +OEMCryptoResult ODK_SetNonceValues(ODK_NonceValues* nonce_values, + uint32_t nonce); + +/* + * ODK_InitializeClockValues + * + * Description: + * This function initializes the clock values in the session clock_values + * structure. It shall be called from OEMCrypto_PrepAndSignLicenseRequest. + * + * Parameters: + * [in/out] clock_values: the session's clock data. + * [in] system_time_seconds: the current time on OEMCrypto's monotonic clock. + * + * Returns: + * OEMCrypto_SUCCESS + * OEMCrypto_ERROR_INVALID_CONTEXT + * + * Version: + * This method is new in version 16 of the API. + */ +OEMCryptoResult ODK_InitializeClockValues(ODK_ClockValues* clock_values, + uint64_t system_time_seconds); + +/* + * ODK_ReloadClockValues + * + * Description: + * This function sets the values in the clock_values structure. It shall be + * called from OEMCrypto_LoadUsageEntry. When a usage entry from a v15 or + * earlier license is loaded, the value time_of_license_loaded shall be used + * in place of time_of_license_signed. + * + * Parameters: + * [in/out] clock_values: the session's clock data. + * [in] time_of_license_signed: the value time_license_received from the + * loaded usage entry. + * [in] time_of_first_decrypt: the value time_of_first_decrypt from the + * loaded usage entry. + * [in] time_of_last_decrypt: the value time_of_last_decrypt from the loaded + * usage entry. + * [in] status: the value status from the loaded usage entry. + * [in] system_time_seconds: the current time on OEMCrypto's monotonic clock. + * + * Returns: + * OEMCrypto_SUCCESS + * OEMCrypto_ERROR_INVALID_CONTEXT + * + * Version: + * This method is new in version 16 of the API. + */ +OEMCryptoResult ODK_ReloadClockValues(ODK_ClockValues* clock_values, + uint64_t time_of_license_signed, + uint64_t time_of_first_decrypt, + uint64_t time_of_last_decrypt, + enum OEMCrypto_Usage_Entry_Status status, + uint64_t system_time_seconds); + +/* + * ODK_AttemptFirstPlayback + * + * Description: + * This updates the clock values, and determines if playback may start based + * on the given system time. It uses the values in clock_values to determine + * if this is the first playback for the license or the first playback for + * just this session. + * + * This shall be called from the first call in a session to any of + * OEMCrypto_DecryptCENC or any of the OEMCrypto_Generic* functions. + * + * If OEMCrypto uses a hardware timer, and this function returns + * ODK_SET_TIMER, then the timer should be set to the value pointed to by + * timer_value. + * + * Parameters: + * [in] system_time_seconds: the current time on OEMCrypto's monotonic clock, + * in seconds. + * [in] timer_limits: timer limits specified in the license. + * [in/out] clock_values: the sessions clock values. + * [out] timer_value: set to the new timer value. Only used if the return + * value is ODK_SET_TIMER. This must be non-null if OEMCrypto uses a + * hardware timer. + * + * Returns: + * ODK_SET_TIMER: Success. The timer should be reset to the specified value + * and playback is allowed. + * ODK_DISABLE_TIMER: Success, but disable timer. Unlimited playback is + * allowed. + * ODK_TIMER_EXPIRED: Set timer as disabled. Playback is not allowed. + * + * Version: + * This method is new in version 16 of the API. + */ +OEMCryptoResult ODK_AttemptFirstPlayback(uint64_t system_time_seconds, + const ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, + uint64_t* timer_value); + +/* + * ODK_UpdateLastPlaybackTime + * + * Description: + * Vendors that do not implement their own timer should call + * ODK_UpdateLastPlaybackTime regularly during playback. This updates the + * clock values, and determines if playback may continue based on the given + * system time. This shall be called from any of OEMCrypto_DecryptCENC or any + * of the OEMCrypto_Generic* functions. + * + * All Vendors (i.e. those that do or do not implement their own timer) shall + * call ODK_UpdateLastPlaybackTime from the function + * OEMCrypto_UpdateUsageEntry before updating the usage entry so that the + * clock values are accurate. + * + * Parameters: + * [in] system_time_seconds: the current time on OEMCrypto's monotonic clock, + * in seconds. + * [in] timer_limits: timer limits specified in the license. + * [in/out] clock_values: the sessions clock values. + * + * Returns: + * OEMCrypto_SUCCESS: Success. Playback is allowed. + * ODK_TIMER_EXPIRED: Set timer as disabled. Playback is not allowed. + * + * Version: + * This method is new in version 16 of the API. + */ +OEMCryptoResult ODK_UpdateLastPlaybackTime(uint64_t system_time_seconds, + const ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values); + +/* + * ODK_DeactivateUsageEntry + * + * Description: + * This function modifies the session's clock values to indicate that the + * license has been deactivated. It shall be called from + * OEMCrypto_DeactivateUsageEntry + * + * Parameters: + * [in/out] clock_values: the sessions clock values. + * + * Returns: + * OEMCrypto_SUCCESS + * OEMCrypto_ERROR_INVALID_CONTEXT + * + * Version: + * This method is new in version 16 of the API. + */ +OEMCryptoResult ODK_DeactivateUsageEntry(ODK_ClockValues* clock_values); + +/* + * ODK_PrepareCoreLicenseRequest + * + * Description: + * Modifies the message to include a core license request at the beginning of + * the message buffer. The values in nonce_values are used to populate the + * message. + * + * This shall be called by OEMCrypto from OEMCrypto_PrepAndSignLicenseRequest. + * + * NOTE: if the message pointer is null and/or input core_message_size is + * zero, this function returns OEMCrypto_ERROR_SHORT_BUFFER and sets output + * core_message_size to the size needed. + * + * Parameters: + * [in/out] message: Pointer to memory for the entire message. Modified by + * the ODK library. + * [in] message_length: length of the entire message buffer. + * [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. + * [in] nonce_values: pointer to the session's nonce data. + * + * Returns: + * OEMCrypto_SUCCESS + * OEMCrypto_ERROR_SHORT_BUFFER: core_message_size is too small + * OEMCrypto_ERROR_INVALID_CONTEXT + * + * Version: + * This method is new in version 16 of the API. + */ +OEMCryptoResult ODK_PrepareCoreLicenseRequest( + uint8_t* message, size_t message_length, size_t* core_message_size, + const ODK_NonceValues* nonce_values); + +/* + * ODK_PrepareCoreRenewalRequest + * + * Description: + * Modifies the message to include a core renewal request at the beginning of + * the message buffer. The values in nonce_values, clock_values and + * system_time_seconds are used to populate the message. The nonce_values + * should match those from the license. + * + * This shall be called by OEMCrypto from OEMCrypto_PrepAndSignRenewalRequest. + * + * If status in clock_values indicates that a license has not been loaded, + * then this is a license release. The ODK library will change the value of + * nonce_values.api_major_version to 15. This will make + * OEMCrypto_PrepAndSignRenewalRequest sign just the message body, as it does + * for all legacy licenses. + * + * NOTE: if the message pointer is null and/or input core_message_size is + * zero, this function returns OEMCrypto_ERROR_SHORT_BUFFER and sets output + * core_message_size to the size needed. + * + * Parameters: + * [in/out] message: Pointer to memory for the entire message. Modified by + * the ODK library. + * [in] message_length: length of the entire message buffer. + * [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. + * [in/out] nonce_values: pointer to the session's nonce data. + * [in/out] clock_values: the session's clock values. + * [in] system_time_seconds: the current time on OEMCrypto's clock, in + * seconds. + * + * Returns: + * OEMCrypto_SUCCESS + * OEMCrypto_ERROR_SHORT_BUFFER: core_message_size is too small + * OEMCrypto_ERROR_INVALID_CONTEXT + * + * Version: + * This method is new in version 16 of the API. + */ +OEMCryptoResult ODK_PrepareCoreRenewalRequest(uint8_t* message, + size_t message_length, + size_t* core_message_size, + ODK_NonceValues* nonce_values, + ODK_ClockValues* clock_values, + uint64_t system_time_seconds); + +/* + * ODK_PrepareCoreProvisioningRequest + * + * Description: + * Modifies the message to include a core provisioning request at the + * beginning of the message buffer. The values in nonce_values are used to + * populate the message. + * + * This shall be called by OEMCrypto from + * OEMCrypto_PrepAndSignProvisioningRequest. + * + * The buffer device_id shall be the same string returned by + * OEMCrypto_GetDeviceID. The device ID shall be unique to the device, and + * stable across reboots and factory resets for an L1 device. + * + * NOTE: if the message pointer is null and/or input core_message_size is + * zero, this function returns OEMCrypto_ERROR_SHORT_BUFFER and sets output + * core_message_size to the size needed. + * + * Parameters: + * [in/out] message: Pointer to memory for the entire message. Modified by + * the ODK library. + * [in] message_length: length of the entire message buffer. + * [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. + * [in] nonce_values: pointer to the session's nonce data. + * [in] device_id: For devices with a keybox, this is the device ID from the + * keybox. For devices with an OEM Certificate, this is a device unique + * id string. + * [in] device_id_length: length of device_id. The device ID can be at most + * 64 bytes. + * + * Returns: + * OEMCrypto_SUCCESS + * OEMCrypto_ERROR_SHORT_BUFFER: core_message_size is too small + * OEMCrypto_ERROR_INVALID_CONTEXT + * + * Version: + * This method is new in version 16 of the API. + */ +OEMCryptoResult ODK_PrepareCoreProvisioningRequest( + uint8_t* message, size_t message_length, size_t* core_message_size, + const ODK_NonceValues* nonce_values, const uint8_t* device_id, + size_t device_id_length); + +/* + * ODK_InitializeV15Values + * + * Description: + * This function sets all limits in the timer_limits struct to the + * key_duration and initializes the other values. The field + * nonce_values.api_major_version will be set to 15. It shall be called from + * OEMCrypto_LoadKeys when loading a legacy license. + * + * Parameters: + * [out] timer_limits: The session's timer limits. + * [in/out] clock_values: The session's clock values. + * [in/out] nonce_values: The session's ODK nonce values. + * [in] key_duration: The duration from the first key's key control block. In + * practice, the key duration is the same for all keys and is the same + * as the license duration. + * [in] system_time_seconds: The current time on the system clock, as + * described in the document "License Duration and Renewal". + * + * Returns: + * OEMCrypto_SUCCESS + * OEMCrypto_ERROR_INVALID_CONTEXT + * + * Version: + * This method is new in version 16 of the API. + */ +OEMCryptoResult ODK_InitializeV15Values(ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, + ODK_NonceValues* nonce_values, + uint32_t key_duration, + uint64_t system_time_seconds); + +/* + * ODK_RefreshV15Values + * + * Description: + * This function updates the clock_values as needed if a v15 renewal is + * accepted. The field nonce_values.api_major_version is verified to be 15. + * + * This is called from OEMCrypto_RefreshKeys for a valid license renewal. + * OEMCrypto shall pass in the current system time, and the key duration from + * the first object in the OEMCrypto_KeyRefreshObject. + * + * Parameters: + * [in] timer_limits: The session's timer limits. + * [in/out] clock_values: The session's clock values. + * [in] nonce_values: The session's ODK nonce values. + * [in] system_time_seconds: The current time on the system clock, as + * described in the document "License Duration and Renewal". + * [in] new_key_duration: The duration from the first + * OEMCrypto_KeyRefreshObject in key_array. + * [out] timer_value: set to the new timer value. Only used if the return + * value is ODK_SET_TIMER. This must be non-null if OEMCrypto uses a + * hardware timer. + * + * Returns: + * OEMCrypto_SUCCESS + * OEMCrypto_ERROR_UNKNOWN_FAILURE + * ODK_SET_TIMER: Success. The timer should be reset to the specified value + * and playback is allowed. + * ODK_DISABLE_TIMER: Success, but disable timer. Unlimited playback is + * allowed. + * ODK_TIMER_EXPIRED: Set timer as disabled. Playback is not allowed. + * + * Version: + * This method is new in version 16 of the API. + */ +OEMCryptoResult ODK_RefreshV15Values(const ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, + const ODK_NonceValues* nonce_values, + uint64_t system_time_seconds, + uint32_t new_key_duration, + uint64_t* timer_value); + +/* + * ODK_ParseLicense + * + * Description: + * The function ODK_ParseLicense will parse the message and verify fields in + * the message. + * + * If the message does not parse correctly, ODK_VerifyAndParseLicense will + * return ODK_ERROR_CORE_MESSAGE that OEMCrypto should return to the CDM + * layer above. + * + * If the API in the message is not 16, then ODK_UNSUPPORTED_API is returned. + * + * If initial_license_load is true, and nonce_required in the license is + * true, then the ODK library shall verify that nonce_values->nonce and + * nonce_values->session_id are the same as those in the message. If + * verification fails, then it shall return OEMCrypto_ERROR_INVALID_NONCE. + * + * If initial_license_load is false, and nonce_required is true, then + * ODK_ParseLicense will set the values in nonce_values from those in the + * message. + * + * The function ODK_ParseLicense will verify that each substring points to a + * location in the message body. The message body is the buffer starting at + * message + core_message_length with size message_length - + * core_message_length. + * + * If initial_license_load is true, then ODK_ParseLicense shall verify that + * the parameter request_hash matches request_hash in the parsed license. If + * verification fails, then it shall return ODK_ERROR_CORE_MESSAGE. This was + * computed by OEMCrypto when the license was requested. + * + * If usage_entry_present is true, then ODK_ParseLicense shall verify that + * the pst in the license has a nonzero length. + * + * Parameters: + * [in] message: pointer to the message buffer. + * [in] message_length: length of the entire message buffer. + * [in] core_message_size: length of the core message, at the beginning of + * the message buffer. + * [in] initial_license_load: true when called for OEMCrypto_LoadLicense and + * false when called for OEMCrypto_ReloadLicense. + * [in] usage_entry_present: true if the session has a new usage entry + * associated with it created via OEMCrypto_CreateNewUsageEntry. + * [in] request_hash: the hash of the license request core message. This was + * computed by OEMCrypto when the license request was signed. + * [in/out] timer_limits: The session's timer limits. These will be updated. + * [in/out] clock_values: The session's clock values. These will be updated. + * [in/out] nonce_values: The session's nonce values. These will be updated. + * [out] parsed_license: the destination for the data. + * + * Returns: + * OEMCrypto_SUCCESS + * ODK_ERROR_CORE_MESSAGE: if the message did not parse correctly, or there + * were other incorrect values. An error should be returned to the CDM + * layer. + * ODK_UNSUPPORTED_API + * OEMCrypto_ERROR_INVALID_NONCE + * + * Version: + * This method is new in version 16 of the API. + */ +OEMCryptoResult ODK_ParseLicense( + const uint8_t* message, size_t message_length, size_t core_message_length, + bool initial_license_load, bool usage_entry_present, + const uint8_t request_hash[ODK_SHA256_HASH_SIZE], + ODK_TimerLimits* timer_limits, ODK_ClockValues* clock_values, + ODK_NonceValues* nonce_values, ODK_ParsedLicense* parsed_license); + +/* + * ODK_ParseRenewal + * + * Description: + * The function ODK_ParseRenewal will parse the message and verify its + * contents. If the message does not parse correctly, an error of + * ODK_ERROR_CORE_MESSAGE is returned. + * + * ODK_ParseRenewal shall verify that all fields in nonce_values match those + * in the license. Otherwise it shall return OEMCrypto_ERROR_INVALID_NONCE. + * + * After parsing the message, this function updates the clock_values based on + * the timer_limits and the current system time. If playback may not + * continue, then ODK_TIMER_EXPIRED is returned. + * + * If playback may continue, a return value of ODK_SET_TIMER or + * ODK_TIMER_EXPIRED is returned. If the return value is ODK_SET_TIMER, then + * playback may continue until the timer expires. If the return value is + * ODK_DISABLE_TIMER, then playback time is not limited. + * + * If OEMCrypto uses a hardware timer, and this function returns + * ODK_SET_TIMER, then OEMCrypto shall set the timer to the value pointed to + * by timer_value. + * + * Parameters: + * [in] message: pointer to the message buffer. + * [in] message_length: length of the entire message buffer. + * [in] core_message_size: length of the core message, at the beginning of + * the message buffer. + * [in] nonce_values: pointer to the session's nonce data. + * [in] system_time_seconds: the current time on OEMCrypto's clock, in + * seconds. + * [in] timer_limits: timer limits specified in the license. + * [in/out] clock_values: the sessions clock values. + * [out] timer_value: set to the new timer value. Only used if the return + * value is ODK_SET_TIMER. This must be non-null if OEMCrypto uses a + * hardware timer. + * + * Returns: + * ODK_ERROR_CORE_MESSAGE: the message did not parse correctly, or there were + * other incorrect values. An error should be returned to the CDM layer. + * ODK_SET_TIMER: Success. The timer should be reset to the specified timer + * value. + * ODK_DISABLE_TIMER: Success, but disable timer. Unlimited playback is + * allowed. + * ODK_TIMER_EXPIRED: Set timer as disabled. Playback is not allowed. + * ODK_UNSUPPORTED_API + * ODK_STALE_RENEWAL: This renewal is not the most recently signed. It is + * rejected. + * OEMCrypto_ERROR_INVALID_NONCE + * + * Version: + * This method is new in version 16 of the API. + */ +OEMCryptoResult ODK_ParseRenewal(const uint8_t* message, size_t message_length, + size_t core_message_length, + const ODK_NonceValues* nonce_values, + uint64_t system_time_seconds, + const ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, + uint64_t* timer_value); + +/* + * ODK_ParseProvisioning + * + * Description: + * The function ODK_ParseProvisioning will parse the message and verify the + * nonce values match those in the license. + * + * If the message does not parse correctly, ODK_ParseProvisioning will return + * an error that OEMCrypto should return to the CDM layer above. + * + * If the API in the message is larger than 16, then ODK_UNSUPPORTED_API is + * returned. + * + * ODK_ParseProvisioning shall verify that nonce_values->nonce and + * nonce_values->session_id are the same as those in the message. Otherwise + * it shall return OEMCrypto_ERROR_INVALID_NONCE. + * + * The function ODK_ParseProvisioning will verify that each substring points + * to a location in the message body. The message body is the buffer starting + * at message + core_message_length with size message_length - + * core_message_length. + * + * Parameters: + * [in] message: pointer to the message buffer. + * [in] message_length: length of the entire message buffer. + * [in] core_message_size: length of the core message, at the beginning of + * the message buffer. + * [in] nonce_values: pointer to the session's nonce data. + * [in] device_id: a pointer to a buffer containing the device ID of the + * device. The ODK function will verify it matches that in the message. + * [in] device_id_length: the length of the device ID. + * [out] parsed_response: destination for the parse data. + * + * Returns: + * OEMCrypto_SUCCESS + * ODK_ERROR_CORE_MESSAGE: the message did not parse correctly, or there were + * other incorrect values. An error should be returned to the CDM layer. + * ODK_UNSUPPORTED_API + * OEMCrypto_ERROR_INVALID_NONCE + * + * Version: + * This method is new in version 16 of the API. + */ +OEMCryptoResult ODK_ParseProvisioning( + const uint8_t* message, size_t message_length, size_t core_message_length, + const ODK_NonceValues* nonce_values, const uint8_t* device_id, + size_t device_id_length, ODK_ParsedProvisioning* parsed_response); + +#ifdef __cplusplus +} +#endif + +#endif /* WIDEVINE_ODK_INCLUDE_ODK_H_ */ diff --git a/oemcrypto/odk/include/odk_structs.h b/oemcrypto/odk/include/odk_structs.h new file mode 100644 index 0000000..5c9ff30 --- /dev/null +++ b/oemcrypto/odk/include/odk_structs.h @@ -0,0 +1,221 @@ +/* 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 WIDEVINE_ODK_INCLUDE_ODK_STRUCTS_H_ +#define WIDEVINE_ODK_INCLUDE_ODK_STRUCTS_H_ + +#include + +#include "OEMCryptoCENCCommon.h" +#include "odk_target.h" + +/* The version of this library. */ +#define ODK_MAJOR_VERSION 16 +#define ODK_MINOR_VERSION 3 + +/* ODK Version string. Date changed automatically on each release. */ +#define ODK_RELEASE_DATE "ODK v16.3 2020-06-02" + +/* The lowest version number for an ODK message. */ +#define ODK_FIRST_VERSION 16 + +/* Some useful constants. */ +#define ODK_DEVICE_ID_LEN_MAX 64 +#define ODK_SHA256_HASH_SIZE 32 + +/* + * ODK_TimerLimits Structure + * + * Description: + * Timer limits are specified in a license and are used to determine when + * playback is allowed. See the document "License Duration and Renewal" for a + * discussion on the time restrictions that may be placed on a license. The + * fields in this structure are directly related to the fields in the core + * license message. The fields are set when OEMCrypto calls the function + * ODK_ParseLicense or ODK_InitializeV15Values. + * + * Fields: + * soft_enforce_rental_duration: A boolean controlling the soft or hard + * enforcement of rental duration. + * soft_enforce_playback_duration: A boolean controlling the soft or hard + * enforcement of playback duration. + * earliest_playback_start_seconds: The earliest time that the first playback + * is allowed. Measured in seconds since the license request was signed. For + * most use cases, this is zero. + * rental_duration_seconds: Window of time for the allowed first playback. + * Measured in seconds since the earliest playback start. If + * soft_enforce_rental_duration is true, this applies only to the first + * playback. If soft_enforce_rental_duration is false, then this restricts + * any playback. A value of zero means no limit. + * total_playback_duration_seconds: Window of time for allowed playback. + * Measured in seconds since the first playback start. If + * soft_enforce_playback_duration is true, this applies only to the start of + * playback for any session. If soft_enforce_playback_duration is false, then + * this restricts any playback. A value of zero means no limit. + * initial_renewal_duration_seconds: Window of time for allowed playback. + * Measured in seconds since the first playback start. This value is only + * used to start the renewal timer. After a renewal message is loaded, the + * timer will be reset. A value of zero means no limit. + * + * Version: + * This struct changed in API version 16.2. + */ +typedef struct { + bool soft_enforce_rental_duration; + bool soft_enforce_playback_duration; + uint64_t earliest_playback_start_seconds; + uint64_t rental_duration_seconds; + uint64_t total_playback_duration_seconds; + uint64_t initial_renewal_duration_seconds; +} ODK_TimerLimits; + +/* + * ODK_ClockValues Structure + * + * Description: + * Clock values are modified when decryption occurs or when a renewal is + * processed. They are used to track the current status of the license -- + * i.e. has playback started? When does the timer expire? See the section + * "Complete ODK API" of the document "Widevine Core Message Serialization" + * for a complete list of all fields in this structure. Most of these values + * shall be saved with the usage entry. + * + * All times are in seconds. Most of the fields in this structure are saved + * in the usage entry. This structure should be initialized when a usage + * entry is created or loaded, and should be used to save a usage entry. It + * is updated using the ODK functions listed below. The time values are based + * on OEMCrypto's system clock, as described in the document "License + * Duration and Renewal". + * + * Fields: + * time_of_license_signed: Time that the license request was signed, based on + * OEMCrypto's system clock. This value shall be stored and reloaded with + * usage entry as time_of_license_received. + * time_of_first_decrypt: Time of the first decrypt or call select key, based + * on OEMCrypto's system clock. This is 0 if the license has not been used to + * decrypt any data. This value shall be stored and reloaded with usage entry. + * time_of_last_decrypt: Time of the most recent decrypt call, based on + * OEMCrypto's system clock. This value shall be stored and reloaded with + * usage entry. + * time_of_renewal_request: Time of the most recent renewal request, based on + * OEMCrypto's system clock. This is used to verify that a renewal is not + * stale. + * time_when_timer_expires: Time that the current timer expires, based on + * OEMCrypto's system clock. If the timer is active, this is used by the ODK + * library to determine if it has expired. + * timer_status: Used internally by the ODK library to indicate the current + * timer status. + * status: The license or usage entry status. This value shall be stored and + * reloaded with usage entry. + * + * Version: + * This struct changed in API version 16.2. + */ +typedef struct { + uint64_t time_of_license_signed; + uint64_t time_of_first_decrypt; + uint64_t time_of_last_decrypt; + uint64_t time_of_renewal_request; + uint64_t time_when_timer_expires; + uint32_t timer_status; + enum OEMCrypto_Usage_Entry_Status status; +} ODK_ClockValues; + +/* + * ODK_NonceValues Structure + * + * Description: + * Nonce values are used to match a license or provisioning request to a + * license or provisioning response. They are also used to match a renewal + * request and response to a license. For this reason, the api_version might + * be lower than that supported by OEMCrypto. The api_version matches the + * version of the license. Similarly the nonce and session_id match the + * session that generated the license request. For an offline license, these + * might not match the session that is loading the license. We use the nonce + * to prevent a license from being replayed. By also including a session_id + * in the license request and license response, we prevent an attack using + * the birthday paradox to generate nonce collisions on a single device. + * + * Fields: + * api_major_version: the API version of the license. This is initialized to + * the API version of the ODK library, but may be lower. + * api_minor_version: the minor version of the ODK library. This is used by + * the server to verify that device is not using an obsolete version of the + * ODK library. + * nonce: a randomly generated number used to prevent replay attacks. + * session_id: the session id of the session which signed the license or + * provisioning request. It is used to prevent replay attacks from one + * session to another. + * + * Version: + * This struct changed in API version 16.2. + */ +typedef struct { + uint16_t api_minor_version; + uint16_t api_major_version; + uint32_t nonce; + uint32_t session_id; +} ODK_NonceValues; + +/* + * ODK_ParsedLicense Structure + * + * Description: + * The parsed license structure contains information from the license + * message. The function ODK_ParseLicense will fill in the fields of this + * message. All substrings are contained within the message body. + * + * Fields: + * enc_mac_keys_iv: IV for decrypting new mac_key. Size is 128 bits. + * enc_mac_keys: encrypted mac_keys for generating new mac_keys. Size is 512 + * bits. + * pst: the Provider Session Token. + * srm_restriction_data: optional data specifying the minimum SRM version. + * license_type: specifies if the license contains content keys or + * entitlement keys. + * nonce_required: indicates if the license requires a nonce. + * timer_limits: time limits of the for the license. + * key_array_length: number of keys present. + * key_array: set of keys to be installed. + * + * Version: + * This struct changed in API version 16.2. + */ +typedef struct { + OEMCrypto_Substring enc_mac_keys_iv; + OEMCrypto_Substring enc_mac_keys; + OEMCrypto_Substring pst; + OEMCrypto_Substring srm_restriction_data; + OEMCrypto_LicenseType license_type; + bool nonce_required; + ODK_TimerLimits timer_limits; + uint32_t key_array_length; + OEMCrypto_KeyObject key_array[ODK_MAX_NUM_KEYS]; +} ODK_ParsedLicense; + +/* + * ODK_ParsedProvisioning Structure + * + * Description: + * The parsed provisioning structure contains information from the license + * message. The function ODK_ParseProvisioning will fill in the fields of + * this message. All substrings are contained within the message body. + * + * Fields: + * key_type: indicates if this key is an RSA or ECC private key. + * enc_private_key: encrypted private key for the DRM certificate. + * enc_private_key_iv: IV for decrypting new private key. Size is 128 bits. + * encrypted_message_key: used for provisioning 3.0 to derive keys. + * + * Version: + * This struct changed in API version 16.2. + */ +typedef struct { + OEMCrypto_PrivateKeyType key_type; + OEMCrypto_Substring enc_private_key; + OEMCrypto_Substring enc_private_key_iv; + OEMCrypto_Substring encrypted_message_key; /* Used for Prov 3.0 */ +} ODK_ParsedProvisioning; + +#endif /* WIDEVINE_ODK_INCLUDE_ODK_STRUCTS_H_ */ diff --git a/oemcrypto/odk/include/odk_target.h b/oemcrypto/odk/include/odk_target.h new file mode 100644 index 0000000..9225210 --- /dev/null +++ b/oemcrypto/odk/include/odk_target.h @@ -0,0 +1,13 @@ +/* Copyright 2019 Google LLC. All rights reserved. This file is distributed */ +/* under the Widevine Master License Agreement. */ + +/* Partners are expected to edit this file to support target specific code */ +/* and limits. */ + +#ifndef WIDEVINE_ODK_INCLUDE_ODK_TARGET_H_ +#define WIDEVINE_ODK_INCLUDE_ODK_TARGET_H_ + +/* Maximum number of keys can be modified to suit target's resource tier. */ +#define ODK_MAX_NUM_KEYS 32 + +#endif /* WIDEVINE_ODK_INCLUDE_ODK_TARGET_H_ */ diff --git a/oemcrypto/odk/src/core_message_deserialize.cpp b/oemcrypto/odk/src/core_message_deserialize.cpp new file mode 100644 index 0000000..1cef5ee --- /dev/null +++ b/oemcrypto/odk/src/core_message_deserialize.cpp @@ -0,0 +1,133 @@ +// 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. + +#include "core_message_deserialize.h" + +#include +#include +#include +#include +#include + +#include "odk_serialize.h" +#include "odk_structs.h" +#include "odk_structs_priv.h" +#include "serialization_base.h" + +namespace oemcrypto_core_message { +namespace deserialize { +namespace { + +/** + * Template for parsing requests + * + * Template arguments: + * S: kdo output struct + * T: struct serialized by odk + * U: auto-generated deserializing function for |T| + */ +template +bool ParseRequest(uint32_t message_type, + const std::string& oemcrypto_core_message, S* core_request, + T* prepared, const U unpacker) { + if (core_request == nullptr || prepared == nullptr) { + return false; + } + + const uint8_t* buf = + reinterpret_cast(oemcrypto_core_message.c_str()); + const size_t buf_length = oemcrypto_core_message.size(); + + Message* msg = nullptr; + AllocateMessage(&msg, message_block); + InitMessage(msg, const_cast(buf), buf_length); + SetSize(msg, buf_length); + + unpacker(msg, prepared); + if (!ValidMessage(msg)) { + return false; + } + + const auto& core_message = prepared->core_message; + core_request->api_major_version = core_message.nonce_values.api_major_version; + core_request->api_minor_version = core_message.nonce_values.api_minor_version; + core_request->nonce = core_message.nonce_values.nonce; + core_request->session_id = core_message.nonce_values.session_id; + // Verify that the minor version matches the released version for the given + // major version. + if (core_request->api_major_version < ODK_FIRST_VERSION) { + // Non existing versions are not supported. + return false; + } else if (core_request->api_major_version == 16) { + // For version 16, we demand a minor version of at least 2. + // We accept 16.2, 16.3, or higher. + if (core_request->api_major_version < 2) return false; + } else { + // Other versions do not (yet) have a restriction on minor number. + // In particular, future versions are accepted for forward compatibility. + } + // For v16, a release and a renewal use the same message structure. + // However, for future API versions, the release might be a separate + // message. Otherwise, we expect an exact match of message types. + if (core_message.message_type != message_type && + !(message_type == ODK_Renewal_Request_Type && + core_message.message_type == ODK_Release_Request_Type)) { + return false; + } + // Verify that the amount of buffer we read, which is GetOffset, is not more + // than the total message size. We allow the total message size to be larger + // for forward compatibility because future messages might have extra fields + // that we can ignore. + if (core_message.message_length < GetOffset(msg)) return false; + return true; +} + +} // namespace + +bool CoreLicenseRequestFromMessage(const std::string& oemcrypto_core_message, + ODK_LicenseRequest* core_license_request) { + const auto unpacker = Unpack_ODK_PreparedLicenseRequest; + ODK_PreparedLicenseRequest prepared_license = {}; + return ParseRequest(ODK_License_Request_Type, oemcrypto_core_message, + core_license_request, &prepared_license, unpacker); +} + +bool CoreRenewalRequestFromMessage(const std::string& oemcrypto_core_message, + ODK_RenewalRequest* core_renewal_request) { + const auto unpacker = Unpack_ODK_PreparedRenewalRequest; + ODK_PreparedRenewalRequest prepared_renewal = {}; + if (!ParseRequest(ODK_Renewal_Request_Type, oemcrypto_core_message, + core_renewal_request, &prepared_renewal, unpacker)) { + return false; + } + core_renewal_request->playback_time_seconds = prepared_renewal.playback_time; + return true; +} + +bool CoreProvisioningRequestFromMessage( + const std::string& oemcrypto_core_message, + ODK_ProvisioningRequest* core_provisioning_request) { + const auto unpacker = Unpack_ODK_PreparedProvisioningRequest; + ODK_PreparedProvisioningRequest prepared_provision = {}; + if (!ParseRequest(ODK_Provisioning_Request_Type, oemcrypto_core_message, + core_provisioning_request, &prepared_provision, unpacker)) { + return false; + } + const uint8_t* device_id = prepared_provision.device_id; + const uint32_t device_id_length = prepared_provision.device_id_length; + if (device_id_length > ODK_DEVICE_ID_LEN_MAX) { + return false; + } + uint8_t zero[ODK_DEVICE_ID_LEN_MAX] = {}; + if (memcmp(zero, device_id + device_id_length, + ODK_DEVICE_ID_LEN_MAX - device_id_length)) { + return false; + } + core_provisioning_request->device_id.assign( + reinterpret_cast(device_id), device_id_length); + return true; +} + +} // namespace deserialize +} // namespace oemcrypto_core_message diff --git a/oemcrypto/odk/src/core_message_serialize.cpp b/oemcrypto/odk/src/core_message_serialize.cpp new file mode 100644 index 0000000..4546460 --- /dev/null +++ b/oemcrypto/odk/src/core_message_serialize.cpp @@ -0,0 +1,125 @@ +// 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. + +#include "core_message_serialize.h" + +#include +#include +#include +#include +#include + +#include "odk_serialize.h" +#include "odk_structs.h" +#include "odk_structs_priv.h" +#include "serialization_base.h" + +namespace oemcrypto_core_message { +namespace serialize { +namespace { + +/** + * Template for parsing requests + * + * Template arguments: + * T: struct to be deserialized by odk + * S: kdo input struct + * P: auto-generated serializing function for |T| + */ +template +bool CreateResponse(uint32_t message_type, const S& core_request, + std::string* oemcrypto_core_message, T& response, + const P& packer) { + if (!oemcrypto_core_message) { + return false; + } + + auto* header = &response.request.core_message; + header->message_type = message_type; + header->nonce_values.api_major_version = core_request.api_major_version; + header->nonce_values.api_minor_version = core_request.api_minor_version; + header->nonce_values.nonce = core_request.nonce; + header->nonce_values.session_id = core_request.session_id; + // The message API version for the response is the minimum of our version and + // the request's version. + if (core_request.api_major_version > ODK_MAJOR_VERSION) { + header->nonce_values.api_major_version = ODK_MAJOR_VERSION; + header->nonce_values.api_minor_version = ODK_MINOR_VERSION; + } + + static constexpr size_t BUF_CAPACITY = 2048; + std::vector buf(BUF_CAPACITY, 0); + Message* msg = nullptr; + AllocateMessage(&msg, message_block); + InitMessage(msg, buf.data(), buf.capacity()); + packer(msg, &response); + if (!ValidMessage(msg)) { + return false; + } + + uint32_t message_length = GetSize(msg); + InitMessage(msg, buf.data() + sizeof(header->message_type), + sizeof(header->message_length)); + Pack_uint32_t(msg, &message_length); + oemcrypto_core_message->assign(reinterpret_cast(buf.data()), + message_length); + return true; +} + +bool CopyDeviceId(const ODK_ProvisioningRequest& src, + ODK_ProvisioningResponse* dest) { + auto& request = dest->request; + const std::string& device_id = src.device_id; + if (request.device_id_length > sizeof(request.device_id)) { + return false; + } + request.device_id_length = device_id.size(); + memset(request.device_id, 0, sizeof(request.device_id)); + memcpy(request.device_id, device_id.data(), request.device_id_length); + return true; +} + +} // namespace + +bool CreateCoreLicenseResponse(const ODK_ParsedLicense& parsed_lic, + const ODK_LicenseRequest& core_request, + const std::string& core_request_sha256, + std::string* oemcrypto_core_message) { + ODK_LicenseResponse license_response{ + {}, const_cast(&parsed_lic), {0}}; + if (core_request_sha256.size() != sizeof(license_response.request_hash)) + return false; + memcpy(license_response.request_hash, core_request_sha256.data(), + sizeof(license_response.request_hash)); + return CreateResponse(ODK_License_Response_Type, core_request, + oemcrypto_core_message, license_response, + Pack_ODK_LicenseResponse); +} + +bool CreateCoreRenewalResponse(const ODK_RenewalRequest& core_request, + uint64_t renewal_duration_seconds, + std::string* oemcrypto_core_message) { + ODK_RenewalResponse renewal_response{{}, core_request.playback_time_seconds}; + renewal_response.request.playback_time = core_request.playback_time_seconds; + renewal_response.renewal_duration_seconds = renewal_duration_seconds; + return CreateResponse(ODK_Renewal_Response_Type, core_request, + oemcrypto_core_message, renewal_response, + Pack_ODK_RenewalResponse); +} + +bool CreateCoreProvisioningResponse(const ODK_ParsedProvisioning& parsed_prov, + const ODK_ProvisioningRequest& core_request, + std::string* oemcrypto_core_message) { + ODK_ProvisioningResponse prov_response{ + {}, const_cast(&parsed_prov)}; + if (!CopyDeviceId(core_request, &prov_response)) { + return false; + } + return CreateResponse(ODK_Provisioning_Response_Type, core_request, + oemcrypto_core_message, prov_response, + Pack_ODK_ProvisioningResponse); +} + +} // namespace serialize +} // namespace oemcrypto_core_message diff --git a/oemcrypto/odk/src/core_message_serialize_proto.cpp b/oemcrypto/odk/src/core_message_serialize_proto.cpp new file mode 100644 index 0000000..cacba42 --- /dev/null +++ b/oemcrypto/odk/src/core_message_serialize_proto.cpp @@ -0,0 +1,186 @@ +// 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. + +#include "core_message_serialize_proto.h" + +#include +#include +#include +#include +#include + +#include "core_message_serialize.h" +#include "license_protocol.pb.h" +#include "odk_serialize.h" +#include "odk_structs.h" +#include "odk_structs_priv.h" +#include "serialization_base.h" + +namespace oemcrypto_core_message { +namespace serialize { +namespace { + +/* @ private functions */ + +/** + * Extract OEMCrypto_Substring (offset, length) from serialized protobuf + * + * Parameters: + * message: serialized license protobuf + * field: substring value + */ +OEMCrypto_Substring GetOecSubstring(const std::string& message, + const std::string& field) { + OEMCrypto_Substring substring = {}; + size_t pos = message.find(field); + if (pos != std::string::npos) { + substring = OEMCrypto_Substring{pos, field.length()}; + } + return substring; +} + +OEMCrypto_KeyObject KeyContainerToOecKey( + const std::string& proto, const video_widevine::License::KeyContainer& k) { + OEMCrypto_KeyObject obj = {}; + obj.key_id = GetOecSubstring(proto, k.id()); + obj.key_data_iv = GetOecSubstring(proto, k.iv()); + // Strip off PKCS#5 padding - since we know the key is 16 or 32 bytes, + // the padding will always be 16 bytes. + const std::string& key_data = k.key(); + const size_t PKCS5_PADDING_SIZE = 16; + obj.key_data = GetOecSubstring( + proto, key_data.substr(0, std::max(PKCS5_PADDING_SIZE, key_data.size()) - + PKCS5_PADDING_SIZE)); + if (k.has_key_control()) { + const auto& key_control = k.key_control(); + obj.key_control_iv = GetOecSubstring(proto, key_control.iv()); + obj.key_control = GetOecSubstring(proto, key_control.key_control_block()); + } + return obj; +} + +} // namespace + +// @ public create response functions + +bool CreateCoreLicenseResponseFromProto(const std::string& serialized_license, + const ODK_LicenseRequest& core_request, + const std::string& core_request_sha256, + const bool nonce_required, + std::string* oemcrypto_core_message) { + video_widevine::License lic; + if (!lic.ParseFromString(serialized_license)) { + return false; + } + + ODK_ParsedLicense parsed_lic{}; + bool any_content = false; + bool any_entitlement = false; + + for (int i = 0; i < lic.key_size(); ++i) { + const auto& k = lic.key(i); + switch (k.type()) { + case video_widevine::License_KeyContainer::SIGNING: { + if (!k.has_key()) { + continue; + } + parsed_lic.enc_mac_keys_iv = + GetOecSubstring(serialized_license, k.iv()); + parsed_lic.enc_mac_keys = GetOecSubstring(serialized_license, k.key()); + break; + } + case video_widevine::License_KeyContainer::CONTENT: { + any_content = true; + if (parsed_lic.key_array_length >= ODK_MAX_NUM_KEYS) { + return false; + } + uint32_t& n = parsed_lic.key_array_length; + parsed_lic.key_array[n++] = KeyContainerToOecKey(serialized_license, k); + break; + } + case video_widevine::License_KeyContainer::ENTITLEMENT: { + any_entitlement = true; + if (parsed_lic.key_array_length >= ODK_MAX_NUM_KEYS) { + return false; + } + uint32_t& n = parsed_lic.key_array_length; + parsed_lic.key_array[n++] = KeyContainerToOecKey(serialized_license, k); + break; + } + default: { + continue; + } + } + } + if (any_content && any_entitlement) { + // TODO(b/147513335): this should be logged -- both type of keys. + return false; + } + if (!any_content && !any_entitlement) { + // TODO(b/147513335): this should be logged -- no keys? + return false; + } + parsed_lic.license_type = + any_content ? OEMCrypto_ContentLicense : OEMCrypto_EntitlementLicense; + const auto& lid = lic.id(); + if (lid.has_provider_session_token()) { + parsed_lic.pst = + GetOecSubstring(serialized_license, lid.provider_session_token()); + } + + if (lic.has_srm_requirement()) { + parsed_lic.srm_restriction_data = + GetOecSubstring(serialized_license, lic.srm_requirement()); + } + + parsed_lic.nonce_required = nonce_required; + const auto& policy = lic.policy(); + ODK_TimerLimits& timer_limits = parsed_lic.timer_limits; + timer_limits.soft_enforce_rental_duration = + policy.soft_enforce_rental_duration(); + timer_limits.soft_enforce_playback_duration = + policy.soft_enforce_playback_duration(); + timer_limits.earliest_playback_start_seconds = 0; + timer_limits.rental_duration_seconds = policy.rental_duration_seconds(); + timer_limits.total_playback_duration_seconds = + policy.playback_duration_seconds(); + timer_limits.initial_renewal_duration_seconds = + policy.renewal_delay_seconds() + + policy.renewal_recovery_duration_seconds(); + + return CreateCoreLicenseResponse(parsed_lic, core_request, + core_request_sha256, oemcrypto_core_message); +} + +bool CreateCoreProvisioningResponseFromProto( + const std::string& serialized_provisioning_resp, + const ODK_ProvisioningRequest& core_request, + std::string* oemcrypto_core_message) { + ODK_ParsedProvisioning parsed_prov{}; + video_widevine::ProvisioningResponse prov; + if (!prov.ParseFromString(serialized_provisioning_resp)) { + return false; + } + + parsed_prov.key_type = + OEMCrypto_RSA_Private_Key; // TODO(b/148404408): ECC or RSA + if (prov.has_device_rsa_key()) { + parsed_prov.enc_private_key = + GetOecSubstring(serialized_provisioning_resp, prov.device_rsa_key()); + } + if (prov.has_device_rsa_key_iv()) { + parsed_prov.enc_private_key_iv = + GetOecSubstring(serialized_provisioning_resp, prov.device_rsa_key_iv()); + } + if (prov.has_wrapping_key()) { + parsed_prov.encrypted_message_key = + GetOecSubstring(serialized_provisioning_resp, prov.wrapping_key()); + } + + return CreateCoreProvisioningResponse(parsed_prov, core_request, + oemcrypto_core_message); +} + +} // namespace serialize +} // namespace oemcrypto_core_message diff --git a/oemcrypto/odk/src/odk.c b/oemcrypto/odk/src/odk.c new file mode 100644 index 0000000..42e82d5 --- /dev/null +++ b/oemcrypto/odk/src/odk.c @@ -0,0 +1,410 @@ +/* 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. */ + +#include "odk.h" + +#include +#include +#include + +#include "odk_overflow.h" +#include "odk_serialize.h" +#include "odk_structs.h" +#include "odk_structs_priv.h" +#include "odk_util.h" +#include "serialization_base.h" + +/* @ private odk functions */ + +static OEMCryptoResult ODK_PrepareRequest( + uint8_t* message, size_t message_length, size_t* core_message_length, + uint32_t message_type, const ODK_NonceValues* nonce_values, + void* prepared_request_buffer, size_t prepared_request_buffer_length) { + if (nonce_values == NULL || core_message_length == NULL || + prepared_request_buffer == NULL || + *core_message_length > message_length) { + return ODK_ERROR_CORE_MESSAGE; + } + + Message* msg = NULL; + AllocateMessage(&msg, message_block); + InitMessage(msg, message, *core_message_length); + + /* The core message should be at the beginning of the buffer, and with a + * shorter length. */ + if (sizeof(ODK_CoreMessage) > prepared_request_buffer_length) { + return ODK_ERROR_CORE_MESSAGE; + } + ODK_CoreMessage* core_message = (ODK_CoreMessage*)prepared_request_buffer; + *core_message = (ODK_CoreMessage){ + message_type, + 0, + *nonce_values, + }; + + /* Set core message length, and pack prepared request into message if the + * message buffer has been correctly initialized by the caller. */ + switch (message_type) { + case ODK_License_Request_Type: { + core_message->message_length = ODK_LICENSE_REQUEST_SIZE; + if (sizeof(ODK_PreparedLicenseRequest) > prepared_request_buffer_length) { + return ODK_ERROR_CORE_MESSAGE; + } + Pack_ODK_PreparedLicenseRequest( + msg, (ODK_PreparedLicenseRequest*)prepared_request_buffer); + break; + } + case ODK_Renewal_Request_Type: { + core_message->message_length = ODK_RENEWAL_REQUEST_SIZE; + if (sizeof(ODK_PreparedRenewalRequest) > prepared_request_buffer_length) { + return ODK_ERROR_CORE_MESSAGE; + } + Pack_ODK_PreparedRenewalRequest( + msg, (ODK_PreparedRenewalRequest*)prepared_request_buffer); + break; + } + case ODK_Provisioning_Request_Type: { + core_message->message_length = ODK_PROVISIONING_REQUEST_SIZE; + if (sizeof(ODK_PreparedProvisioningRequest) > + prepared_request_buffer_length) { + return ODK_ERROR_CORE_MESSAGE; + } + Pack_ODK_PreparedProvisioningRequest( + msg, (ODK_PreparedProvisioningRequest*)prepared_request_buffer); + break; + } + default: { + return ODK_ERROR_CORE_MESSAGE; + } + } + + *core_message_length = core_message->message_length; + if (GetStatus(msg) != MESSAGE_STATUS_OK) { + /* This is to indicate the caller that the core_message_length has been + * appropriately set, but the message buffer is either empty or too small, + * which needs to be initialized and filled in the subsequent call. */ + return OEMCrypto_ERROR_SHORT_BUFFER; + } + if (GetSize(msg) != *core_message_length) { + /* This should not happen. Something is wrong. */ + return ODK_ERROR_CORE_MESSAGE; + } + return OEMCrypto_SUCCESS; +} + +static OEMCryptoResult ODK_ParseResponse( + const uint8_t* message, size_t message_length, size_t core_message_length, + uint32_t message_type, const ODK_NonceValues* nonce_values, + void* response_buffer, uint32_t response_buffer_length) { + if (message == NULL || response_buffer == NULL || + core_message_length > message_length) { + return ODK_ERROR_CORE_MESSAGE; + } + + Message* msg = NULL; + AllocateMessage(&msg, message_block); + /* We initialize the message buffer with a size of the entire message + * length. */ + InitMessage(msg, (uint8_t*)message, message_length); + /* The core message should be at the beginning of the buffer, and with a + * shorter length. The core message is the part we are parsing. */ + SetSize(msg, core_message_length); + + /* Parse message and unpack it into response buffer. */ + switch (message_type) { + case ODK_License_Response_Type: { + if (sizeof(ODK_LicenseResponse) > response_buffer_length) { + return ODK_ERROR_CORE_MESSAGE; + } + Unpack_ODK_LicenseResponse(msg, (ODK_LicenseResponse*)response_buffer); + break; + } + case ODK_Renewal_Response_Type: { + if (sizeof(ODK_RenewalResponse) > response_buffer_length) { + return ODK_ERROR_CORE_MESSAGE; + } + Unpack_ODK_RenewalResponse(msg, (ODK_RenewalResponse*)response_buffer); + break; + } + case ODK_Provisioning_Response_Type: { + if (sizeof(ODK_ProvisioningResponse) > response_buffer_length) { + return ODK_ERROR_CORE_MESSAGE; + } + Unpack_ODK_ProvisioningResponse( + msg, (ODK_ProvisioningResponse*)response_buffer); + break; + } + default: { + return ODK_ERROR_CORE_MESSAGE; + } + } + + ODK_CoreMessage* core_message = (ODK_CoreMessage*)response_buffer; + if (GetStatus(msg) != MESSAGE_STATUS_OK || + message_type != core_message->message_type || + GetOffset(msg) != core_message->message_length) { + return ODK_ERROR_CORE_MESSAGE; + } + + if (nonce_values) { + /* always verify nonce_values for Renewal and Provisioning responses */ + if (!ODK_NonceValuesEqual(nonce_values, &(core_message->nonce_values))) { + return OEMCrypto_ERROR_INVALID_NONCE; + } + } + + return OEMCrypto_SUCCESS; +} + +/* @ public odk functions */ + +/* @@ prepare request functions */ + +OEMCryptoResult ODK_PrepareCoreLicenseRequest( + uint8_t* message, size_t message_length, size_t* core_message_length, + const ODK_NonceValues* nonce_values) { + if (core_message_length == NULL || nonce_values == NULL) { + return ODK_ERROR_CORE_MESSAGE; + } + ODK_PreparedLicenseRequest license_request = { + {0}, + }; + return ODK_PrepareRequest( + message, message_length, core_message_length, ODK_License_Request_Type, + nonce_values, &license_request, sizeof(ODK_PreparedLicenseRequest)); +} + +OEMCryptoResult ODK_PrepareCoreRenewalRequest(uint8_t* message, + size_t message_length, + size_t* core_message_size, + ODK_NonceValues* nonce_values, + ODK_ClockValues* clock_values, + uint64_t system_time_seconds) { + if (core_message_size == NULL || nonce_values == NULL || + clock_values == NULL) { + return ODK_ERROR_CORE_MESSAGE; + } + + /* If the license has not been loaded, then this is release instead of a + * renewal. All releases use v15. */ + if (clock_values->timer_status == ODK_CLOCK_TIMER_STATUS_LICENSE_NOT_LOADED || + clock_values->timer_status == ODK_CLOCK_TIMER_STATUS_LICENSE_INACTIVE) { + nonce_values->api_major_version = ODK_FIRST_VERSION - 1; + } + if (nonce_values->api_major_version < ODK_FIRST_VERSION) { + *core_message_size = 0; + return OEMCrypto_SUCCESS; + } + + ODK_PreparedRenewalRequest renewal_request = {{0}, 0}; + /* First, we compute the time this request was made relative to the playback + * clock. */ + if (clock_values->time_of_first_decrypt == 0) { + /* It is OK to preemptively request a renewal before playback starts. + * We'll treat this as asking for a renewal at playback time 0. */ + renewal_request.playback_time = 0; + } else { + /* Otherwise, playback_time is relative to the first decrypt. */ + if (odk_sub_overflow_u64(system_time_seconds, + clock_values->time_of_first_decrypt, + &renewal_request.playback_time)) { + return ODK_ERROR_CORE_MESSAGE; + } + } + + /* Save time for this request so that we can verify the response. This makes + * all earlier requests invalid. If preparing this request fails, then all + * requests will be bad. */ + clock_values->time_of_renewal_request = renewal_request.playback_time; + + return ODK_PrepareRequest( + message, message_length, core_message_size, ODK_Renewal_Request_Type, + nonce_values, &renewal_request, sizeof(ODK_PreparedRenewalRequest)); +} + +OEMCryptoResult ODK_PrepareCoreProvisioningRequest( + uint8_t* message, size_t message_length, size_t* core_message_length, + const ODK_NonceValues* nonce_values, const uint8_t* device_id, + size_t device_id_length) { + if (core_message_length == NULL || nonce_values == NULL) { + return ODK_ERROR_CORE_MESSAGE; + } + ODK_PreparedProvisioningRequest provisioning_request = { + {0}, + 0, + {0}, + }; + if (device_id_length > sizeof(provisioning_request.device_id)) { + return ODK_ERROR_CORE_MESSAGE; + } + provisioning_request.device_id_length = device_id_length; + if (device_id) { + memcpy(provisioning_request.device_id, device_id, device_id_length); + } + return ODK_PrepareRequest(message, message_length, core_message_length, + ODK_Provisioning_Request_Type, nonce_values, + &provisioning_request, + sizeof(ODK_PreparedProvisioningRequest)); +} + +/* @@ parse response functions */ + +OEMCryptoResult ODK_ParseLicense( + const uint8_t* message, size_t message_length, size_t core_message_length, + bool initial_license_load, bool usage_entry_present, + const uint8_t* request_hash, ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, ODK_NonceValues* nonce_values, + ODK_ParsedLicense* parsed_license) { + if (message == NULL || request_hash == NULL || timer_limits == NULL || + clock_values == NULL || nonce_values == NULL || parsed_license == NULL) { + return ODK_ERROR_CORE_MESSAGE; + } + + ODK_LicenseResponse license_response = {{{0}}, parsed_license, {0}}; + const OEMCryptoResult err = ODK_ParseResponse( + message, message_length, core_message_length, ODK_License_Response_Type, + NULL, &license_response, sizeof(ODK_LicenseResponse)); + + if (err != OEMCrypto_SUCCESS) { + return err; + } + + /* We do not support future API version. Also, this function should not be + * used for legacy licenses. */ + if (license_response.request.core_message.nonce_values.api_major_version > + ODK_MAJOR_VERSION || + license_response.request.core_message.nonce_values.api_major_version < + ODK_FIRST_VERSION) { + return ODK_UNSUPPORTED_API; + } + + /* If the server sent us an older format, record the license's API version. */ + if (nonce_values->api_major_version > + license_response.request.core_message.nonce_values.api_major_version) { + nonce_values->api_major_version = + license_response.request.core_message.nonce_values.api_major_version; + nonce_values->api_minor_version = + license_response.request.core_message.nonce_values.api_minor_version; + } else if (nonce_values->api_minor_version > + license_response.request.core_message.nonce_values + .api_minor_version) { + nonce_values->api_minor_version = + license_response.request.core_message.nonce_values.api_minor_version; + } + /* If the license has a provider session token (pst), then OEMCrypto should + * have a usage entry loaded. The opposite is also an error. */ + if ((usage_entry_present && parsed_license->pst.length == 0) || + (!usage_entry_present && parsed_license->pst.length > 0)) { + return ODK_ERROR_CORE_MESSAGE; + } + + if (parsed_license->nonce_required) { + if (initial_license_load) { + if (nonce_values->nonce != + license_response.request.core_message.nonce_values.nonce || + nonce_values->session_id != + license_response.request.core_message.nonce_values.session_id) { + return OEMCrypto_ERROR_INVALID_NONCE; + } + } else { /* !initial_license_load */ + nonce_values->nonce = + license_response.request.core_message.nonce_values.nonce; + nonce_values->session_id = + license_response.request.core_message.nonce_values.session_id; + } + } + /* For v16, in order to be backwards compatible with a v15 license server, + * OEMCrypto stores a hash of the core license request and only signs the + * message body. Here, when we process the license response, we verify that + * the server has the same hash of the core request. */ + if (initial_license_load && parsed_license->nonce_required && + crypto_memcmp(request_hash, license_response.request_hash, + ODK_SHA256_HASH_SIZE)) { + return ODK_ERROR_CORE_MESSAGE; + } + *timer_limits = parsed_license->timer_limits; + /* And update the clock values state. */ + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_LICENSE_LOADED; + return OEMCrypto_SUCCESS; +} + +OEMCryptoResult ODK_ParseRenewal(const uint8_t* message, size_t message_length, + size_t core_message_length, + const ODK_NonceValues* nonce_values, + uint64_t system_time, + const ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, + uint64_t* timer_value) { + if (message == NULL || nonce_values == NULL || timer_limits == NULL || + clock_values == NULL) { + return ODK_ERROR_CORE_MESSAGE; + } + + ODK_RenewalResponse renewal_response = { + {{0}, 0}, + 0, + }; + const OEMCryptoResult err = ODK_ParseResponse( + message, message_length, core_message_length, ODK_Renewal_Response_Type, + nonce_values, &renewal_response, sizeof(ODK_RenewalResponse)); + + if (err != OEMCrypto_SUCCESS) { + return err; + } + + /* Reference: + * Doc: License Duration and Renewal (Changes for OEMCrypto v16) + * Section: Renewal Message + */ + /* If a renewal request is lost in transit, we should throw it out and create + * a new one. We use the timestamp to make sure we have the latest request. + */ + if (clock_values->time_of_renewal_request < + renewal_response.request.playback_time) { + return ODK_STALE_RENEWAL; + } + return ODK_ComputeRenewalDuration(timer_limits, clock_values, system_time, + renewal_response.renewal_duration_seconds, + timer_value); +} + +OEMCryptoResult ODK_ParseProvisioning( + const uint8_t* message, size_t message_length, size_t core_message_length, + const ODK_NonceValues* nonce_values, const uint8_t* device_id, + size_t device_id_length, ODK_ParsedProvisioning* parsed_response) { + if (message == NULL || nonce_values == NULL || device_id == NULL || + parsed_response == NULL) { + return ODK_ERROR_CORE_MESSAGE; + } + + ODK_ProvisioningResponse provisioning_response = {{{0}, 0, {0}}, + parsed_response}; + if (device_id_length > ODK_DEVICE_ID_LEN_MAX) { + return ODK_ERROR_CORE_MESSAGE; + } + + const OEMCryptoResult err = ODK_ParseResponse( + message, message_length, core_message_length, + ODK_Provisioning_Response_Type, nonce_values, &provisioning_response, + sizeof(ODK_ProvisioningResponse)); + + if (err != OEMCrypto_SUCCESS) { + return err; + } + + if (crypto_memcmp(device_id, provisioning_response.request.device_id, + device_id_length) != 0) { + return ODK_ERROR_CORE_MESSAGE; + } + + const uint8_t zero[ODK_DEVICE_ID_LEN_MAX] = {0}; + /* check bytes beyond device_id_length are 0 */ + if (crypto_memcmp(zero, + provisioning_response.request.device_id + device_id_length, + ODK_DEVICE_ID_LEN_MAX - device_id_length) != 0) { + return ODK_ERROR_CORE_MESSAGE; + } + + return OEMCrypto_SUCCESS; +} diff --git a/oemcrypto/odk/src/odk_assert.h b/oemcrypto/odk/src/odk_assert.h new file mode 100644 index 0000000..6fda98b --- /dev/null +++ b/oemcrypto/odk/src/odk_assert.h @@ -0,0 +1,24 @@ +/* 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 WIDEVINE_ODK_SRC_ODK_ASSERT_H_ +#define WIDEVINE_ODK_SRC_ODK_ASSERT_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#if (__STDC_VERSION__ >= 201112L) +#include +#define odk_static_assert static_assert +#else +#define odk_static_assert(msg, e) \ + enum { odk_static_assert = 1 / (!!((msg) && (e))) }; +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* WIDEVINE_ODK_SRC_ODK_ASSERT_H_ */ diff --git a/oemcrypto/odk/src/odk_endian.h b/oemcrypto/odk/src/odk_endian.h new file mode 100644 index 0000000..2a6f143 --- /dev/null +++ b/oemcrypto/odk/src/odk_endian.h @@ -0,0 +1,29 @@ +/* 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 WIDEVINE_ODK_SRC_ODK_ENDIAN_H_ +#define WIDEVINE_ODK_SRC_ODK_ENDIAN_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(__linux__) || defined(__ANDROID__) +#include +#define oemcrypto_htobe32 htobe32 +#define oemcrypto_be32toh be32toh +#define oemcrypto_htobe64 htobe64 +#define oemcrypto_be64toh be64toh +#else /* defined(__linux__) || defined(__ANDROID__) */ +uint32_t oemcrypto_htobe32(uint32_t u32); +uint32_t oemcrypto_be32toh(uint32_t u32); +uint64_t oemcrypto_htobe64(uint64_t u64); +uint64_t oemcrypto_be64toh(uint64_t u64); +#endif /* defined(__linux__) || defined(__ANDROID__) */ + +#ifdef __cplusplus +} +#endif + +#endif /* WIDEVINE_ODK_SRC_ODK_ENDIAN_H_ */ diff --git a/oemcrypto/odk/src/odk_overflow.c b/oemcrypto/odk/src/odk_overflow.c new file mode 100644 index 0000000..76c685f --- /dev/null +++ b/oemcrypto/odk/src/odk_overflow.c @@ -0,0 +1,36 @@ +/* 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. */ + +#include +#include + +int odk_sub_overflow_u64(uint64_t a, uint64_t b, uint64_t* c) { + if (a >= b) { + if (c) { + *c = a - b; + } + return 0; + } + return 1; +} + +int odk_add_overflow_u64(uint64_t a, uint64_t b, uint64_t* c) { + if (UINT64_MAX - a >= b) { + if (c) { + *c = a + b; + } + return 0; + } + return 1; +} + +int odk_add_overflow_ux(size_t a, size_t b, size_t* c) { + if (SIZE_MAX - a >= b) { + if (c) { + *c = a + b; + } + return 0; + } + return 1; +} diff --git a/oemcrypto/odk/src/odk_overflow.h b/oemcrypto/odk/src/odk_overflow.h new file mode 100644 index 0000000..b1e03ee --- /dev/null +++ b/oemcrypto/odk/src/odk_overflow.h @@ -0,0 +1,23 @@ +/* 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 WIDEVINE_ODK_SRC_ODK_OVERFLOW_H_ +#define WIDEVINE_ODK_SRC_ODK_OVERFLOW_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +int odk_sub_overflow_u64(uint64_t a, uint64_t b, uint64_t* c); +int odk_add_overflow_u64(uint64_t a, uint64_t b, uint64_t* c); +int odk_add_overflow_ux(size_t a, size_t b, size_t* c); + +#ifdef __cplusplus +} +#endif + +#endif /* WIDEVINE_ODK_SRC_ODK_OVERFLOW_H_ */ diff --git a/oemcrypto/odk/src/odk_serialize.c b/oemcrypto/odk/src/odk_serialize.c new file mode 100644 index 0000000..efd9475 --- /dev/null +++ b/oemcrypto/odk/src/odk_serialize.c @@ -0,0 +1,213 @@ +/* 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. */ + +/* + * This code is auto-generated, do not edit + */ + +#include "odk_structs_priv.h" +#include "serialization_base.h" + +/* @ serialize */ + +/* @@ private serialize */ + +static void Pack_ODK_NonceValues(Message* msg, ODK_NonceValues const* obj) { + Pack_uint16_t(msg, &obj->api_minor_version); + Pack_uint16_t(msg, &obj->api_major_version); + Pack_uint32_t(msg, &obj->nonce); + Pack_uint32_t(msg, &obj->session_id); +} + +static void Pack_ODK_CoreMessage(Message* msg, ODK_CoreMessage const* obj) { + Pack_uint32_t(msg, &obj->message_type); + Pack_uint32_t(msg, &obj->message_length); + Pack_ODK_NonceValues(msg, &obj->nonce_values); +} + +static void Pack_OEMCrypto_KeyObject(Message* msg, + OEMCrypto_KeyObject const* obj) { + Pack_OEMCrypto_Substring(msg, &obj->key_id); + Pack_OEMCrypto_Substring(msg, &obj->key_data_iv); + Pack_OEMCrypto_Substring(msg, &obj->key_data); + Pack_OEMCrypto_Substring(msg, &obj->key_control_iv); + Pack_OEMCrypto_Substring(msg, &obj->key_control); +} + +static void Pack_ODK_TimerLimits(Message* msg, ODK_TimerLimits const* obj) { + Pack_bool(msg, &obj->soft_enforce_rental_duration); + Pack_bool(msg, &obj->soft_enforce_playback_duration); + Pack_uint64_t(msg, &obj->earliest_playback_start_seconds); + Pack_uint64_t(msg, &obj->rental_duration_seconds); + Pack_uint64_t(msg, &obj->total_playback_duration_seconds); + Pack_uint64_t(msg, &obj->initial_renewal_duration_seconds); +} + +static void Pack_ODK_ParsedLicense(Message* msg, ODK_ParsedLicense const* obj) { + /* hand-coded */ + if (obj->key_array_length > ODK_MAX_NUM_KEYS) { + SetStatus(msg, MESSAGE_STATUS_OVERFLOW_ERROR); + return; + } + Pack_OEMCrypto_Substring(msg, &obj->enc_mac_keys_iv); + Pack_OEMCrypto_Substring(msg, &obj->enc_mac_keys); + Pack_OEMCrypto_Substring(msg, &obj->pst); + Pack_OEMCrypto_Substring(msg, &obj->srm_restriction_data); + Pack_enum(msg, obj->license_type); + Pack_bool(msg, &obj->nonce_required); + Pack_ODK_TimerLimits(msg, &obj->timer_limits); + Pack_uint32_t(msg, &obj->key_array_length); + size_t i; + for (i = 0; i < (size_t)obj->key_array_length; i++) { + Pack_OEMCrypto_KeyObject(msg, &obj->key_array[i]); + } +} + +static void Pack_ODK_ParsedProvisioning(Message* msg, + ODK_ParsedProvisioning const* obj) { + Pack_enum(msg, obj->key_type); + Pack_OEMCrypto_Substring(msg, &obj->enc_private_key); + Pack_OEMCrypto_Substring(msg, &obj->enc_private_key_iv); + Pack_OEMCrypto_Substring(msg, &obj->encrypted_message_key); +} + +/* @@ odk serialize */ + +void Pack_ODK_PreparedLicenseRequest(Message* msg, + ODK_PreparedLicenseRequest const* obj) { + Pack_ODK_CoreMessage(msg, &obj->core_message); +} + +void Pack_ODK_PreparedRenewalRequest(Message* msg, + ODK_PreparedRenewalRequest const* obj) { + Pack_ODK_CoreMessage(msg, &obj->core_message); + Pack_uint64_t(msg, &obj->playback_time); +} + +void Pack_ODK_PreparedProvisioningRequest( + Message* msg, ODK_PreparedProvisioningRequest const* obj) { + Pack_ODK_CoreMessage(msg, &obj->core_message); + Pack_uint32_t(msg, &obj->device_id_length); + PackArray(msg, &obj->device_id[0], sizeof(obj->device_id)); +} + +/* @@ kdo serialize */ + +void Pack_ODK_LicenseResponse(Message* msg, ODK_LicenseResponse const* obj) { + Pack_ODK_PreparedLicenseRequest(msg, &obj->request); + Pack_ODK_ParsedLicense(msg, (const ODK_ParsedLicense*)obj->parsed_license); + PackArray(msg, &obj->request_hash[0], sizeof(obj->request_hash)); +} + +void Pack_ODK_RenewalResponse(Message* msg, ODK_RenewalResponse const* obj) { + Pack_ODK_PreparedRenewalRequest(msg, &obj->request); + Pack_uint64_t(msg, &obj->renewal_duration_seconds); +} + +void Pack_ODK_ProvisioningResponse(Message* msg, + ODK_ProvisioningResponse const* obj) { + Pack_ODK_PreparedProvisioningRequest(msg, &obj->request); + Pack_ODK_ParsedProvisioning( + msg, (const ODK_ParsedProvisioning*)obj->parsed_provisioning); +} + +/* @ deserialize */ + +/* @@ private deserialize */ + +static void Unpack_ODK_NonceValues(Message* msg, ODK_NonceValues* obj) { + Unpack_uint16_t(msg, &obj->api_minor_version); + Unpack_uint16_t(msg, &obj->api_major_version); + Unpack_uint32_t(msg, &obj->nonce); + Unpack_uint32_t(msg, &obj->session_id); +} + +static void Unpack_ODK_CoreMessage(Message* msg, ODK_CoreMessage* obj) { + Unpack_uint32_t(msg, &obj->message_type); + Unpack_uint32_t(msg, &obj->message_length); + Unpack_ODK_NonceValues(msg, &obj->nonce_values); +} + +static void Unpack_OEMCrypto_KeyObject(Message* msg, OEMCrypto_KeyObject* obj) { + Unpack_OEMCrypto_Substring(msg, &obj->key_id); + Unpack_OEMCrypto_Substring(msg, &obj->key_data_iv); + Unpack_OEMCrypto_Substring(msg, &obj->key_data); + Unpack_OEMCrypto_Substring(msg, &obj->key_control_iv); + Unpack_OEMCrypto_Substring(msg, &obj->key_control); +} + +static void Unpack_ODK_TimerLimits(Message* msg, ODK_TimerLimits* obj) { + Unpack_bool(msg, &obj->soft_enforce_rental_duration); + Unpack_bool(msg, &obj->soft_enforce_playback_duration); + Unpack_uint64_t(msg, &obj->earliest_playback_start_seconds); + Unpack_uint64_t(msg, &obj->rental_duration_seconds); + Unpack_uint64_t(msg, &obj->total_playback_duration_seconds); + Unpack_uint64_t(msg, &obj->initial_renewal_duration_seconds); +} + +static void Unpack_ODK_ParsedLicense(Message* msg, ODK_ParsedLicense* obj) { + Unpack_OEMCrypto_Substring(msg, &obj->enc_mac_keys_iv); + Unpack_OEMCrypto_Substring(msg, &obj->enc_mac_keys); + Unpack_OEMCrypto_Substring(msg, &obj->pst); + Unpack_OEMCrypto_Substring(msg, &obj->srm_restriction_data); + obj->license_type = (OEMCrypto_LicenseType)Unpack_enum(msg); + Unpack_bool(msg, &obj->nonce_required); + Unpack_ODK_TimerLimits(msg, &obj->timer_limits); + Unpack_uint32_t(msg, &obj->key_array_length); + if (obj->key_array_length > ODK_MAX_NUM_KEYS) { + SetStatus(msg, MESSAGE_STATUS_OVERFLOW_ERROR); + return; + } + uint32_t i; + for (i = 0; i < obj->key_array_length; i++) { + Unpack_OEMCrypto_KeyObject(msg, &obj->key_array[i]); + } +} + +static void Unpack_ODK_ParsedProvisioning(Message* msg, + ODK_ParsedProvisioning* obj) { + obj->key_type = (OEMCrypto_PrivateKeyType)Unpack_enum(msg); + Unpack_OEMCrypto_Substring(msg, &obj->enc_private_key); + Unpack_OEMCrypto_Substring(msg, &obj->enc_private_key_iv); + Unpack_OEMCrypto_Substring(msg, &obj->encrypted_message_key); +} + +/* @ kdo deserialize */ + +void Unpack_ODK_PreparedLicenseRequest(Message* msg, + ODK_PreparedLicenseRequest* obj) { + Unpack_ODK_CoreMessage(msg, &obj->core_message); +} + +void Unpack_ODK_PreparedRenewalRequest(Message* msg, + ODK_PreparedRenewalRequest* obj) { + Unpack_ODK_CoreMessage(msg, &obj->core_message); + Unpack_uint64_t(msg, &obj->playback_time); +} + +void Unpack_ODK_PreparedProvisioningRequest( + Message* msg, ODK_PreparedProvisioningRequest* obj) { + Unpack_ODK_CoreMessage(msg, &obj->core_message); + Unpack_uint32_t(msg, &obj->device_id_length); + UnpackArray(msg, &obj->device_id[0], sizeof(obj->device_id)); +} + +/* @@ odk deserialize */ + +void Unpack_ODK_LicenseResponse(Message* msg, ODK_LicenseResponse* obj) { + Unpack_ODK_PreparedLicenseRequest(msg, &obj->request); + Unpack_ODK_ParsedLicense(msg, obj->parsed_license); + UnpackArray(msg, &obj->request_hash[0], sizeof(obj->request_hash)); +} + +void Unpack_ODK_RenewalResponse(Message* msg, ODK_RenewalResponse* obj) { + Unpack_ODK_PreparedRenewalRequest(msg, &obj->request); + Unpack_uint64_t(msg, &obj->renewal_duration_seconds); +} + +void Unpack_ODK_ProvisioningResponse(Message* msg, + ODK_ProvisioningResponse* obj) { + Unpack_ODK_PreparedProvisioningRequest(msg, &obj->request); + Unpack_ODK_ParsedProvisioning(msg, obj->parsed_provisioning); +} diff --git a/oemcrypto/odk/src/odk_serialize.h b/oemcrypto/odk/src/odk_serialize.h new file mode 100644 index 0000000..f35f178 --- /dev/null +++ b/oemcrypto/odk/src/odk_serialize.h @@ -0,0 +1,49 @@ +/* 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. */ + +/* + * This code is auto-generated, do not edit + */ +#ifndef WIDEVINE_ODK_SRC_ODK_SERIALIZE_H_ +#define WIDEVINE_ODK_SRC_ODK_SERIALIZE_H_ + +#include "odk_structs_priv.h" +#include "serialization_base.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* odk pack */ +void Pack_ODK_PreparedLicenseRequest(Message* msg, + const ODK_PreparedLicenseRequest* obj); +void Pack_ODK_PreparedRenewalRequest(Message* msg, + const ODK_PreparedRenewalRequest* obj); +void Pack_ODK_PreparedProvisioningRequest( + Message* msg, const ODK_PreparedProvisioningRequest* obj); + +/* odk unpack */ +void Unpack_ODK_LicenseResponse(Message* msg, ODK_LicenseResponse* obj); +void Unpack_ODK_RenewalResponse(Message* msg, ODK_RenewalResponse* obj); +void Unpack_ODK_ProvisioningResponse(Message* msg, + ODK_ProvisioningResponse* obj); + +/* kdo pack */ +void Pack_ODK_LicenseResponse(Message* msg, const ODK_LicenseResponse* obj); +void Pack_ODK_RenewalResponse(Message* msg, const ODK_RenewalResponse* obj); +void Pack_ODK_ProvisioningResponse(Message* msg, + const ODK_ProvisioningResponse* obj); + +/* kdo unpack */ +void Unpack_ODK_PreparedLicenseRequest(Message* msg, + ODK_PreparedLicenseRequest* obj); +void Unpack_ODK_PreparedRenewalRequest(Message* msg, + ODK_PreparedRenewalRequest* obj); +void Unpack_ODK_PreparedProvisioningRequest( + Message* msg, ODK_PreparedProvisioningRequest* obj); + +#ifdef __cplusplus +} /* extern "C" */ +#endif +#endif /* WIDEVINE_ODK_SRC_ODK_SERIALIZE_H_ */ diff --git a/oemcrypto/odk/src/odk_structs_priv.h b/oemcrypto/odk/src/odk_structs_priv.h new file mode 100644 index 0000000..3c5a502 --- /dev/null +++ b/oemcrypto/odk/src/odk_structs_priv.h @@ -0,0 +1,104 @@ +/* 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 WIDEVINE_ODK_SRC_ODK_STRUCTS_PRIV_H_ +#define WIDEVINE_ODK_SRC_ODK_STRUCTS_PRIV_H_ + +#include + +#include "OEMCryptoCENCCommon.h" +#include "odk_structs.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + ODK_License_Request_Type = 1, + ODK_License_Response_Type = 2, + ODK_Renewal_Request_Type = 3, + ODK_Renewal_Response_Type = 4, + ODK_Provisioning_Request_Type = 5, + ODK_Provisioning_Response_Type = 6, + + /* Reserve future message types to support forward compatibility. */ + ODK_Release_Request_Type = 7, + ODK_Release_Response_Type = 8, +} ODK_MessageType; + +typedef struct { + uint32_t message_type; + uint32_t message_length; + ODK_NonceValues nonce_values; +} ODK_CoreMessage; + +typedef struct { + ODK_CoreMessage core_message; +} ODK_PreparedLicenseRequest; + +typedef struct { + ODK_CoreMessage core_message; + uint64_t playback_time; +} ODK_PreparedRenewalRequest; + +typedef struct { + ODK_CoreMessage core_message; + uint32_t device_id_length; + uint8_t device_id[ODK_DEVICE_ID_LEN_MAX]; +} ODK_PreparedProvisioningRequest; + +typedef struct { + ODK_PreparedLicenseRequest request; + ODK_ParsedLicense* parsed_license; + uint8_t request_hash[ODK_SHA256_HASH_SIZE]; +} ODK_LicenseResponse; + +typedef struct { + ODK_PreparedRenewalRequest request; + uint64_t renewal_duration_seconds; +} ODK_RenewalResponse; + +typedef struct { + ODK_PreparedProvisioningRequest request; + ODK_ParsedProvisioning* parsed_provisioning; +} ODK_ProvisioningResponse; + +/* These are the sum of sizeof of each individual member of the request structs + */ +/* without any padding added by the compiler. Make sure they get updated when */ +/* request structs change. Refer to test suite OdkSizeTest in */ +/* ../test/odk_test.cpp for validations of each of the defined request sizes. */ +#define ODK_LICENSE_REQUEST_SIZE 20 +#define ODK_RENEWAL_REQUEST_SIZE 28 +#define ODK_PROVISIONING_REQUEST_SIZE 88 + +/* These are the possible timer status values. */ +#define ODK_CLOCK_TIMER_STATUS_UNDEFINED 0 /* Should not happen. */ +/* When the structure has been initialized, but no license is loaded. */ +#define ODK_CLOCK_TIMER_STATUS_LICENSE_NOT_LOADED 1 +/* After the license is loaded, before a successful decrypt. */ +#define ODK_CLOCK_TIMER_STATUS_LICENSE_LOADED 2 +/* After the license is loaded, if a renewal has also been loaded. */ +#define ODK_CLOCK_TIMER_STATUS_RENEWAL_LOADED 3 +/* The first decrypt has occurred and the timer is active. */ +#define ODK_CLOCK_TIMER_STATUS_ACTIVE 4 +/* The first decrypt has occurred and the timer is unlimited. */ +#define ODK_CLOCK_TIMER_STATUS_UNLIMITED 5 +/* The timer has transitioned from active to expired. */ +#define ODK_CLOCK_TIMER_STATUS_EXPIRED 6 +/* The license has been marked as inactive. */ +#define ODK_CLOCK_TIMER_STATUS_LICENSE_INACTIVE 7 + +/* A helper function for computing timer limits when a renewal is loaded. */ +OEMCryptoResult ODK_ComputeRenewalDuration(const ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, + uint64_t system_time_seconds, + uint64_t new_renewal_duration, + uint64_t* timer_value); + +#ifdef __cplusplus +} +#endif + +#endif /* WIDEVINE_ODK_SRC_ODK_STRUCTS_PRIV_H_ */ diff --git a/oemcrypto/odk/src/odk_timer.c b/oemcrypto/odk/src/odk_timer.c new file mode 100644 index 0000000..0fbcf18 --- /dev/null +++ b/oemcrypto/odk/src/odk_timer.c @@ -0,0 +1,502 @@ +/* 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. */ + +#include +#include + +#include "odk.h" +#include "odk_overflow.h" +#include "odk_structs_priv.h" + +/* Private function. Checks to see if the license is active. Returns + * ODK_TIMER_EXPIRED if the license is valid but inactive. Returns + * OEMCrypto_SUCCESS if the license is active. Returns + * OEMCrypto_ERROR_UNKNOWN_FAILURE on other errors. */ +static OEMCryptoResult ODK_LicenseActive(const ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values) { + /* Check some basic errors. */ + if (clock_values == NULL || timer_limits == NULL) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + /* Check if the license has not been loaded yet. */ + if (clock_values->timer_status == ODK_CLOCK_TIMER_STATUS_UNDEFINED || + clock_values->timer_status == ODK_CLOCK_TIMER_STATUS_LICENSE_NOT_LOADED) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (clock_values->status > kActive) { + return ODK_TIMER_EXPIRED; + } + return OEMCrypto_SUCCESS; +} + +/* Private function. Sets the timer_value to be the min(timer_value, new_value), + * with the convention that 0 means infinite. The convention that 0 means + * infinite is used for all Widevine license and duration values. */ +static void ComputeMinimum(uint64_t* timer_value, uint64_t new_value) { + if (timer_value == NULL) return; + if (new_value > 0) { + if (*timer_value == 0 || *timer_value > new_value) { + *timer_value = new_value; + } + } +} + +/* Private function. Check to see if the rental window restricts playback. If + * the rental enforcement is hard, or if this is the first playback, then we + * verify that system_time_seconds is within the rental window. If the + * enforcement is soft and we have already started playback, then there is no + * restriction. + * Return ODK_TIMER_EXPIRED if out of the window. + * Return ODK_TIMER_ACTIVE if within the window, and there is a hard limit. + * Return ODK_DISABLE_TIMER if no there should be no limit. + * Return other error on error. + * Also, if this function does compute a limit, the timer_value is reduced to + * obey that limit. If the limit is less restrictive than the current + * timer_value, then timer_value is not changed. */ +static OEMCryptoResult ODK_CheckRentalWindow( + const ODK_TimerLimits* timer_limits, ODK_ClockValues* clock_values, + uint64_t system_time_seconds, uint64_t* timer_value) { + if (clock_values == NULL || timer_limits == NULL || timer_value == NULL) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + /* If playback has already started, and rental duration enforcement is soft, + * then there is no restriction. */ + if (clock_values->time_of_first_decrypt > 0 && + timer_limits->soft_enforce_rental_duration) { + return ODK_DISABLE_TIMER; + } + + /* rental_clock = time since license signed. */ + uint64_t rental_clock = 0; + if (odk_sub_overflow_u64(system_time_seconds, + clock_values->time_of_license_signed, + &rental_clock)) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + /* Check if it is before license is valid. This is an unusual case. First + * playback may still work if it occurs after the rental window opens. */ + if (rental_clock < timer_limits->earliest_playback_start_seconds) { + return ODK_TIMER_EXPIRED; + } + /* If the rental duration is 0, there is no limit. */ + if (timer_limits->rental_duration_seconds == 0) { + return ODK_DISABLE_TIMER; + } + /* End of rental window, based on rental clock (not system time). */ + uint64_t end_of_rental_window = 0; + if (odk_add_overflow_u64(timer_limits->earliest_playback_start_seconds, + timer_limits->rental_duration_seconds, + &end_of_rental_window)) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if (end_of_rental_window <= rental_clock) { + return ODK_TIMER_EXPIRED; + } + /* At this point system_time is within the rental window. */ + if (timer_limits->soft_enforce_rental_duration) { + /* For soft enforcement, we allow playback, and do not adjust the timer. */ + return ODK_DISABLE_TIMER; + } + uint64_t time_left = 0; + if (odk_sub_overflow_u64(end_of_rental_window, rental_clock, &time_left)) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + ComputeMinimum(timer_value, time_left); + return ODK_SET_TIMER; +} + +/* Private function. Check to see if the playback window restricts + * playback. This should only be called if playback has started, so that + * clock_values->time_of_first_decrypt is nonzero. + * Return ODK_TIMER_EXPIRED if out of the window. + * Return ODK_SET_TIMER if within the window, and there is a hard limit. + * Return ODK_DISABLE_TIMER if no limit. + * Return other error on error. + * Also, if this function does compute a limit, the timer_value is reduced to + * obey that limit. If the limit is less restrictive than the current + * timer_value, then timer_value is not changed. */ +static OEMCryptoResult ODK_CheckPlaybackWindow( + const ODK_TimerLimits* timer_limits, ODK_ClockValues* clock_values, + uint64_t system_time_seconds, uint64_t* timer_value) { + if (clock_values == NULL || timer_limits == NULL || timer_value == NULL) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + /* if the playback duration is 0, there is no limit. */ + if (timer_limits->total_playback_duration_seconds == 0) { + return ODK_DISABLE_TIMER; + } + uint64_t end_of_playback_window = 0; + if (odk_add_overflow_u64(timer_limits->total_playback_duration_seconds, + clock_values->time_of_first_decrypt, + &end_of_playback_window)) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if (end_of_playback_window <= system_time_seconds) { + return ODK_TIMER_EXPIRED; + } + /* At this point, system_time is within the total playback window. */ + if (timer_limits->soft_enforce_playback_duration) { + /* For soft enforcement, we allow playback, and do not adjust the timer. */ + return ODK_DISABLE_TIMER; + } + uint64_t time_left = 0; + if (odk_sub_overflow_u64(end_of_playback_window, system_time_seconds, + &time_left)) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + ComputeMinimum(timer_value, time_left); + return ODK_SET_TIMER; +} + +/* Update the timer status. If playback has already started, we use the given + * status. However, if playback has not yet started, then we expect a call to + * ODK_AttemptFirstPlayback in the future, and we need to signal to it that we + * have already computed the timer limit. */ +static void ODK_UpdateTimerStatusForRenewal(ODK_ClockValues* clock_values, + uint32_t new_status) { + if (clock_values == NULL) { + return; /* should not happen. */ + } + if (clock_values->timer_status == ODK_CLOCK_TIMER_STATUS_LICENSE_LOADED) { + /* Signal that the timer is already set. */ + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_RENEWAL_LOADED; + } else { + clock_values->timer_status = new_status; + } +} + +/* Private function, but accessed from odk.c so cannot be static. This checks to + * see if a renewal message should restart the playback timer and sets the value + * appropriately. */ +OEMCryptoResult ODK_ComputeRenewalDuration(const ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, + uint64_t system_time_seconds, + uint64_t new_renewal_duration, + uint64_t* timer_value) { + if (timer_limits == NULL || clock_values == NULL) { + return OEMCrypto_ERROR_INVALID_CONTEXT; /* should not happen. */ + } + /* If this is before the license was signed, something is odd. Return an + * error. */ + if (system_time_seconds < clock_values->time_of_license_signed) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + + const OEMCryptoResult license_status = + ODK_LicenseActive(timer_limits, clock_values); + /* If the license is not active, then we cannot renew the license. */ + if (license_status != OEMCrypto_SUCCESS) { + return license_status; + } + + /* We start with the new renewal duration as the new timer limit. */ + uint64_t new_timer_value = new_renewal_duration; + + /* Then we factor in the rental window restrictions. This might decrease + * new_timer_value. */ + const OEMCryptoResult rental_status = ODK_CheckRentalWindow( + timer_limits, clock_values, system_time_seconds, &new_timer_value); + + /* If the rental status forbids playback, then we're done. */ + if ((rental_status != ODK_DISABLE_TIMER) && + (rental_status != ODK_SET_TIMER)) { + return rental_status; + } + + /* If playback has already started and it has hard enforcement, then check + * total playback window. */ + if (clock_values->time_of_first_decrypt > 0 && + !timer_limits->soft_enforce_playback_duration) { + /* This might decrease new_timer_value. */ + const OEMCryptoResult playback_status = ODK_CheckPlaybackWindow( + timer_limits, clock_values, system_time_seconds, &new_timer_value); + /* If the timer limits forbid playback in the playback window, then we're + * done. */ + if ((playback_status != ODK_DISABLE_TIMER) && + (playback_status != ODK_SET_TIMER)) { + return playback_status; + } + } + + /* If new_timer_value is infinite (represented by 0), then there are no + * limits, so we can return now. */ + if (new_timer_value == 0) { + clock_values->time_when_timer_expires = 0; + ODK_UpdateTimerStatusForRenewal(clock_values, + ODK_CLOCK_TIMER_STATUS_UNLIMITED); + return ODK_DISABLE_TIMER; + } + + /* If the caller gave us a pointer to store the new timer value. Fill it. */ + if (timer_value != NULL) { + *timer_value = new_timer_value; + } + if (odk_add_overflow_u64(system_time_seconds, new_timer_value, + &clock_values->time_when_timer_expires)) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + ODK_UpdateTimerStatusForRenewal(clock_values, ODK_CLOCK_TIMER_STATUS_ACTIVE); + return ODK_SET_TIMER; +} + +/************************************************************************/ +/************************************************************************/ +/* Public functions, declared in odk.h. */ + +/* This is called when OEMCrypto opens a new session. */ +OEMCryptoResult ODK_InitializeSessionValues(ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, + ODK_NonceValues* nonce_values, + uint32_t api_major_version, + uint32_t session_id) { + if (timer_limits == NULL || clock_values == NULL || nonce_values == NULL) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + /* Check that the API version passed in from OEMCrypto matches the version of + * this ODK library. */ + if (api_major_version != ODK_MAJOR_VERSION) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + timer_limits->soft_enforce_rental_duration = false; + timer_limits->soft_enforce_playback_duration = false; + timer_limits->earliest_playback_start_seconds = 0; + timer_limits->rental_duration_seconds = 0; + timer_limits->total_playback_duration_seconds = 0; + timer_limits->initial_renewal_duration_seconds = 0; + + ODK_InitializeClockValues(clock_values, 0); + + nonce_values->api_major_version = ODK_MAJOR_VERSION; + nonce_values->api_minor_version = ODK_MINOR_VERSION; + nonce_values->nonce = 0; + nonce_values->session_id = session_id; + + return OEMCrypto_SUCCESS; +} + +/* This is called when OEMCrypto generates a new nonce in + * OEMCrypto_GenerateNonce. */ +OEMCryptoResult ODK_SetNonceValues(ODK_NonceValues* nonce_values, + uint32_t nonce) { + if (nonce_values == NULL) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + /* Setting the nonce should only happen once per session. */ + if (nonce_values->nonce != 0) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + nonce_values->nonce = nonce; + return OEMCrypto_SUCCESS; +} + +/* This is called when OEMCrypto signs a license. */ +OEMCryptoResult ODK_InitializeClockValues(ODK_ClockValues* clock_values, + uint64_t system_time_seconds) { + if (clock_values == NULL) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + clock_values->time_of_license_signed = system_time_seconds; + clock_values->time_of_first_decrypt = 0; + clock_values->time_of_last_decrypt = 0; + clock_values->time_when_timer_expires = 0; + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_LICENSE_NOT_LOADED; + clock_values->status = kUnused; + return OEMCrypto_SUCCESS; +} + +/* This is called when OEMCrypto reloads a usage entry. */ +OEMCryptoResult ODK_ReloadClockValues(ODK_ClockValues* clock_values, + uint64_t time_of_license_signed, + uint64_t time_of_first_decrypt, + uint64_t time_of_last_decrypt, + enum OEMCrypto_Usage_Entry_Status status, + uint64_t system_time_seconds) { + if (clock_values == NULL) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + clock_values->time_of_license_signed = time_of_license_signed; + clock_values->time_of_first_decrypt = time_of_first_decrypt; + clock_values->time_of_last_decrypt = time_of_last_decrypt; + clock_values->time_when_timer_expires = 0; + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_LICENSE_NOT_LOADED; + clock_values->status = status; + return OEMCrypto_SUCCESS; +} + +/* This is called on the first playback for a session. */ +OEMCryptoResult ODK_AttemptFirstPlayback(uint64_t system_time_seconds, + const ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, + uint64_t* timer_value) { + if (clock_values == NULL || timer_limits == NULL) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + /* All times are relative to when the license was signed. */ + uint64_t rental_time = 0; + if (odk_sub_overflow_u64(system_time_seconds, + clock_values->time_of_license_signed, + &rental_time)) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if (rental_time < timer_limits->earliest_playback_start_seconds) { + clock_values->timer_status = ODK_TIMER_EXPIRED; + return ODK_TIMER_EXPIRED; + } + /* If the license is inactive or not loaded, then playback is not allowed. */ + OEMCryptoResult status = ODK_LicenseActive(timer_limits, clock_values); + if (status != OEMCrypto_SUCCESS) { + return status; + } + + /* We start with the initial renewal duration as the timer limit. */ + uint64_t new_timer_value = timer_limits->initial_renewal_duration_seconds; + /* However, if a renewal was loaded before this first playback, use the + * previously computed limit. */ + if (clock_values->timer_status == ODK_CLOCK_TIMER_STATUS_RENEWAL_LOADED) { + if (clock_values->time_when_timer_expires <= system_time_seconds) { + return ODK_TIMER_EXPIRED; + } + if (odk_sub_overflow_u64(clock_values->time_when_timer_expires, + system_time_seconds, &new_timer_value)) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + } + + /* Then we factor in the rental window restrictions. This might decrease + * new_timer_value. */ + status = ODK_CheckRentalWindow(timer_limits, clock_values, + system_time_seconds, &new_timer_value); + if ((status != ODK_DISABLE_TIMER) && (status != ODK_SET_TIMER)) { + return status; + } + + /* If playback has not already started, then this is the first playback. */ + if (clock_values->time_of_first_decrypt == 0) { + clock_values->time_of_first_decrypt = system_time_seconds; + clock_values->status = kActive; + } + + /* Similar to the rental window, we check the playback window + * restrictions. This might decrease new_timer_value. */ + status = ODK_CheckPlaybackWindow(timer_limits, clock_values, + system_time_seconds, &new_timer_value); + if ((status != ODK_DISABLE_TIMER) && (status != ODK_SET_TIMER)) { + return status; + } + + /* We know we are allowed to decrypt. The rest computes the timer duration. */ + clock_values->time_of_last_decrypt = system_time_seconds; + + /* If new_timer_value is infinite (represented by 0), then there are no + * limits, so we can return now. */ + if (new_timer_value == 0) { + clock_values->time_when_timer_expires = 0; + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_UNLIMITED; + return ODK_DISABLE_TIMER; + } + /* If the caller gave us a pointer to store the new timer value. Fill it. */ + if (timer_value) { + *timer_value = new_timer_value; + } + if (odk_add_overflow_u64(system_time_seconds, new_timer_value, + &clock_values->time_when_timer_expires)) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_ACTIVE; + return ODK_SET_TIMER; +} + +/* This is called regularly during playback if OEMCrypto does not implement its + * own timer. */ +OEMCryptoResult ODK_UpdateLastPlaybackTime(uint64_t system_time_seconds, + const ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values) { + OEMCryptoResult status = ODK_LicenseActive(timer_limits, clock_values); + if (status != OEMCrypto_SUCCESS) { + return status; + } + switch (clock_values->timer_status) { + case ODK_CLOCK_TIMER_STATUS_UNLIMITED: + break; + case ODK_CLOCK_TIMER_STATUS_ACTIVE: + /* Note: we allow playback at the time when the timer expires, but not + * after. This is not important for business cases, but it makes it + * easier to write tests. */ + if (clock_values->time_when_timer_expires > 0 && + system_time_seconds > clock_values->time_when_timer_expires) { + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_EXPIRED; + return ODK_TIMER_EXPIRED; + } + break; + default: /* Expired, error state, or never started. */ + return ODK_TIMER_EXPIRED; + } + clock_values->time_of_last_decrypt = system_time_seconds; + return OEMCrypto_SUCCESS; +} + +/* This is called from OEMCrypto_DeactivateUsageEntry. */ +OEMCryptoResult ODK_DeactivateUsageEntry(ODK_ClockValues* clock_values) { + if (clock_values == NULL) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (clock_values->status == kUnused) { + clock_values->status = kInactiveUnused; + } else if (clock_values->status == kActive) { + clock_values->status = kInactiveUsed; + } + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_LICENSE_INACTIVE; + return OEMCrypto_SUCCESS; +} + +/* This is called when OEMCrypto loads a legacy v15 license, from + * OEMCrypto_LoadKeys. */ +OEMCryptoResult ODK_InitializeV15Values(ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, + ODK_NonceValues* nonce_values, + uint32_t key_duration, + uint64_t system_time_seconds) { + if (timer_limits == NULL || clock_values == NULL || nonce_values == NULL) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + timer_limits->soft_enforce_playback_duration = false; + timer_limits->soft_enforce_rental_duration = false; + timer_limits->earliest_playback_start_seconds = 0; + timer_limits->rental_duration_seconds = 0; + timer_limits->total_playback_duration_seconds = 0; + timer_limits->initial_renewal_duration_seconds = key_duration; + + nonce_values->api_major_version = 15; + nonce_values->api_minor_version = 0; + if (key_duration > 0) { + clock_values->time_when_timer_expires = system_time_seconds + key_duration; + } else { + clock_values->time_when_timer_expires = 0; + } + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_LICENSE_LOADED; + return OEMCrypto_SUCCESS; +} + +/* This is called when OEMCrypto loads a legacy license renewal in + * OEMCrypto_RefreshKeys. */ +OEMCryptoResult ODK_RefreshV15Values(const ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, + const ODK_NonceValues* nonce_values, + uint64_t system_time_seconds, + uint32_t new_key_duration, + uint64_t* timer_value) { + if (timer_limits == NULL || clock_values == NULL || nonce_values == NULL) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if (nonce_values->api_major_version != 15) { + return OEMCrypto_ERROR_INVALID_NONCE; + } + if (clock_values->status > kActive) { + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_LICENSE_INACTIVE; + return ODK_TIMER_EXPIRED; + } + return ODK_ComputeRenewalDuration(timer_limits, clock_values, + system_time_seconds, new_key_duration, + timer_value); +} diff --git a/oemcrypto/odk/src/odk_util.c b/oemcrypto/odk/src/odk_util.c new file mode 100644 index 0000000..ff85a9c --- /dev/null +++ b/oemcrypto/odk/src/odk_util.c @@ -0,0 +1,34 @@ +/* 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. */ + +#include "odk_util.h" + +int crypto_memcmp(const void* in_a, const void* in_b, size_t len) { + if (len == 0) { + return 0; + } + + /* Only valid pointers are allowed. */ + if (in_a == NULL || in_b == NULL) { + return -1; + } + + const uint8_t* a = (const uint8_t*)in_a; + const uint8_t* b = (const uint8_t*)in_b; + uint8_t x = 0; + + for (size_t i = 0; i < len; i++) { + x |= a[i] ^ b[i]; + } + return x; +} + +bool ODK_NonceValuesEqual(const ODK_NonceValues* a, const ODK_NonceValues* b) { + if (a == NULL || b == NULL) { + return (a == b); + } + return (a->api_major_version == b->api_major_version && + a->api_minor_version == b->api_minor_version && + a->nonce == b->nonce && a->session_id == b->session_id); +} diff --git a/oemcrypto/odk/src/odk_util.h b/oemcrypto/odk/src/odk_util.h new file mode 100644 index 0000000..7de668e --- /dev/null +++ b/oemcrypto/odk/src/odk_util.h @@ -0,0 +1,28 @@ +/* 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 WIDEVINE_ODK_SRC_ODK_UTIL_H_ +#define WIDEVINE_ODK_SRC_ODK_UTIL_H_ + +#include +#include + +#include "odk_structs.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* crypto_memcmp returns zero iff the |len| bytes at |a| and |b| are equal. It + * takes an amount of time dependent on |len|, but independent of the contents + * of |a| and |b|. Unlike memcmp, it cannot be used to order elements as the + * return value when a != b is undefined, other than being non-zero. */ +int crypto_memcmp(const void* a, const void* b, size_t len); + +bool ODK_NonceValuesEqual(const ODK_NonceValues* a, const ODK_NonceValues* b); + +#ifdef __cplusplus +} /* extern "C" */ +#endif +#endif /* WIDEVINE_ODK_SRC_ODK_UTIL_H_ */ diff --git a/oemcrypto/odk/src/serialization_base.c b/oemcrypto/odk/src/serialization_base.c new file mode 100644 index 0000000..4681b54 --- /dev/null +++ b/oemcrypto/odk/src/serialization_base.c @@ -0,0 +1,259 @@ +/* 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. */ + +#include "serialization_base.h" + +#include +#include +#include + +#include "OEMCryptoCENCCommon.h" +#include "odk_assert.h" +#include "odk_overflow.h" + +struct _Message { + uint8_t* base; + size_t capacity; + size_t size; /* bytes written */ + size_t read_offset; /* bytes read */ + MessageStatus status; +}; + +/* TODO(b/150776214): this can be removed once AllocateMessage gets cleaned up + */ +/* + * odk_static_assert(SIZE_OF_MESSAGE_STRUCT >= sizeof(struct _Message), + * "SIZE_OF_MESSAGE_STRUCT too small"); + */ + +bool ValidMessage(Message* message) { + if (message == NULL) { + return false; + } + if (message->status != MESSAGE_STATUS_OK) { + return false; + } + if (message->base == NULL) { + message->status = MESSAGE_STATUS_NULL_POINTER_ERROR; + return false; + } + if (message->size > message->capacity || + message->read_offset > message->size) { + message->status = MESSAGE_STATUS_OVERFLOW_ERROR; + return false; + } + return true; +} + +static void PackBytes(Message* message, const uint8_t* ptr, size_t count) { + if (count <= message->capacity - message->size) { + memcpy((void*)(message->base + message->size), (void*)ptr, count); + message->size += count; + } else { + message->status = MESSAGE_STATUS_OVERFLOW_ERROR; + } +} + +void Pack_enum(Message* message, int value) { + uint32_t v32 = value; + Pack_uint32_t(message, &v32); +} + +void Pack_bool(Message* message, const bool* value) { + if (!ValidMessage(message)) return; + uint8_t data[4] = {0}; + data[3] = *value ? 1 : 0; + PackBytes(message, data, sizeof(data)); +} + +void Pack_uint16_t(Message* message, const uint16_t* value) { + if (!ValidMessage(message)) return; + uint8_t data[2] = {0}; + data[0] = *value >> 8; + data[1] = *value >> 0; + PackBytes(message, data, sizeof(data)); +} + +void Pack_uint32_t(Message* message, const uint32_t* value) { + if (!ValidMessage(message)) return; + uint8_t data[4] = {0}; + data[0] = *value >> 24; + data[1] = *value >> 16; + data[2] = *value >> 8; + data[3] = *value >> 0; + PackBytes(message, data, sizeof(data)); +} + +void Pack_uint64_t(Message* message, const uint64_t* value) { + if (!ValidMessage(message)) return; + uint32_t hi = *value >> 32; + uint32_t lo = *value; + Pack_uint32_t(message, &hi); + Pack_uint32_t(message, &lo); +} + +void PackArray(Message* message, const uint8_t* base, size_t size) { + if (!ValidMessage(message)) return; + PackBytes(message, base, size); +} + +void Pack_OEMCrypto_Substring(Message* msg, const OEMCrypto_Substring* obj) { + uint32_t offset = obj->offset; + uint32_t length = obj->length; + Pack_uint32_t(msg, &offset); + Pack_uint32_t(msg, &length); +} + +static void UnpackBytes(Message* message, uint8_t* ptr, size_t count) { + if (count <= message->size - message->read_offset) { + memcpy((void*)ptr, (void*)(message->base + message->read_offset), count); + message->read_offset += count; + } else { + message->status = MESSAGE_STATUS_UNDERFLOW_ERROR; + } +} + +int Unpack_enum(Message* message) { + uint32_t v32; + Unpack_uint32_t(message, &v32); + return v32; +} + +void Unpack_bool(Message* message, bool* value) { + if (!ValidMessage(message)) return; + uint8_t data[4] = {0}; + UnpackBytes(message, data, sizeof(data)); + *value = (0 != data[3]); +} + +void Unpack_uint16_t(Message* message, uint16_t* value) { + if (!ValidMessage(message)) return; + uint8_t data[2] = {0}; + UnpackBytes(message, data, sizeof(data)); + *value = data[0]; + *value = *value << 8 | data[1]; +} + +void Unpack_uint32_t(Message* message, uint32_t* value) { + if (!ValidMessage(message)) return; + uint8_t data[4] = {0}; + UnpackBytes(message, data, sizeof(data)); + *value = data[0]; + *value = *value << 8 | data[1]; + *value = *value << 8 | data[2]; + *value = *value << 8 | data[3]; +} + +void Unpack_uint64_t(Message* message, uint64_t* value) { + if (!ValidMessage(message)) return; + uint32_t hi = 0; + uint32_t lo = 0; + Unpack_uint32_t(message, &hi); + Unpack_uint32_t(message, &lo); + *value = hi; + *value = *value << 32 | lo; +} + +void Unpack_OEMCrypto_Substring(Message* msg, OEMCrypto_Substring* obj) { + uint32_t offset = 0, length = 0; + Unpack_uint32_t(msg, &offset); + Unpack_uint32_t(msg, &length); + if (!ValidMessage(msg)) return; + /* Each substring should be contained within the message body, which is in the + * total message, just after the core message. The offset of a substring is + * relative to the message body. So we need to verify: + * 0 < offset and offset + length < message->capacity - message->size + * or offset + length + message->size < message->capacity + */ + size_t substring_end = 0; /* = offset + length; */ + size_t end = 0; /* = substring_end + message->size; */ + if (odk_add_overflow_ux(offset, length, &substring_end) || + odk_add_overflow_ux(substring_end, msg->size, &end) || + end > msg->capacity) { + msg->status = MESSAGE_STATUS_OVERFLOW_ERROR; + return; + } + obj->offset = offset; + obj->length = length; +} + +/* copy out */ +void UnpackArray(Message* message, uint8_t* address, size_t size) { + if (!ValidMessage(message)) return; + UnpackBytes(message, address, size); +} + +/* + * The message structure, which is separate from the buffer, + * is initialized to reference the buffer + */ +void InitMessage(Message* message, uint8_t* buffer, size_t capacity) { + if (message == NULL) return; + memset(message, 0, sizeof(Message)); + message->base = buffer; + message->capacity = capacity; + message->size = 0; + message->read_offset = 0; + message->status = MESSAGE_STATUS_OK; +} + +/* + * The message structure is in the first sizeof(Memory) bytes + * of the buffer + */ +Message* CreateMessage(uint8_t* buffer, size_t buffer_size) { + if (buffer == NULL || buffer_size < sizeof(Message)) return NULL; + Message* message = (Message*)buffer; + message->base = buffer + sizeof(Message); + message->capacity = buffer_size - sizeof(Message); + message->size = 0; + message->read_offset = 0; + message->status = MESSAGE_STATUS_OK; + return message; +} + +/* + * Set the message to an empty state + */ +void ResetMessage(Message* message) { + message->size = 0; + message->read_offset = 0; + message->status = MESSAGE_STATUS_OK; +} + +uint8_t* GetBase(Message* message) { + if (message == NULL) return NULL; + return message->base; +} + +size_t GetCapacity(Message* message) { + if (message == NULL) return 0; + return message->capacity; +} + +size_t GetSize(Message* message) { + if (message == NULL) return 0; + return message->size; +} + +void SetSize(Message* message, size_t size) { + if (message == NULL) return; + if (size > message->capacity) + message->status = MESSAGE_STATUS_OVERFLOW_ERROR; + else + message->size = size; +} + +MessageStatus GetStatus(Message* message) { return message->status; } + +void SetStatus(Message* message, MessageStatus status) { + message->status = status; +} + +size_t GetOffset(Message* message) { + if (message == NULL) return 0; + return message->read_offset; +} + +size_t SizeOfMessageStruct() { return sizeof(Message); } diff --git a/oemcrypto/odk/src/serialization_base.h b/oemcrypto/odk/src/serialization_base.h new file mode 100644 index 0000000..c99f4d1 --- /dev/null +++ b/oemcrypto/odk/src/serialization_base.h @@ -0,0 +1,96 @@ +/* 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 WIDEVINE_ODK_SRC_SERIALIZATION_BASE_H_ +#define WIDEVINE_ODK_SRC_SERIALIZATION_BASE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#include "OEMCryptoCENCCommon.h" + +#define SIZE_OF_MESSAGE_STRUCT 64 + +/* + * Description: + * Point |msg| to stack-array |blk|. + * |blk| is guaranteed large enough to hold a |Message| struct. + * |blk| cannot be used in the same scope as a variable name. + * |msg| points to valid memory in the same scope |AllocateMessage| is used. + * Parameters: + * msg: pointer to pointer to |Message| struct + * blk: variable name for stack-array + */ +#define AllocateMessage(msg, blk) \ + uint8_t blk[SIZE_OF_MESSAGE_STRUCT]; \ + *(msg) = (Message*)(blk) + +typedef struct _Message Message; + +typedef enum { + MESSAGE_STATUS_OK, + MESSAGE_STATUS_UNKNOWN_ERROR, + MESSAGE_STATUS_OVERFLOW_ERROR, + MESSAGE_STATUS_UNDERFLOW_ERROR, + MESSAGE_STATUS_PARSE_ERROR, + MESSAGE_STATUS_NULL_POINTER_ERROR, + MESSAGE_STATUS_API_VALUE_ERROR +} MessageStatus; + +bool ValidMessage(Message* message); + +void Pack_enum(Message* message, int value); +void Pack_bool(Message* message, const bool* value); +void Pack_uint16_t(Message* message, const uint16_t* value); +void Pack_uint32_t(Message* message, const uint32_t* value); +void Pack_uint64_t(Message* message, const uint64_t* value); +void PackArray(Message* message, const uint8_t* base, size_t size); +void Pack_OEMCrypto_Substring(Message* msg, const OEMCrypto_Substring* obj); + +int Unpack_enum(Message* message); +void Unpack_bool(Message* message, bool* value); +void Unpack_uint16_t(Message* message, uint16_t* value); +void Unpack_uint32_t(Message* message, uint32_t* value); +void Unpack_uint64_t(Message* message, uint64_t* value); +void UnpackArray(Message* message, uint8_t* address, + size_t size); /* copy out */ +void Unpack_OEMCrypto_Substring(Message* msg, OEMCrypto_Substring* obj); + +/* + * Create a message from a buffer. The message structure consumes the first + * sizeof(Message) bytes of the buffer. The caller is responsible for ensuring + * that the buffer remains allocated for the lifetime of the message. + */ +Message* CreateMessage(uint8_t* buffer, size_t buffer_size); + +/* + * Initialize a message structure to reference a separate buffer. The caller + * is responsible for ensuring that the buffer remains allocated for the + * lifetime of the message. + */ +void InitMessage(Message* message, uint8_t* buffer, size_t capacity); + +/* + * Reset an existing the message to an empty state + */ +void ResetMessage(Message* message); +uint8_t* GetBase(Message* message); +size_t GetCapacity(Message* message); +size_t GetSize(Message* message); +void SetSize(Message* message, size_t size); +MessageStatus GetStatus(Message* message); +void SetStatus(Message* message, MessageStatus status); +size_t GetOffset(Message* message); + +size_t SizeOfMessageStruct(); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* WIDEVINE_ODK_SRC_SERIALIZATION_BASE_H_ */ diff --git a/oemcrypto/odk/test/fuzzing/Android.bp b/oemcrypto/odk/test/fuzzing/Android.bp new file mode 100644 index 0000000..d6b1f2a --- /dev/null +++ b/oemcrypto/odk/test/fuzzing/Android.bp @@ -0,0 +1,168 @@ +// 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. + +cc_defaults { + name: "odk_fuzz_library_defaults", + srcs: [ + "odk_fuzz_helper.cpp", + ], + include_dirs: [ + "vendor/widevine/libwvmediacas/oemcrypto/odk/test", + "vendor/widevine/libwvmediacas/oemcrypto/odk/include", + "vendor/widevine/libwvmediacas/oemcrypto/odk/src", + ], +} + +cc_fuzz { + name: "odk_license_request_fuzz", + srcs: [ + "odk_license_request_fuzz.cpp", + ], + fuzz_config: { + componentid: 611718, + }, + corpus: ["corpus/little_endian_64bit/license_request_corpus/*"], + static_libs: [ + "libwv_kdo", + "libwv_odk", + ], + defaults: ["odk_fuzz_library_defaults"], + proprietary: true, +} + +cc_fuzz { + name: "odk_renewal_request_fuzz", + srcs: [ + "odk_renewal_request_fuzz.cpp", + ], + fuzz_config: { + componentid: 611718, + }, + corpus: ["corpus/little_endian_64bit/renewal_request_corpus/*"], + static_libs: [ + "libwv_kdo", + "libwv_odk", + ], + defaults: ["odk_fuzz_library_defaults"], + proprietary: true, +} + +cc_fuzz { + name: "odk_provisioning_request_fuzz", + srcs: [ + "odk_provisioning_request_fuzz.cpp", + ], + fuzz_config: { + componentid: 611718, + }, + corpus: ["corpus/little_endian_64bit/provisioning_request_corpus/*"], + static_libs: [ + "libwv_kdo", + "libwv_odk", + ], + defaults: ["odk_fuzz_library_defaults"], + proprietary: true, +} + +cc_fuzz { + name: "odk_license_response_fuzz", + srcs: [ + "odk_license_response_fuzz.cpp", + ], + fuzz_config: { + componentid: 611718, + }, + corpus: ["corpus/little_endian_64bit/license_response_corpus/*"], + static_libs: [ + "libwv_kdo", + "libwv_odk", + ], + defaults: ["odk_fuzz_library_defaults"], + proprietary: true, +} + +cc_fuzz { + name: "odk_renewal_response_fuzz", + srcs: [ + "odk_renewal_response_fuzz.cpp", + ], + fuzz_config: { + componentid: 611718, + }, + corpus: ["corpus/little_endian_64bit/renewal_response_corpus/*"], + static_libs: [ + "libwv_kdo", + "libwv_odk", + ], + defaults: ["odk_fuzz_library_defaults"], + proprietary: true, +} + +cc_fuzz { + name: "odk_provisioning_response_fuzz", + srcs: [ + "odk_provisioning_response_fuzz.cpp", + ], + fuzz_config: { + componentid: 611718, + }, + corpus: ["corpus/little_endian_64bit/provisioning_response_corpus/*"], + static_libs: [ + "libwv_kdo", + "libwv_odk", + ], + defaults: ["odk_fuzz_library_defaults"], + proprietary: true, +} + +cc_fuzz { + name: "odk_license_response_fuzz_with_mutator", + srcs: [ + "odk_license_response_fuzz_with_mutator.cpp", + ], + fuzz_config: { + componentid: 611718, + }, + corpus: ["corpus/little_endian_64bit/license_response_corpus/*"], + static_libs: [ + "libwv_kdo", + "libwv_odk", + ], + defaults: ["odk_fuzz_library_defaults"], + proprietary: true, +} + +cc_fuzz { + name: "odk_renewal_response_fuzz_with_mutator", + srcs: [ + "odk_renewal_response_fuzz_with_mutator.cpp", + ], + fuzz_config: { + componentid: 611718, + }, + corpus: ["corpus/little_endian_64bit/renewal_response_corpus/*"], + static_libs: [ + "libwv_kdo", + "libwv_odk", + ], + defaults: ["odk_fuzz_library_defaults"], + proprietary: true, +} + +cc_fuzz { + name: "odk_provisioning_response_fuzz_with_mutator", + srcs: [ + "odk_provisioning_response_fuzz_with_mutator.cpp", + ], + fuzz_config: { + componentid: 611718, + }, + corpus: ["corpus/little_endian_64bit/provisioning_response_corpus/*"], + static_libs: [ + "libwv_kdo", + "libwv_odk", + ], + defaults: ["odk_fuzz_library_defaults"], + proprietary: true, +} \ No newline at end of file diff --git a/oemcrypto/odk/test/fuzzing/README.md b/oemcrypto/odk/test/fuzzing/README.md new file mode 100644 index 0000000..eb7da4f --- /dev/null +++ b/oemcrypto/odk/test/fuzzing/README.md @@ -0,0 +1,19 @@ +# ODK Fuzzing + +## Objective + +* Run fuzzing on ODK and KDO serialize and deserialize APIs using google + supported fuzzer engines to find security vulnerabilities. Any issues found + by clusterfuzz will be reported to + [odk fuzz buganizer](https://b.corp.google.com/issues?q=componentid:425099%20status:open%20reporter:cluster-fuzz-googleplex@google.com). + +## Run fuzz target on local machine + +* In order to run fuzz target locally and see code coverage, save binary input + to be tested against fuzz target into a temporary corpus directory and + execute following commands + + ```shell + $ blaze build --config=asan-fuzzer //your:target + $ blaze-bin/your/target FULL_CORPUS_DIR + ``` diff --git a/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/license_request_corpus/602c63d2f3d13ca3206cdf204cde24e7d8f4266c b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/license_request_corpus/602c63d2f3d13ca3206cdf204cde24e7d8f4266c new file mode 100644 index 0000000..dc8cabc Binary files /dev/null and b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/license_request_corpus/602c63d2f3d13ca3206cdf204cde24e7d8f4266c differ diff --git a/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/license_request_corpus/8cebdcc0161125a10e19c45f055051712873de25 b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/license_request_corpus/8cebdcc0161125a10e19c45f055051712873de25 new file mode 100644 index 0000000..608e888 Binary files /dev/null and b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/license_request_corpus/8cebdcc0161125a10e19c45f055051712873de25 differ diff --git a/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/license_response_corpus/4e578d6c9628e832c099623b44f56d95aa37f94b b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/license_response_corpus/4e578d6c9628e832c099623b44f56d95aa37f94b new file mode 100644 index 0000000..e319747 Binary files /dev/null and b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/license_response_corpus/4e578d6c9628e832c099623b44f56d95aa37f94b differ diff --git a/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/license_response_corpus/5b693511ef850e42c5ffded171794dbeb9460cc0 b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/license_response_corpus/5b693511ef850e42c5ffded171794dbeb9460cc0 new file mode 100644 index 0000000..0cf77b5 Binary files /dev/null and b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/license_response_corpus/5b693511ef850e42c5ffded171794dbeb9460cc0 differ diff --git a/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/license_response_corpus/b6b865b095697164ad032c2f695ed828f5754749 b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/license_response_corpus/b6b865b095697164ad032c2f695ed828f5754749 new file mode 100644 index 0000000..225f87a --- /dev/null +++ b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/license_response_corpus/b6b865b095697164ad032c2f695ed828f5754749 @@ -0,0 +1 @@ +{"componentid":425099} diff --git a/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/license_response_corpus/dba39b6cf6524e996397ddc1e08b928b5c92bb5d b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/license_response_corpus/dba39b6cf6524e996397ddc1e08b928b5c92bb5d new file mode 100644 index 0000000..51f67eb Binary files /dev/null and b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/license_response_corpus/dba39b6cf6524e996397ddc1e08b928b5c92bb5d differ diff --git a/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/license_response_corpus/dd1bc1827a331b7aed2a6fb6740da032123aa0a8 b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/license_response_corpus/dd1bc1827a331b7aed2a6fb6740da032123aa0a8 new file mode 100644 index 0000000..a3f3b26 Binary files /dev/null and b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/license_response_corpus/dd1bc1827a331b7aed2a6fb6740da032123aa0a8 differ diff --git a/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/provisioning_request_corpus/53c26407b39c997143146a0dce8ff0ac11f565e1 b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/provisioning_request_corpus/53c26407b39c997143146a0dce8ff0ac11f565e1 new file mode 100644 index 0000000..a2d65a8 Binary files /dev/null and b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/provisioning_request_corpus/53c26407b39c997143146a0dce8ff0ac11f565e1 differ diff --git a/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/provisioning_request_corpus/fab3c99d604bab7b7bf5c54c5bd995fc98d4d96f b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/provisioning_request_corpus/fab3c99d604bab7b7bf5c54c5bd995fc98d4d96f new file mode 100644 index 0000000..a4ded57 Binary files /dev/null and b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/provisioning_request_corpus/fab3c99d604bab7b7bf5c54c5bd995fc98d4d96f differ diff --git a/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/provisioning_response_corpus/91e10d030fbdd3374e57a2720f09488f2b03ce69 b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/provisioning_response_corpus/91e10d030fbdd3374e57a2720f09488f2b03ce69 new file mode 100644 index 0000000..0616a29 Binary files /dev/null and b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/provisioning_response_corpus/91e10d030fbdd3374e57a2720f09488f2b03ce69 differ diff --git a/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/renewal_request_corpus/12a72efb395e731ec4470b5f5b6768d6806e9131 b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/renewal_request_corpus/12a72efb395e731ec4470b5f5b6768d6806e9131 new file mode 100644 index 0000000..7371c1f Binary files /dev/null and b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/renewal_request_corpus/12a72efb395e731ec4470b5f5b6768d6806e9131 differ diff --git a/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/renewal_request_corpus/21de033b9baf2a0e82ae3b4185b22aa0acf69bbc b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/renewal_request_corpus/21de033b9baf2a0e82ae3b4185b22aa0acf69bbc new file mode 100644 index 0000000..ef24b18 Binary files /dev/null and b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/renewal_request_corpus/21de033b9baf2a0e82ae3b4185b22aa0acf69bbc differ diff --git a/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/renewal_request_corpus/97bf96be666434bfa93dbfb36b81baeefed14170 b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/renewal_request_corpus/97bf96be666434bfa93dbfb36b81baeefed14170 new file mode 100644 index 0000000..4e6f216 Binary files /dev/null and b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/renewal_request_corpus/97bf96be666434bfa93dbfb36b81baeefed14170 differ diff --git a/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/renewal_request_corpus/a7b0e7dca597331d7f051204096c9d01ba6d468e b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/renewal_request_corpus/a7b0e7dca597331d7f051204096c9d01ba6d468e new file mode 100644 index 0000000..748c29c Binary files /dev/null and b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/renewal_request_corpus/a7b0e7dca597331d7f051204096c9d01ba6d468e differ diff --git a/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/renewal_response_corpus/38df40a320f60e955006aaa294b74d45a316e50f b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/renewal_response_corpus/38df40a320f60e955006aaa294b74d45a316e50f new file mode 100644 index 0000000..4abb16a Binary files /dev/null and b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/renewal_response_corpus/38df40a320f60e955006aaa294b74d45a316e50f differ diff --git a/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/renewal_response_corpus/9962997b5ea87005276319cbfff67884846485cf b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/renewal_response_corpus/9962997b5ea87005276319cbfff67884846485cf new file mode 100644 index 0000000..892a4ed Binary files /dev/null and b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/renewal_response_corpus/9962997b5ea87005276319cbfff67884846485cf differ diff --git a/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/renewal_response_corpus/c84663115c890873dd585987c1223193d29aef16 b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/renewal_response_corpus/c84663115c890873dd585987c1223193d29aef16 new file mode 100644 index 0000000..b1fc6da Binary files /dev/null and b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/renewal_response_corpus/c84663115c890873dd585987c1223193d29aef16 differ diff --git a/oemcrypto/odk/test/fuzzing/corpus_generator/Android.bp b/oemcrypto/odk/test/fuzzing/corpus_generator/Android.bp new file mode 100644 index 0000000..cdd672e --- /dev/null +++ b/oemcrypto/odk/test/fuzzing/corpus_generator/Android.bp @@ -0,0 +1,27 @@ +// 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. + +// ---------------------------------------------------------------- +// Builds odk_corpus_generator shared library, which can be used with +// LD_PRELOAD command to generate corpus by intercepting oemcrypto +// unit tests. +// ---------------------------------------------------------------- +// Builds libwv_odk.so, The ODK shared Library (libwv_odk) is used +// by the OEMCrypto unit tests to generate corpus for ODK fuzz scrips. +cc_library_shared { + name: "libwv_odk_corpus_generator", + include_dirs: [ + "vendor/widevine/libwvmediacas/oemcrypto/include", + "vendor/widevine/libwvmediacas/oemcrypto/odk/include", + "vendor/widevine/libwvmediacas/oemcrypto/odk/test", + ], + host_ldlibs: ["-ldl"], + srcs: [ + "odk_corpus_generator.c", + "odk_corpus_generator_helper.c", + ], + proprietary: true, + + owner: "widevine", +} diff --git a/oemcrypto/odk/test/fuzzing/corpus_generator/README.md b/oemcrypto/odk/test/fuzzing/corpus_generator/README.md new file mode 100644 index 0000000..f6df374 --- /dev/null +++ b/oemcrypto/odk/test/fuzzing/corpus_generator/README.md @@ -0,0 +1,79 @@ +# Objective + +The Idea behind the corpus generator code is to intercept OEMCrypto unit test +calls to odk APIs using LD_PRELOAD and read the data into corpus files which can +be fed as corpus to fuzzer scripts. + +LD_PRELOAD command needs to be run from cdm repository while running oemcrypto +unit tests. + +## Get OEMCrypto and Build OEMCrypto unit tests: + +* Install Pre-requisites + + ```shell + $ sudo apt-get install gyp ninja-build + ``` + +* download cdm source code (including ODK & OEMCrypto unit tests): + + ```shell + $ git clone sso://widevine-internal/cdm + ``` + +* We need to run odk as a dynamic library in order to use LD_PRELOAD, apply + patch from go/wvgerrit/95090 to locally cloned repo which has changes to run + odk as dynamic library: + + ```shell + $ cd /path/to/cdm/repo + $ git fetch origin 209721cc901745999e08e35466e74f708321267e + $ git cherry-pick FETCH_HEAD + ``` + +* Build OEMCrypto unit tests: + + ```shell + $ cd /path/to/cdm/repo + $ export PATH_TO_CDM_DIR=.. + $ gyp --format=ninja --depth=$(pwd) oemcrypto/oemcrypto_unittests.gyp + $ ninja -C out/Default/ + ``` + +## Capture corpus for odk fuzzer by intercepting OEMCrypto unit tests: + +When we run LD_PRELOAD command odk_corpus_generator.so gets preloaded before +oemcrypto_unittests and odk_corpus_generator has functions to intercept calls to +ODK request and response APIs. Each call to odk API from oemcrypto_unittests +gets intercepted and input to ODK de serialize response APIs and output from ODK +serialize request APIs is captured in binary format and stored into corpus files + +In order to run LD_PRELOAD command, we need to compile corpus generator shared +library and need to preload that before OEMCrypto unit tests run + +* Compile shared library + + ```shell + $ cd /path/to/cdm/repo + $ gyp --format=ninja --depth=$(pwd) oemcrypto/odk/test/fuzzing/corpus_generator/odk_fuzz_corpus_generator.gyp + $ ninja -C out/Default/ + ``` + +* Preload the shared library before running OEMCrypto unit tests + + ```shell + $ cd oemcrypto/odk/test/fuzzing/corpus + $ mkdir license_request_corpus license_response_corpus renewal_request_corpus renewal_response_corpus provisioning_request_corpus provisioning_response_corpus + $ cd /path/to/cdm/repo + $ LD_PRELOAD=out/Default/lib/libodk_corpus_generator.so ./out/Default/oemcrypto_unittests + ``` + +LD_PRELOAD command runs oemcrypto_unittests with odk_corpus_generator as +interceptor. We should see unit tests being executed. The corpus files in binary +format will be captured into `oemcrypto/odk/test/fuzzing/corpus` path. These +files can be used as input corpus for ODK request and response fuzzer scripts. + +The generated corpus files can be minimized using go/testcorpus#minimize and +uploaded into google3 repository under following directory under respective +corpus types +`fuzzing/corpus` diff --git a/oemcrypto/odk/test/fuzzing/corpus_generator/odk_corpus_generator.c b/oemcrypto/odk/test/fuzzing/corpus_generator/odk_corpus_generator.c new file mode 100644 index 0000000..5b36d89 --- /dev/null +++ b/oemcrypto/odk/test/fuzzing/corpus_generator/odk_corpus_generator.c @@ -0,0 +1,160 @@ +/* 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. */ + +/* We must define this macro to get RTLD_NEXT definition from */ +#define _GNU_SOURCE + +#include + +#include "fuzzing/corpus_generator/odk_corpus_generator_helper.h" +#include "fuzzing/odk_fuzz_structs.h" +#include "odk_structs.h" + +OEMCryptoResult ODK_PrepareCoreLicenseRequest( + uint8_t* message, size_t message_length, size_t* core_message_length, + const ODK_NonceValues* nonce_values) { + OEMCryptoResult (*original_function)(uint8_t*, size_t, size_t*, + const ODK_NonceValues*); + original_function = dlsym(RTLD_NEXT, "ODK_PrepareCoreLicenseRequest"); + OEMCryptoResult oem_crypto_result = (*original_function)( + message, message_length, core_message_length, nonce_values); + char* file_name = GetFileName("license_request_corpus"); + + /* License Request format expected by fuzzer - [Core License Request] */ + AppendToFile(file_name, (const char*)message, *core_message_length); + free(file_name); + return oem_crypto_result; +} + +OEMCryptoResult ODK_ParseLicense( + const uint8_t* message, size_t message_length, size_t core_message_length, + bool initial_license_load, bool usage_entry_present, + const uint8_t* request_hash, ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, ODK_NonceValues* nonce_values, + ODK_ParsedLicense* parsed_license) { + struct ODK_ParseLicense_Args parse_license_args; + parse_license_args.nonce_values = *nonce_values; + memcpy(parse_license_args.request_hash, request_hash, ODK_SHA256_HASH_SIZE); + parse_license_args.timer_limits = *timer_limits; + parse_license_args.clock_values = *clock_values; + parse_license_args.usage_entry_present = usage_entry_present; + parse_license_args.initial_license_load = initial_license_load; + OEMCryptoResult (*original_function)( + const uint8_t*, size_t, size_t, bool, bool, const uint8_t*, + ODK_TimerLimits*, ODK_ClockValues*, ODK_NonceValues*, ODK_ParsedLicense*); + original_function = dlsym(RTLD_NEXT, "ODK_ParseLicense"); + OEMCryptoResult oem_crypto_result = (*original_function)( + message, message_length, core_message_length, initial_license_load, + usage_entry_present, request_hash, timer_limits, clock_values, + nonce_values, parsed_license); + char* file_name = GetFileName("license_response_corpus"); + + /* License Response format expected by fuzzer - [ODK_ParseLicense_Args][Core + */ + /* License Response] */ + AppendToFile(file_name, (const char*)&parse_license_args, + sizeof(struct ODK_ParseLicense_Args)); + AppendToFile(file_name, (const char*)message, core_message_length); + free(file_name); + return oem_crypto_result; +} + +OEMCryptoResult ODK_PrepareCoreRenewalRequest(uint8_t* message, + size_t message_length, + size_t* core_message_size, + ODK_NonceValues* nonce_values, + ODK_ClockValues* clock_values, + uint64_t system_time_seconds) { + OEMCryptoResult (*original_function)( + uint8_t*, size_t, size_t*, ODK_NonceValues*, ODK_ClockValues*, uint64_t); + original_function = dlsym(RTLD_NEXT, "ODK_PrepareCoreRenewalRequest"); + OEMCryptoResult oem_crypto_result = + (*original_function)(message, message_length, core_message_size, + nonce_values, clock_values, system_time_seconds); + char* file_name = GetFileName("renewal_request_corpus"); + + /* License Request format expected by fuzzer - [ODK_ClockValues][Core */ + /* License Request] */ + AppendToFile(file_name, (const char*)clock_values, sizeof(ODK_ClockValues)); + AppendToFile(file_name, (const char*)message, *core_message_size); + free(file_name); + return oem_crypto_result; +} + +OEMCryptoResult ODK_ParseRenewal(const uint8_t* message, size_t message_length, + size_t core_message_length, + const ODK_NonceValues* nonce_values, + uint64_t system_time, + const ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, + uint64_t* timer_value) { + struct ODK_ParseRenewal_Args parse_renewal_args; + parse_renewal_args.nonce_values = *nonce_values; + parse_renewal_args.clock_values = *clock_values; + parse_renewal_args.timer_limits = *timer_limits; + parse_renewal_args.system_time = system_time; + OEMCryptoResult (*original_function)( + const uint8_t*, size_t, size_t, const ODK_NonceValues*, uint64_t, + const ODK_TimerLimits*, ODK_ClockValues*, uint64_t*); + original_function = dlsym(RTLD_NEXT, "ODK_ParseRenewal"); + OEMCryptoResult oem_crypto_result = (*original_function)( + message, message_length, core_message_length, nonce_values, system_time, + timer_limits, clock_values, timer_value); + char* file_name = GetFileName("renewal_response_corpus"); + + /* Renewal Response format expected by fuzzer - [ODK_ParseRenewal_Args][Core + */ + /* Renewal Response] */ + AppendToFile(file_name, (const char*)&parse_renewal_args, + sizeof(struct ODK_ParseRenewal_Args)); + AppendToFile(file_name, (const char*)message, core_message_length); + free(file_name); + return oem_crypto_result; +} + +OEMCryptoResult ODK_PrepareCoreProvisioningRequest( + uint8_t* message, size_t message_length, size_t* core_message_length, + const ODK_NonceValues* nonce_values, const uint8_t* device_id, + size_t device_id_length) { + OEMCryptoResult (*original_function)(uint8_t*, size_t, size_t*, + const ODK_NonceValues*, const uint8_t*, + size_t); + original_function = dlsym(RTLD_NEXT, "ODK_PrepareCoreProvisioningRequest"); + OEMCryptoResult oem_crypto_result = + (*original_function)(message, message_length, core_message_length, + nonce_values, device_id, device_id_length); + char* file_name = GetFileName("provisioning_request_corpus"); + + /* Provisioning Request format expected by fuzzer - [Core Provisioning */ + /* Request] */ + AppendToFile(file_name, (const char*)message, *core_message_length); + free(file_name); + return oem_crypto_result; +} + +OEMCryptoResult ODK_ParseProvisioning( + const uint8_t* message, size_t message_length, size_t core_message_length, + const ODK_NonceValues* nonce_values, const uint8_t* device_id, + size_t device_id_length, ODK_ParsedProvisioning* parsed_response) { + struct ODK_ParseProvisioning_Args parse_provisioning_args; + parse_provisioning_args.nonce_values = *nonce_values; + memcpy(parse_provisioning_args.device_id, device_id, device_id_length); + parse_provisioning_args.device_id_length = device_id_length; + OEMCryptoResult (*original_function)(const uint8_t*, size_t, size_t, + const ODK_NonceValues*, const uint8_t*, + size_t, ODK_ParsedProvisioning*); + original_function = dlsym(RTLD_NEXT, "ODK_ParseProvisioning"); + OEMCryptoResult oem_crypto_result = (*original_function)( + message, message_length, core_message_length, nonce_values, device_id, + device_id_length, parsed_response); + char* file_name = GetFileName("provisioning_response_corpus"); + + /* Provisioning Response format expected by fuzzer - */ + /* [ODK_ParseProvisioning_Args][Core Provisioning Response] */ + AppendToFile(file_name, (const char*)&parse_provisioning_args, + sizeof(struct ODK_ParseProvisioning_Args)); + AppendToFile(file_name, (const char*)message, core_message_length); + free(file_name); + return oem_crypto_result; +} diff --git a/oemcrypto/odk/test/fuzzing/corpus_generator/odk_corpus_generator_helper.c b/oemcrypto/odk/test/fuzzing/corpus_generator/odk_corpus_generator_helper.c new file mode 100644 index 0000000..fa35dca --- /dev/null +++ b/oemcrypto/odk/test/fuzzing/corpus_generator/odk_corpus_generator_helper.c @@ -0,0 +1,22 @@ +/* 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 "fuzzing/corpus_generator/odk_corpus_generator_helper.h" + +void AppendToFile(const char* file_name, const char* message, + const size_t message_size) { + FILE* fptr; + if ((fptr = fopen(file_name, "ab")) == NULL) { + printf("Error! opening file %s", file_name); + return; + } + fwrite(message, message_size, 1, fptr); + fclose(fptr); +} + +char* GetFileName(const char* directory) { + char* file_name; + file_name = malloc(150); + sprintf(file_name, "%s%s/%d", PATH_TO_CORPUS, directory, rand()); + return file_name; +} diff --git a/oemcrypto/odk/test/fuzzing/corpus_generator/odk_corpus_generator_helper.h b/oemcrypto/odk/test/fuzzing/corpus_generator/odk_corpus_generator_helper.h new file mode 100644 index 0000000..2dc6d51 --- /dev/null +++ b/oemcrypto/odk/test/fuzzing/corpus_generator/odk_corpus_generator_helper.h @@ -0,0 +1,19 @@ +/* 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 WIDEVINE_ODK_TEST_FUZZING_CORPUS_GENERATOR_ODK_CORPUS_GENERATOR_HELPER_H_ +#define WIDEVINE_ODK_TEST_FUZZING_CORPUS_GENERATOR_ODK_CORPUS_GENERATOR_HELPER_H_ + +#define PATH_TO_CORPUS "./oemcrypto/odk/test/fuzzing/corpus/" + +#include +#include +#include + +void AppendToFile(const char* file_name, const char* message, + const size_t message_size); + +char* GetFileName(const char* directory); + +#endif /* WIDEVINE_ODK_TEST_FUZZING_CORPUS_GENERATOR_ODK_CORPUS_GENERATOR_HELPER_H_ \ + */ diff --git a/oemcrypto/odk/test/fuzzing/odk_fuzz_helper.cpp b/oemcrypto/odk/test/fuzzing/odk_fuzz_helper.cpp new file mode 100644 index 0000000..3e6f2e0 --- /dev/null +++ b/oemcrypto/odk/test/fuzzing/odk_fuzz_helper.cpp @@ -0,0 +1,159 @@ +// 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 "fuzzing/odk_fuzz_helper.h" + +#include "odk.h" + +namespace oemcrypto_core_message { + +bool convert_byte_to_valid_boolean(const bool* in) { + const char* buf = reinterpret_cast(in); + for (int i = 0; i < sizeof(bool); i++) { + if (buf[i]) { + return true; + } + } + return false; +} + +void ConvertDataToValidBools(ODK_ParsedLicense* t) { + // Convert boolean flags in parsed_license to valid bytes to + // avoid errors from msan + t->nonce_required = convert_byte_to_valid_boolean(&t->nonce_required); + t->timer_limits.soft_enforce_playback_duration = + convert_byte_to_valid_boolean( + &t->timer_limits.soft_enforce_playback_duration); + t->timer_limits.soft_enforce_rental_duration = convert_byte_to_valid_boolean( + &t->timer_limits.soft_enforce_rental_duration); +} + +void ConvertDataToValidBools(ODK_PreparedRenewalRequest* t) {} + +void ConvertDataToValidBools(ODK_ParsedProvisioning* t) {} + +OEMCryptoResult odk_serialize_LicenseRequest( + const void* in, uint8_t* out, size_t* size, + const ODK_LicenseRequest& core_license_request, + const ODK_NonceValues* nonce_values) { + return ODK_PrepareCoreLicenseRequest(out, SIZE_MAX, size, nonce_values); +} + +OEMCryptoResult odk_serialize_RenewalRequest( + const void* in, uint8_t* out, size_t* size, + const ODK_RenewalRequest& core_renewal, ODK_NonceValues* nonce_values) { + ODK_ClockValues clock{}; + memcpy(&clock, in, sizeof(ODK_ClockValues)); + uint64_t system_time_seconds = core_renewal.playback_time_seconds; + return ODK_PrepareCoreRenewalRequest(out, SIZE_MAX, size, nonce_values, + &clock, system_time_seconds); +} + +OEMCryptoResult odk_serialize_ProvisioningRequest( + const void* in, uint8_t* out, size_t* size, + const ODK_ProvisioningRequest& core_provisioning, + const ODK_NonceValues* nonce_values) { + const std::string& device_id = core_provisioning.device_id; + return ODK_PrepareCoreProvisioningRequest( + out, SIZE_MAX, size, nonce_values, + reinterpret_cast(device_id.data()), device_id.size()); +} + +OEMCryptoResult odk_deserialize_LicenseResponse(const uint8_t* message, + size_t core_message_length, + ODK_ParseLicense_Args* a, + ODK_NonceValues* nonce_values, + ODK_ParsedLicense* parsed_lic) { + return ODK_ParseLicense(message, SIZE_MAX, core_message_length, + static_cast(a->initial_license_load), + static_cast(a->usage_entry_present), + a->request_hash, &a->timer_limits, &a->clock_values, + nonce_values, parsed_lic); +} + +OEMCryptoResult odk_deserialize_RenewalResponse( + const uint8_t* buf, size_t len, ODK_ParseRenewal_Args* a, + ODK_NonceValues* nonce_values, ODK_PreparedRenewalRequest* renewal_msg) { + /* Address Sanitizer doesn't like values other than 0 OR 1 for boolean + * variables. Input from fuzzer can be parsed and any random bytes can be + * assigned to boolean variables. Using the workaround to mitigate sanitizer + * errors in fuzzer code and converting random bytes to 0 OR 1. + * This has no negative security impact*/ + a->timer_limits.soft_enforce_playback_duration = + convert_byte_to_valid_boolean( + &a->timer_limits.soft_enforce_playback_duration); + a->timer_limits.soft_enforce_rental_duration = convert_byte_to_valid_boolean( + &a->timer_limits.soft_enforce_rental_duration); + uint64_t timer_value = 0; + OEMCryptoResult err = + ODK_ParseRenewal(buf, SIZE_MAX, len, nonce_values, a->system_time, + &a->timer_limits, &a->clock_values, &timer_value); + const bool is_parse_renewal_response_successful = + err == ODK_SET_TIMER || err == ODK_DISABLE_TIMER || + err == ODK_TIMER_EXPIRED || err == ODK_STALE_RENEWAL; + if (!is_parse_renewal_response_successful) { + return err; + } + // In order to capture playback_time information which is part of + // renewal_msg and will be later used in kdo_serialize_RenewalResponse in + // odk_kdo method, we call Unpack_ODK_PreparedRenewalRequest private method. + // playback_time cannot be captured from publicly exposed API + // ODK_ParseRenewal. + Message* msg = nullptr; + AllocateMessage(&msg, message_block); + InitMessage(msg, const_cast(buf), len); + SetSize(msg, len); + Unpack_ODK_PreparedRenewalRequest(msg, renewal_msg); + return OEMCrypto_SUCCESS; +} + +OEMCryptoResult odk_deserialize_ProvisioningResponse( + const uint8_t* buf, size_t len, ODK_ParseProvisioning_Args* a, + ODK_NonceValues* nonce_values, ODK_ParsedProvisioning* parsed_prov) { + return ODK_ParseProvisioning(buf, SIZE_MAX, len, nonce_values, a->device_id, + a->device_id_length, parsed_prov); +} + +bool kdo_serialize_LicenseResponse(const ODK_ParseLicense_Args* args, + const ODK_ParsedLicense& parsed_lic, + std::string* oemcrypto_core_message) { + const auto& nonce_values = args->nonce_values; + ODK_LicenseRequest core_request{nonce_values.api_minor_version, + nonce_values.api_major_version, + nonce_values.nonce, nonce_values.session_id}; + std::string core_request_sha_256( + reinterpret_cast(args->request_hash), ODK_SHA256_HASH_SIZE); + return serialize::CreateCoreLicenseResponse( + parsed_lic, core_request, core_request_sha_256, oemcrypto_core_message); +} + +bool kdo_serialize_RenewalResponse( + const ODK_ParseRenewal_Args* args, + const ODK_PreparedRenewalRequest& renewal_msg, + std::string* oemcrypto_core_message) { + const auto& nonce_values = args->nonce_values; + ODK_RenewalRequest core_request{ + nonce_values.api_minor_version, nonce_values.api_major_version, + nonce_values.nonce, nonce_values.session_id, renewal_msg.playback_time}; + return serialize::CreateCoreRenewalResponse( + core_request, args->timer_limits.initial_renewal_duration_seconds, + oemcrypto_core_message); +} + +bool kdo_serialize_ProvisioningResponse( + const ODK_ParseProvisioning_Args* args, + const ODK_ParsedProvisioning& parsed_prov, + std::string* oemcrypto_core_message) { + const auto& nonce_values = args->nonce_values; + if (args->device_id_length > sizeof(args->device_id)) { + return false; + } + ODK_ProvisioningRequest core_request{ + nonce_values.api_minor_version, nonce_values.api_major_version, + nonce_values.nonce, nonce_values.session_id, + std::string(reinterpret_cast(args->device_id), + args->device_id_length)}; + return serialize::CreateCoreProvisioningResponse(parsed_prov, core_request, + oemcrypto_core_message); +} +} // namespace oemcrypto_core_message diff --git a/oemcrypto/odk/test/fuzzing/odk_fuzz_helper.h b/oemcrypto/odk/test/fuzzing/odk_fuzz_helper.h new file mode 100644 index 0000000..5672c00 --- /dev/null +++ b/oemcrypto/odk/test/fuzzing/odk_fuzz_helper.h @@ -0,0 +1,206 @@ +/* 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 WIDEVINE_ODK_TEST_FUZZING_ODK_FUZZ_HELPER_H_ +#define WIDEVINE_ODK_TEST_FUZZING_ODK_FUZZ_HELPER_H_ + +#include +#include + +#include "core_message_serialize.h" +#include "fuzzing/odk_fuzz_structs.h" +#include "odk_serialize.h" + +namespace oemcrypto_core_message { +bool convert_byte_to_valid_boolean(const bool* in); + +OEMCryptoResult odk_serialize_LicenseRequest( + const void* in, uint8_t* out, size_t* size, + const ODK_LicenseRequest& core_license_request, + const ODK_NonceValues* nonce_values); + +OEMCryptoResult odk_serialize_RenewalRequest( + const void* in, uint8_t* out, size_t* size, + const ODK_RenewalRequest& core_renewal, ODK_NonceValues* nonce_values); + +OEMCryptoResult odk_serialize_ProvisioningRequest( + const void* in, uint8_t* out, size_t* size, + const ODK_ProvisioningRequest& core_provisioning, + const ODK_NonceValues* nonce_values); + +OEMCryptoResult odk_deserialize_LicenseResponse(const uint8_t* message, + size_t core_message_length, + ODK_ParseLicense_Args* a, + ODK_NonceValues* nonce_values, + ODK_ParsedLicense* parsed_lic); + +OEMCryptoResult odk_deserialize_RenewalResponse( + const uint8_t* buf, size_t len, ODK_ParseRenewal_Args* a, + ODK_NonceValues* nonce_values, ODK_PreparedRenewalRequest* renewal_msg); + +OEMCryptoResult odk_deserialize_ProvisioningResponse( + const uint8_t* buf, size_t len, ODK_ParseProvisioning_Args* a, + ODK_NonceValues* nonce_values, ODK_ParsedProvisioning* parsed_prov); + +bool kdo_serialize_LicenseResponse(const ODK_ParseLicense_Args* args, + const ODK_ParsedLicense& parsed_lic, + std::string* oemcrypto_core_message); + +bool kdo_serialize_RenewalResponse( + const ODK_ParseRenewal_Args* args, + const ODK_PreparedRenewalRequest& renewal_msg, + std::string* oemcrypto_core_message); + +bool kdo_serialize_ProvisioningResponse( + const ODK_ParseProvisioning_Args* args, + const ODK_ParsedProvisioning& parsed_prov, + std::string* oemcrypto_core_message); + +/* Idea behind having three different functions is: */ +/* Only ODK_ParseLicense structure had fields which needed additional */ +/* procession. Having a single function with templated parameter T was */ +/* failing during compile time because other two structures doesn't have */ +/* fields that need additional processing. Hence to reduce code redundance and + */ +/* make us of common FuzzerMutateResponse across three response fuzzers, */ +/* three independent functions were defined and renewal and provisioning */ +/* functions would be empty as no additional processing is needed for them. */ +void ConvertDataToValidBools(ODK_ParsedLicense* t); + +void ConvertDataToValidBools(ODK_PreparedRenewalRequest* t); + +void ConvertDataToValidBools(ODK_ParsedProvisioning* t); + +/* Forward-declare the libFuzzer's mutator callback. Mark it weak so that */ +/* the program links successfully even outside of --config=asan-fuzzer */ +/* (apparently the only config in which LLVM uses our custom mutator). */ +extern "C" size_t LLVMFuzzerMutate(uint8_t* Data, size_t Size, size_t MaxSize) + __attribute__((weak)); + +template +size_t FuzzerMutateResponse(uint8_t* data, size_t size, size_t max_size, + const F& odk_deserialize_fun, + const G& kdo_serialize_fun) { + const size_t kArgsSize = sizeof(A); + const size_t kCoreResponseSize = sizeof(T); + const size_t kTotalResponseSize = kArgsSize + kCoreResponseSize; + + /* Deserializing data in order to make sure it deserializes properly. */ + /* Input byte array format: [function arguments][data to parse]. */ + std::shared_ptr _args(new A()); + A* args = _args.get(); + memcpy(args, data, kArgsSize); + ODK_NonceValues nonce_values = args->nonce_values; + args->nonce_values.api_major_version = ODK_MAJOR_VERSION; + const uint8_t* buf = data + kArgsSize; + T t = {}; + OEMCryptoResult result = + odk_deserialize_fun(buf, size - kArgsSize, args, &nonce_values, &t); + + /* If data doesn't deserialize successfully, We copy random bytes into */ + /* T and serialize using kdo function */ + /* which will create a valid oemcrypto core message using */ + /* nonce and request hash from function args. OEMCrypto core message acts as + */ + /* input to odk_kdo. */ + if (result != OEMCrypto_SUCCESS) { + if (max_size < kTotalResponseSize) { + return 0; + } + /* Initialize remaining bytes needed in data to zero. */ + if (size < kTotalResponseSize) { + memset(data + size, 0, kTotalResponseSize - size); + } + t = {}; + memcpy(&t, buf, kCoreResponseSize); + } + + /* Ask LLVM to run its usual mutations, hopefully giving us interesting */ + /* inputs. We copy deserialized data into pointer data, run mutations */ + /* and copy back the mutated data to args and t */ + memcpy(data + kArgsSize, &t, kCoreResponseSize); + LLVMFuzzerMutate(data, kTotalResponseSize, kTotalResponseSize); + memcpy(args, data, kArgsSize); + memcpy(&t, data + kArgsSize, kCoreResponseSize); + /* Convert boolean flags in parsed message to valid bytes to */ + /* avoid errors from msan. Only needed for parsed license. */ + ConvertDataToValidBools(&t); + /* Serialize the data after mutation. */ + std::string oemcrypto_core_message; + if (!kdo_serialize_fun(args, t, &oemcrypto_core_message)) { + return 0; + } + + /* Copy mutated and serialized oemcrypto_core_message to data */ + /* so that it acts as input to odk_kdo function. */ + memcpy(data + kArgsSize, oemcrypto_core_message.data(), + oemcrypto_core_message.size()); + return kArgsSize + oemcrypto_core_message.size(); +} + +/** + * Template arguments: + * A: struct holding function arguments + * T: odk deserialize output/kdo serialize input structure + * F: odk deserialize function + * G: kdo serialize function + * + * raw bytes -> F deserialize -> struct T -> G serialize -> raw bytes + */ +template +void odk_kdo(const F& odk_fun, const G& kdo_fun, const uint8_t* in, + const size_t size, const size_t args_size, uint8_t* out) { + T t = {}; + /* Input byte array format: [function arguments][data to parse] */ + if (size < args_size) { + return; + } + const uint8_t* buf = in + args_size; + std::shared_ptr _args(new A()); + A* args = _args.get(); + memcpy(args, in, args_size); + args->nonce_values.api_major_version = ODK_MAJOR_VERSION; + ODK_NonceValues nonce_values = args->nonce_values; + + OEMCryptoResult result = + odk_fun(buf, size - args_size, args, &nonce_values, &t); + if (result != OEMCrypto_SUCCESS) { + return; + } + std::string oemcrypto_core_message; + if (!kdo_fun(args, t, &oemcrypto_core_message)) { + return; + } +} + +/** + * Template arguments: + * T: kdo deserialize output/odk serialize input structure + * F: kdo deserialize function + * G: odk serialize function + * + * raw bytes -> F deserialize -> struct T -> G serialize -> raw bytes + */ +template +static void kdo_odk(const F& kdo_fun, const G& odk_fun, const uint8_t* in, + size_t size, const size_t clock_value_size, uint8_t* out) { + if (size <= clock_value_size) { + return; + } + /* Input byte array format: [Clock Values][data to parse]. */ + /* Only Renewal Request expects clock values to be present. */ + std::string input(reinterpret_cast(in) + clock_value_size, + size - clock_value_size); + T t = {}; + if (!kdo_fun(input, &t)) { + return; + } + ODK_NonceValues nonce_values = {t.api_minor_version, t.api_major_version, + t.nonce, t.session_id}; + OEMCryptoResult err = odk_fun(in, out, &size, t, &nonce_values); + if (OEMCrypto_SUCCESS != err) { + return; + } +} +} /* namespace oemcrypto_core_message */ +#endif /* WIDEVINE_ODK_TEST_FUZZING_ODK_FUZZ_HELPER_H_ */ diff --git a/oemcrypto/odk/test/fuzzing/odk_fuzz_structs.h b/oemcrypto/odk/test/fuzzing/odk_fuzz_structs.h new file mode 100644 index 0000000..c88de19 --- /dev/null +++ b/oemcrypto/odk/test/fuzzing/odk_fuzz_structs.h @@ -0,0 +1,28 @@ +/* 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 WIDEVINE_ODK_TEST_FUZZING_ODK_FUZZ_STRUCTS_H_ +#define WIDEVINE_ODK_TEST_FUZZING_ODK_FUZZ_STRUCTS_H_ + +#include "odk_structs.h" + +struct ODK_ParseLicense_Args { + ODK_NonceValues nonce_values; + uint8_t initial_license_load; + uint8_t usage_entry_present; + uint8_t request_hash[ODK_SHA256_HASH_SIZE]; + ODK_TimerLimits timer_limits; + ODK_ClockValues clock_values; +}; +struct ODK_ParseRenewal_Args { + ODK_NonceValues nonce_values; + uint64_t system_time; + ODK_TimerLimits timer_limits; + ODK_ClockValues clock_values; +}; +struct ODK_ParseProvisioning_Args { + ODK_NonceValues nonce_values; + size_t device_id_length; + uint8_t device_id[64]; +}; +#endif /* WIDEVINE_ODK_TEST_FUZZING_ODK_FUZZ_STRUCTS_H_ */ diff --git a/oemcrypto/odk/test/fuzzing/odk_license_request_fuzz.cpp b/oemcrypto/odk/test/fuzzing/odk_license_request_fuzz.cpp new file mode 100644 index 0000000..463c604 --- /dev/null +++ b/oemcrypto/odk/test/fuzzing/odk_license_request_fuzz.cpp @@ -0,0 +1,22 @@ +/* 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 + +#include "core_message_deserialize.h" +#include "fuzzing/odk_fuzz_helper.h" + +namespace oemcrypto_core_message { +using oemcrypto_core_message::deserialize::CoreLicenseRequestFromMessage; + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + std::vector out(size); + const size_t kClockValueSize = 0; + kdo_odk(CoreLicenseRequestFromMessage, + odk_serialize_LicenseRequest, data, size, + kClockValueSize, out.data()); + return 0; +} +} // namespace oemcrypto_core_message diff --git a/oemcrypto/odk/test/fuzzing/odk_license_response_fuzz.cpp b/oemcrypto/odk/test/fuzzing/odk_license_response_fuzz.cpp new file mode 100644 index 0000000..12398fd --- /dev/null +++ b/oemcrypto/odk/test/fuzzing/odk_license_response_fuzz.cpp @@ -0,0 +1,20 @@ +/* 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 + +#include "fuzzing/odk_fuzz_helper.h" + +namespace oemcrypto_core_message { + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + const size_t kLicenseResponseArgsSize = sizeof(ODK_ParseLicense_Args); + std::vector out(size); + odk_kdo( + odk_deserialize_LicenseResponse, kdo_serialize_LicenseResponse, data, + size, kLicenseResponseArgsSize, out.data()); + return 0; +} +} // namespace oemcrypto_core_message diff --git a/oemcrypto/odk/test/fuzzing/odk_license_response_fuzz_with_mutator.cpp b/oemcrypto/odk/test/fuzzing/odk_license_response_fuzz_with_mutator.cpp new file mode 100644 index 0000000..80c8ff3 --- /dev/null +++ b/oemcrypto/odk/test/fuzzing/odk_license_response_fuzz_with_mutator.cpp @@ -0,0 +1,35 @@ +/* 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 + +#include "fuzzing/odk_fuzz_helper.h" + +namespace oemcrypto_core_message { + +// The custom mutator: Ensure that each input can be deserialized properly +// by ODK function after mutation. +extern "C" size_t LLVMFuzzerCustomMutator(uint8_t* data, size_t size, + size_t max_size, unsigned int seed) { + const size_t kLicenseResponseArgsSize = sizeof(ODK_ParseLicense_Args); + if (size < kLicenseResponseArgsSize) { + return 0; + } + + // Mutate input data and return mutated input size. + return FuzzerMutateResponse( + data, size, max_size, odk_deserialize_LicenseResponse, + kdo_serialize_LicenseResponse); +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + const size_t kLicenseResponseArgsSize = sizeof(ODK_ParseLicense_Args); + std::vector out(size); + odk_kdo( + odk_deserialize_LicenseResponse, kdo_serialize_LicenseResponse, data, + size, kLicenseResponseArgsSize, out.data()); + return 0; +} +} // namespace oemcrypto_core_message diff --git a/oemcrypto/odk/test/fuzzing/odk_provisioning_request_fuzz.cpp b/oemcrypto/odk/test/fuzzing/odk_provisioning_request_fuzz.cpp new file mode 100644 index 0000000..984534e --- /dev/null +++ b/oemcrypto/odk/test/fuzzing/odk_provisioning_request_fuzz.cpp @@ -0,0 +1,22 @@ +/* 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 + +#include "core_message_deserialize.h" +#include "fuzzing/odk_fuzz_helper.h" + +namespace oemcrypto_core_message { +using oemcrypto_core_message::deserialize::CoreProvisioningRequestFromMessage; + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + std::vector out(size); + const size_t kClockValueSize = 0; + kdo_odk(CoreProvisioningRequestFromMessage, + odk_serialize_ProvisioningRequest, data, + size, kClockValueSize, out.data()); + return 0; +} +} // namespace oemcrypto_core_message diff --git a/oemcrypto/odk/test/fuzzing/odk_provisioning_response_fuzz.cpp b/oemcrypto/odk/test/fuzzing/odk_provisioning_response_fuzz.cpp new file mode 100644 index 0000000..90dc017 --- /dev/null +++ b/oemcrypto/odk/test/fuzzing/odk_provisioning_response_fuzz.cpp @@ -0,0 +1,21 @@ +/* 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 + +#include "fuzzing/odk_fuzz_helper.h" + +namespace oemcrypto_core_message { + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + const size_t kProvisioningResponseArgsSize = + sizeof(ODK_ParseProvisioning_Args); + std::vector out(size); + odk_kdo( + odk_deserialize_ProvisioningResponse, kdo_serialize_ProvisioningResponse, + data, size, kProvisioningResponseArgsSize, out.data()); + return 0; +} +} // namespace oemcrypto_core_message diff --git a/oemcrypto/odk/test/fuzzing/odk_provisioning_response_fuzz_with_mutator.cpp b/oemcrypto/odk/test/fuzzing/odk_provisioning_response_fuzz_with_mutator.cpp new file mode 100644 index 0000000..17787a4 --- /dev/null +++ b/oemcrypto/odk/test/fuzzing/odk_provisioning_response_fuzz_with_mutator.cpp @@ -0,0 +1,38 @@ +/* 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 + +#include "fuzzing/odk_fuzz_helper.h" + +namespace oemcrypto_core_message { + +// The custom mutator: Ensure that each input can be deserialized properly +// by ODK function after mutation. +extern "C" size_t LLVMFuzzerCustomMutator(uint8_t* data, size_t size, + size_t max_size, unsigned int seed) { + const size_t kProvisioningResponseArgsSize = + sizeof(ODK_ParseProvisioning_Args); + if (size < kProvisioningResponseArgsSize) { + return 0; + } + + // Mutate input data and return mutated input size. + return FuzzerMutateResponse( + data, size, max_size, odk_deserialize_ProvisioningResponse, + kdo_serialize_ProvisioningResponse); +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + const size_t kProvisioningResponseArgsSize = + sizeof(ODK_ParseProvisioning_Args); + std::vector out(size); + odk_kdo( + odk_deserialize_ProvisioningResponse, kdo_serialize_ProvisioningResponse, + data, size, kProvisioningResponseArgsSize, out.data()); + return 0; +} +} // namespace oemcrypto_core_message diff --git a/oemcrypto/odk/test/fuzzing/odk_renewal_request_fuzz.cpp b/oemcrypto/odk/test/fuzzing/odk_renewal_request_fuzz.cpp new file mode 100644 index 0000000..602b37a --- /dev/null +++ b/oemcrypto/odk/test/fuzzing/odk_renewal_request_fuzz.cpp @@ -0,0 +1,22 @@ +/* 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 + +#include "core_message_deserialize.h" +#include "fuzzing/odk_fuzz_helper.h" + +namespace oemcrypto_core_message { +using oemcrypto_core_message::deserialize::CoreRenewalRequestFromMessage; + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + std::vector out(size); + const size_t kClockValueSize = sizeof(ODK_ClockValues); + kdo_odk(CoreRenewalRequestFromMessage, + odk_serialize_RenewalRequest, data, size, + kClockValueSize, out.data()); + return 0; +} +} // namespace oemcrypto_core_message diff --git a/oemcrypto/odk/test/fuzzing/odk_renewal_response_fuzz.cpp b/oemcrypto/odk/test/fuzzing/odk_renewal_response_fuzz.cpp new file mode 100644 index 0000000..8d66908 --- /dev/null +++ b/oemcrypto/odk/test/fuzzing/odk_renewal_response_fuzz.cpp @@ -0,0 +1,20 @@ +/* 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 + +#include "fuzzing/odk_fuzz_helper.h" + +namespace oemcrypto_core_message { + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + const size_t kRenewalResponseArgsSize = sizeof(ODK_ParseRenewal_Args); + std::vector out(size); + odk_kdo( + odk_deserialize_RenewalResponse, kdo_serialize_RenewalResponse, data, + size, kRenewalResponseArgsSize, out.data()); + return 0; +} +} // namespace oemcrypto_core_message diff --git a/oemcrypto/odk/test/fuzzing/odk_renewal_response_fuzz_with_mutator.cpp b/oemcrypto/odk/test/fuzzing/odk_renewal_response_fuzz_with_mutator.cpp new file mode 100644 index 0000000..0073c4e --- /dev/null +++ b/oemcrypto/odk/test/fuzzing/odk_renewal_response_fuzz_with_mutator.cpp @@ -0,0 +1,36 @@ +/* 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 + +#include "fuzzing/odk_fuzz_helper.h" + +namespace oemcrypto_core_message { + +// The custom mutator: Ensure that each input can be deserialized properly +// by ODK function after mutation. +extern "C" size_t LLVMFuzzerCustomMutator(uint8_t* data, size_t size, + size_t max_size, unsigned int seed) { + const size_t kRenewalResponseArgsSize = sizeof(ODK_ParseRenewal_Args); + if (size < kRenewalResponseArgsSize) { + return 0; + } + + // Mutate input data and return mutated input size. + return FuzzerMutateResponse( + data, size, max_size, odk_deserialize_RenewalResponse, + kdo_serialize_RenewalResponse); +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + const size_t kRenewalResponseArgsSize = sizeof(ODK_ParseRenewal_Args); + std::vector out(size); + odk_kdo( + odk_deserialize_RenewalResponse, kdo_serialize_RenewalResponse, data, + size, kRenewalResponseArgsSize, out.data()); + return 0; +} +} // namespace oemcrypto_core_message diff --git a/oemcrypto/odk/test/odk_core_message_test.cpp b/oemcrypto/odk/test/odk_core_message_test.cpp new file mode 100644 index 0000000..c824759 --- /dev/null +++ b/oemcrypto/odk/test/odk_core_message_test.cpp @@ -0,0 +1,37 @@ +// 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 "OEMCryptoCENCCommon.h" +#include "gtest/gtest.h" +#include "odk.h" +#include "third_party/absl/strings/escaping.h" + +namespace wvodk_test { +TEST(CoreMessageTest, RenwalRequest) { + std::string oem = + "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrst" + "uvwxyzabcdefghijklmnopqrstuvwxyz"; + const uint8_t* buf = reinterpret_cast(oem.c_str()); + uint8_t* message = const_cast(buf); + size_t message_length = 88; + size_t core_message_length = 88; + uint16_t api_minor_version = 16; + uint16_t api_major_version = 16; + uint32_t nonce = 0; + uint32_t timer_status = 2; + uint64_t time = 10; + enum OEMCrypto_Usage_Entry_Status status = kInactiveUsed; + ODK_NonceValues nonce_values{api_minor_version, api_major_version, nonce}; + ODK_ClockValues clock_values{time, time, time, time, + time, timer_status, status}; + uint64_t system_time_seconds = 100; + EXPECT_EQ(OEMCrypto_SUCCESS, + ODK_PrepareCoreRenewalRequest(message, message_length, + &core_message_length, &nonce_values, + &clock_values, system_time_seconds)); + // All messages have at least a five 4-byte fields. + char* m = reinterpret_cast(message); + VLOG(0) << absl::BytesToHexString(std::string(m, core_message_length)); +} +} // namespace wvodk_test diff --git a/oemcrypto/odk/test/odk_test.cpp b/oemcrypto/odk/test/odk_test.cpp new file mode 100644 index 0000000..4e24895 --- /dev/null +++ b/oemcrypto/odk/test/odk_test.cpp @@ -0,0 +1,729 @@ +// 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. + +#include "odk.h" + +#include // TODO(b/147944591): use this one? Or odk_endian.h? + +#include +#include + +#include "OEMCryptoCENCCommon.h" +#include "core_message_deserialize.h" +#include "core_message_serialize.h" +#include "core_message_types.h" +#include "gtest/gtest.h" +#include "odk_structs_priv.h" +#include "odk_test_helper.h" + +namespace wvodk_test { + +namespace { + +using oemcrypto_core_message::ODK_LicenseRequest; +using oemcrypto_core_message::ODK_ProvisioningRequest; +using oemcrypto_core_message::ODK_RenewalRequest; + +using oemcrypto_core_message::deserialize::CoreLicenseRequestFromMessage; +using oemcrypto_core_message::deserialize::CoreProvisioningRequestFromMessage; +using oemcrypto_core_message::deserialize::CoreRenewalRequestFromMessage; + +using oemcrypto_core_message::serialize::CreateCoreLicenseResponse; +using oemcrypto_core_message::serialize::CreateCoreProvisioningResponse; +using oemcrypto_core_message::serialize::CreateCoreRenewalResponse; + +constexpr uint32_t kExtraPayloadSize = 128u; + +template +void ValidateRequest(uint32_t message_type, + const std::vector& extra_fields, + const F& odk_prepare_func, const G& kdo_parse_func) { + uint32_t message_size = 0; + uint16_t api_major_version = ODK_MAJOR_VERSION; + uint16_t api_minor_version = ODK_MINOR_VERSION; + uint32_t nonce = 0xdeadbeef; + uint32_t session_id = 0xcafebabe; + ODK_NonceValues nonce_values{api_minor_version, api_major_version, nonce, + session_id}; + std::vector total_fields = { + {ODK_UINT32, &message_type, "message_type"}, + {ODK_UINT32, &message_size, "message_size"}, + {ODK_UINT16, &api_minor_version, "api_minor_version"}, + {ODK_UINT16, &api_major_version, "api_major_version"}, + {ODK_UINT32, &nonce, "nonce"}, + {ODK_UINT32, &session_id, "session_id"}, + }; + + total_fields.insert(total_fields.end(), extra_fields.begin(), + extra_fields.end()); + for (auto& field : total_fields) { + message_size += ODK_FieldLength(field.type); + } + + // empty buf, expect core message length to be set correctly + size_t core_message_length = 0; + uint8_t* buf_empty = nullptr; + EXPECT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, + odk_prepare_func(buf_empty, &core_message_length, &nonce_values)); + EXPECT_EQ(core_message_length, message_size); + + // non-empty buf, expect core message length to be set correctly, and buf is + // filled with ODK_Field values appropriately + uint8_t* buf = new uint8_t[message_size]{}; + EXPECT_EQ(OEMCrypto_SUCCESS, + odk_prepare_func(buf, &core_message_length, &nonce_values)); + EXPECT_EQ(core_message_length, message_size); + + uint8_t* buf_expected = new uint8_t[message_size]{}; + size_t buf_len_expected = 0; + EXPECT_EQ(OEMCrypto_SUCCESS, ODK_IterFields(ODK_WRITE, buf_expected, SIZE_MAX, + &buf_len_expected, total_fields)); + EXPECT_EQ(buf_len_expected, message_size); + + EXPECT_NO_FATAL_FAILURE( + ODK_ExpectEqualBuf(buf_expected, buf, message_size, total_fields)); + + // odk kdo round-trip: deserialize from buf, then serialize it to buf2 + // expect them to be identical + T t = {}; + std::string oemcrypto_core_message(reinterpret_cast(buf), + message_size); + EXPECT_TRUE(kdo_parse_func(oemcrypto_core_message, &t)); + nonce_values.api_minor_version = t.api_minor_version; + nonce_values.api_major_version = t.api_major_version; + nonce_values.nonce = t.nonce; + nonce_values.session_id = t.session_id; + uint8_t* buf2 = new uint8_t[message_size]{}; + EXPECT_EQ(OEMCrypto_SUCCESS, + odk_prepare_func(buf2, &core_message_length, &nonce_values)); + EXPECT_EQ(core_message_length, message_size); + EXPECT_NO_FATAL_FAILURE( + ODK_ExpectEqualBuf(buf, buf2, message_size, total_fields)); + + delete[] buf; + delete[] buf_expected; + delete[] buf2; +} + +/** + * Template arguments: + * T: kdo input struct + * F: odk deserializer + * G: kdo serializer + */ +template +void ValidateResponse(ODK_CoreMessage* core_message, + const std::vector& extra_fields, + const F& odk_parse_func, const G& kdo_prepare_func) { + T t = {}; + t.api_minor_version = core_message->nonce_values.api_minor_version; + t.api_major_version = core_message->nonce_values.api_major_version; + t.nonce = core_message->nonce_values.nonce; + t.session_id = core_message->nonce_values.session_id; + + uint8_t* buf = nullptr; + uint32_t buf_size = 0; + ODK_BuildMessageBuffer(core_message, extra_fields, &buf, &buf_size); + + uint8_t* zero = new uint8_t[buf_size]{}; + size_t bytes_read = 0; + // zero-out input + EXPECT_EQ(OEMCrypto_SUCCESS, ODK_IterFields(ODK_READ, zero, buf_size, + &bytes_read, extra_fields)); + + // parse buf with odk + EXPECT_EQ(OEMCrypto_SUCCESS, odk_parse_func(buf, buf_size)); + + size_t size_out = 0; + ODK_IterFields(ODK_FieldMode::ODK_DUMP, buf, buf_size, &size_out, + extra_fields); + + // serialize odk output to oemcrypto_core_message + std::string oemcrypto_core_message; + EXPECT_TRUE(kdo_prepare_func(t, &oemcrypto_core_message)); + + // verify round-trip works + EXPECT_NO_FATAL_FAILURE(ODK_ExpectEqualBuf(buf, oemcrypto_core_message.data(), + buf_size, extra_fields)); + delete[] buf; + delete[] zero; +} + +TEST(OdkTest, SerializeFields) { + uint32_t x[] = {0, 1, 2}; + uint64_t y[] = {3LL << 32, 4LL << 32, 5LL << 32}; + OEMCrypto_Substring s = {.offset = 6, .length = 7}; + std::vector fields = { + {ODK_UINT32, &x[0], "x[0]"}, {ODK_UINT32, &x[1], "x[1]"}, + {ODK_UINT32, &x[2], "x[2]"}, {ODK_UINT64, &y[0], "y[0]"}, + {ODK_UINT64, &y[1], "y[1]"}, {ODK_UINT64, &y[2], "y[2]"}, + {ODK_SUBSTRING, &s, "s"}, + }; + uint8_t buf[1024] = {0}; + uint8_t buf2[1024] = {0}; + size_t bytes_read = 0, bytes_written = 0; + ODK_IterFields(ODK_WRITE, buf, SIZE_MAX, &bytes_read, fields); + std::vector fields2(fields.size()); + ODK_ResetOdkFields(&fields); + ODK_IterFields(ODK_READ, buf, bytes_read, &bytes_written, fields); + ODK_IterFields(ODK_WRITE, buf2, SIZE_MAX, &bytes_read, fields); + + EXPECT_NO_FATAL_FAILURE(ODK_ExpectEqualBuf(buf, buf2, bytes_read, fields)); +} + +TEST(OdkTest, SerializeFieldsStress) { + const int n = 1024; + std::vector fields(n); + std::srand(0); + size_t total_size = 0; + for (int i = 0; i < n; i++) { + fields[i].type = static_cast(std::rand() % + static_cast(ODK_NUMTYPES)); + fields[i].value = malloc(ODK_AllocSize(fields[i].type)); + fields[i].name = "stress"; + total_size += ODK_FieldLength(fields[i].type); + } + + uint8_t* buf = new uint8_t[total_size]{}; + for (int i = 0; i < total_size; i++) { + buf[i] = std::rand() & 0xff; + } + + size_t bytes_read = 0, bytes_written = 0; + uint8_t* buf2 = new uint8_t[total_size]{}; + ODK_IterFields(ODK_READ, buf, total_size, &bytes_read, fields); + EXPECT_EQ(bytes_read, total_size); + ODK_IterFields(ODK_WRITE, buf2, total_size, &bytes_written, fields); + EXPECT_EQ(bytes_written, total_size); + + EXPECT_NO_FATAL_FAILURE(ODK_ExpectEqualBuf(buf, buf2, total_size, fields)); + + // cleanup + for (int i = 0; i < n; i++) { + free(fields[i].value); + } + delete[] buf; + delete[] buf2; +} + +TEST(OdkTest, NullRequestTest) { + size_t core_message_length = 0; + ODK_NonceValues nonce_values{0}; + ODK_ClockValues clock_values{0}; + + // Assert that nullptr does not cause a core dump. + EXPECT_EQ(ODK_ERROR_CORE_MESSAGE, ODK_PrepareCoreLicenseRequest( + nullptr, 0uL, nullptr, &nonce_values)); + EXPECT_EQ(ODK_ERROR_CORE_MESSAGE, + ODK_PrepareCoreLicenseRequest(nullptr, 0uL, &core_message_length, + nullptr)); + + EXPECT_EQ(ODK_ERROR_CORE_MESSAGE, + ODK_PrepareCoreRenewalRequest(nullptr, 0uL, nullptr, &nonce_values, + &clock_values, 0uL)); + EXPECT_EQ(ODK_ERROR_CORE_MESSAGE, + ODK_PrepareCoreRenewalRequest(nullptr, 0uL, &core_message_length, + nullptr, &clock_values, 0uL)); + EXPECT_EQ(ODK_ERROR_CORE_MESSAGE, + ODK_PrepareCoreRenewalRequest(nullptr, 0uL, &core_message_length, + &nonce_values, nullptr, 0uL)); + + EXPECT_EQ(ODK_ERROR_CORE_MESSAGE, + ODK_PrepareCoreProvisioningRequest( + nullptr, 0uL, &core_message_length, nullptr, nullptr, 0uL)); + EXPECT_EQ(ODK_ERROR_CORE_MESSAGE, + ODK_PrepareCoreProvisioningRequest(nullptr, 0uL, nullptr, + &nonce_values, nullptr, 0uL)); + + // Null device id in provisioning request is ok + uint8_t message[ODK_PROVISIONING_REQUEST_SIZE] = {0}; + core_message_length = ODK_PROVISIONING_REQUEST_SIZE; + EXPECT_EQ(OEMCrypto_SUCCESS, + ODK_PrepareCoreProvisioningRequest( + message, ODK_PROVISIONING_REQUEST_SIZE, &core_message_length, + &nonce_values, nullptr, 0uL)); +} + +TEST(OdkTest, NullResponseTest) { + constexpr size_t message_size = 64; + uint8_t message[message_size] = {0}; + size_t core_message_length = message_size; + uint8_t request_hash[ODK_SHA256_HASH_SIZE] = {0}; + ODK_TimerLimits timer_limits{0}; + ODK_ParsedLicense parsed_license; + ODK_NonceValues nonce_values{0}; + ODK_ClockValues clock_values{0}; + + // Assert that nullptr does not cause a core dump. + EXPECT_EQ(ODK_ERROR_CORE_MESSAGE, + ODK_ParseLicense(message, message_size, core_message_length, true, + true, request_hash, &timer_limits, &clock_values, + &nonce_values, nullptr)); + EXPECT_EQ(ODK_ERROR_CORE_MESSAGE, + ODK_ParseLicense(message, message_size, core_message_length, true, + true, request_hash, &timer_limits, &clock_values, + nullptr, &parsed_license)); + EXPECT_EQ(ODK_ERROR_CORE_MESSAGE, + ODK_ParseLicense(message, message_size, core_message_length, true, + true, request_hash, &timer_limits, nullptr, + &nonce_values, &parsed_license)); + EXPECT_EQ(ODK_ERROR_CORE_MESSAGE, + ODK_ParseLicense(message, message_size, core_message_length, true, + true, request_hash, nullptr, &clock_values, + &nonce_values, &parsed_license)); + EXPECT_EQ(ODK_ERROR_CORE_MESSAGE, + ODK_ParseLicense(message, message_size, core_message_length, true, + true, nullptr, &timer_limits, &clock_values, + &nonce_values, &parsed_license)); + EXPECT_EQ(ODK_ERROR_CORE_MESSAGE, + ODK_ParseLicense(nullptr, message_size, core_message_length, true, + true, request_hash, &timer_limits, &clock_values, + &nonce_values, &parsed_license)); + + constexpr uint64_t system_time = 0; + uint64_t timer_value = 0; + EXPECT_EQ(ODK_ERROR_CORE_MESSAGE, + ODK_ParseRenewal(message, message_size, core_message_length, + &nonce_values, system_time, &timer_limits, nullptr, + &timer_value)); + EXPECT_EQ(ODK_ERROR_CORE_MESSAGE, + ODK_ParseRenewal(message, message_size, core_message_length, + &nonce_values, system_time, nullptr, &clock_values, + &timer_value)); + EXPECT_EQ(ODK_ERROR_CORE_MESSAGE, + ODK_ParseRenewal(message, message_size, core_message_length, + nullptr, system_time, &timer_limits, &clock_values, + &timer_value)); + EXPECT_EQ(ODK_ERROR_CORE_MESSAGE, + ODK_ParseRenewal(nullptr, message_size, core_message_length, + &nonce_values, system_time, &timer_limits, + &clock_values, &timer_value)); + + uint8_t device_id[ODK_DEVICE_ID_LEN_MAX] = {0}; + ODK_ParsedProvisioning parsed_response; + EXPECT_EQ(ODK_ERROR_CORE_MESSAGE, + ODK_ParseProvisioning(message, message_size, core_message_length, + &nonce_values, device_id, + ODK_DEVICE_ID_LEN_MAX, nullptr)); + EXPECT_EQ(ODK_ERROR_CORE_MESSAGE, + ODK_ParseProvisioning(message, message_size, core_message_length, + &nonce_values, nullptr, 0, &parsed_response)); + EXPECT_EQ(ODK_ERROR_CORE_MESSAGE, + ODK_ParseProvisioning(message, message_size, core_message_length, + nullptr, device_id, ODK_DEVICE_ID_LEN_MAX, + &parsed_response)); + EXPECT_EQ(ODK_ERROR_CORE_MESSAGE, + ODK_ParseProvisioning(nullptr, message_size, core_message_length, + &nonce_values, device_id, + ODK_DEVICE_ID_LEN_MAX, &parsed_response)); +} + +TEST(OdkTest, PrepareCoreLicenseRequest) { + uint8_t license_message[ODK_LICENSE_REQUEST_SIZE] = {0}; + size_t core_message_length = sizeof(license_message); + ODK_NonceValues nonce_values{0}; + EXPECT_EQ(OEMCrypto_SUCCESS, ODK_PrepareCoreLicenseRequest( + license_message, sizeof(license_message), + &core_message_length, &nonce_values)); +} + +TEST(OdkTest, PrepareCoreLicenseRequestSize) { + uint8_t license_message[ODK_LICENSE_REQUEST_SIZE] = {0}; + size_t core_message_length = sizeof(license_message); + ODK_NonceValues nonce_values{0}; + // message length smaller than core message length + size_t core_message_length_invalid = core_message_length + 1; + EXPECT_EQ(ODK_ERROR_CORE_MESSAGE, + ODK_PrepareCoreLicenseRequest( + license_message, sizeof(license_message), + &core_message_length_invalid, &nonce_values)); + // message length larger than core message length + uint8_t license_message_large[ODK_LICENSE_REQUEST_SIZE * 2] = {0}; + EXPECT_EQ(OEMCrypto_SUCCESS, + ODK_PrepareCoreLicenseRequest(license_message_large, + sizeof(license_message_large), + &core_message_length, &nonce_values)); +} + +TEST(OdkTest, PrepareCoreRenewalRequest) { + uint8_t renewal_message[ODK_RENEWAL_REQUEST_SIZE] = {0}; + size_t core_message_length = sizeof(renewal_message); + ODK_NonceValues nonce_values{0}; + ODK_ClockValues clock_values{0}; + constexpr uint64_t system_time_seconds = 10; + EXPECT_EQ(OEMCrypto_SUCCESS, + ODK_PrepareCoreRenewalRequest( + renewal_message, sizeof(renewal_message), &core_message_length, + &nonce_values, &clock_values, system_time_seconds)); +} + +TEST(OdkTest, PrepareCoreRenewalRequestTimer) { + uint8_t renewal_message[ODK_RENEWAL_REQUEST_SIZE] = {0}; + size_t core_message_length = sizeof(renewal_message); + ODK_NonceValues nonce_values{2, 16, 0, 0}; + constexpr uint64_t system_time_seconds = 10; + ODK_ClockValues clock_values_updated{0}; + // system time smaller than first decrypt time + clock_values_updated.time_of_first_decrypt = system_time_seconds + 1; + EXPECT_EQ(ODK_ERROR_CORE_MESSAGE, + ODK_PrepareCoreRenewalRequest( + renewal_message, sizeof(renewal_message), &core_message_length, + &nonce_values, &clock_values_updated, system_time_seconds)); + clock_values_updated.time_of_first_decrypt = system_time_seconds - 1; + EXPECT_EQ(OEMCrypto_SUCCESS, + ODK_PrepareCoreRenewalRequest( + renewal_message, sizeof(renewal_message), &core_message_length, + &nonce_values, &clock_values_updated, system_time_seconds)); + // clock_values.time_of_renewal_request should get updated + EXPECT_EQ(system_time_seconds - clock_values_updated.time_of_first_decrypt, + clock_values_updated.time_of_renewal_request); +} + +TEST(OdkTest, PrepareCoreProvisioningRequest) { + uint8_t provisioning_message[ODK_PROVISIONING_REQUEST_SIZE] = {0}; + size_t core_message_length = sizeof(provisioning_message); + ODK_NonceValues nonce_values{0}; + uint8_t device_id[ODK_DEVICE_ID_LEN_MAX] = {0}; + EXPECT_EQ( + OEMCrypto_SUCCESS, + ODK_PrepareCoreProvisioningRequest( + provisioning_message, sizeof(provisioning_message), + &core_message_length, &nonce_values, device_id, sizeof(device_id))); +} + +TEST(OdkTest, PrepareCoreProvisioningRequestDeviceId) { + uint8_t provisioning_message[ODK_PROVISIONING_REQUEST_SIZE] = {0}; + size_t core_message_length = sizeof(provisioning_message); + ODK_NonceValues nonce_values{0}; + uint8_t device_id_invalid[ODK_DEVICE_ID_LEN_MAX + 1] = {0}; + EXPECT_EQ(ODK_ERROR_CORE_MESSAGE, + ODK_PrepareCoreProvisioningRequest( + provisioning_message, sizeof(provisioning_message), + &core_message_length, &nonce_values, device_id_invalid, + sizeof(device_id_invalid))); +} + +// Serialize and de-serialize license request +TEST(OdkTest, LicenseRequestRoundtrip) { + std::vector empty; + auto odk_prepare_func = [&](uint8_t* const buf, size_t* size, + ODK_NonceValues* nonce_values) { + return ODK_PrepareCoreLicenseRequest(buf, SIZE_MAX, size, nonce_values); + }; + auto kdo_parse_func = CoreLicenseRequestFromMessage; + ValidateRequest(ODK_License_Request_Type, empty, + odk_prepare_func, kdo_parse_func); +} + +TEST(OdkTest, RenewalRequestRoundtrip) { + constexpr uint64_t system_time_seconds = 0xBADDCAFE000FF1CE; + uint64_t playback_time = 0xCAFE00000000; + const uint64_t playback_start = system_time_seconds - playback_time; + const std::vector extra_fields = { + {ODK_UINT64, &playback_time, "playback_time"}, + }; + ODK_ClockValues clock_values = {0}; + clock_values.time_of_first_decrypt = playback_start; + auto odk_prepare_func = [&](uint8_t* const buf, size_t* size, + ODK_NonceValues* nonce_values) { + return ODK_PrepareCoreRenewalRequest(buf, SIZE_MAX, size, nonce_values, + &clock_values, system_time_seconds); + }; + auto kdo_parse_func = [&](const std::string& oemcrypto_core_message, + ODK_RenewalRequest* core_renewal_request) { + bool ok = CoreRenewalRequestFromMessage(oemcrypto_core_message, + core_renewal_request); + return ok; + }; + ValidateRequest(ODK_Renewal_Request_Type, extra_fields, + odk_prepare_func, kdo_parse_func); +} + +TEST(OdkTest, ProvisionRequestRoundtrip) { + uint32_t device_id_length = ODK_DEVICE_ID_LEN_MAX / 2; + uint8_t device_id[ODK_DEVICE_ID_LEN_MAX] = {0}; + memset(device_id, 0xff, device_id_length); + std::vector extra_fields = { + {ODK_UINT32, &device_id_length, "device_id_length"}, + {ODK_DEVICEID, device_id, "device_id"}, + }; + auto odk_prepare_func = [&](uint8_t* const buf, size_t* size, + const ODK_NonceValues* nonce_values) { + return ODK_PrepareCoreProvisioningRequest(buf, SIZE_MAX, size, nonce_values, + device_id, device_id_length); + }; + auto kdo_parse_func = + [&](const std::string& oemcrypto_core_message, + ODK_ProvisioningRequest* core_provisioning_request) { + bool ok = CoreProvisioningRequestFromMessage(oemcrypto_core_message, + core_provisioning_request); + return ok; + }; + ValidateRequest(ODK_Provisioning_Request_Type, + extra_fields, odk_prepare_func, + kdo_parse_func); +} + +TEST(OdkTest, ParseLicenseErrorNonce) { + ODK_LicenseResponseParams params; + ODK_SetDefaultLicenseResponseParams(¶ms); + uint8_t* buf = nullptr; + uint32_t buf_size = 0; + ODK_BuildMessageBuffer(&(params.core_message), params.extra_fields, &buf, + &buf_size); + // temporarily mess up with nonce + params.core_message.nonce_values.nonce = 0; + OEMCryptoResult err = ODK_ParseLicense( + buf, buf_size + kExtraPayloadSize, buf_size, params.initial_license_load, + params.usage_entry_present, params.request_hash, &(params.timer_limits), + &(params.clock_values), &(params.core_message.nonce_values), + &(params.parsed_license)); + EXPECT_EQ(OEMCrypto_ERROR_INVALID_NONCE, err); + delete[] buf; +} + +TEST(OdkTest, ParseLicenseErrorUsageEntry) { + ODK_LicenseResponseParams params; + ODK_SetDefaultLicenseResponseParams(¶ms); + uint8_t* buf = nullptr; + uint32_t buf_size = 0; + ODK_BuildMessageBuffer(&(params.core_message), params.extra_fields, &buf, + &buf_size); + params.usage_entry_present = false; + OEMCryptoResult err = ODK_ParseLicense( + buf, buf_size + kExtraPayloadSize, buf_size, params.initial_license_load, + params.usage_entry_present, params.request_hash, &(params.timer_limits), + &(params.clock_values), &(params.core_message.nonce_values), + &(params.parsed_license)); + EXPECT_EQ(ODK_ERROR_CORE_MESSAGE, err); + delete[] buf; +} + +TEST(OdkTest, ParseLicenseErrorRequestHash) { + ODK_LicenseResponseParams params; + ODK_SetDefaultLicenseResponseParams(¶ms); + uint8_t* buf = nullptr; + uint32_t buf_size = 0; + ODK_BuildMessageBuffer(&(params.core_message), params.extra_fields, &buf, + &buf_size); + // temporarily mess up with request hash + params.request_hash[0] = 0xff; + OEMCryptoResult err = ODK_ParseLicense( + buf, buf_size + kExtraPayloadSize, buf_size, params.initial_license_load, + params.usage_entry_present, params.request_hash, &(params.timer_limits), + &(params.clock_values), &(params.core_message.nonce_values), + &(params.parsed_license)); + EXPECT_EQ(ODK_ERROR_CORE_MESSAGE, err); + delete[] buf; +} + +TEST(OdkTest, ParseRenewalErrorTimer) { + ODK_RenewalResponseParams params; + ODK_SetDefaultRenewalResponseParams(¶ms); + uint8_t* buf = nullptr; + uint32_t buf_size = 0; + ODK_BuildMessageBuffer(&(params.core_message), params.extra_fields, &buf, + &buf_size); + params.clock_values.time_of_renewal_request = 0; + OEMCryptoResult err = ODK_ParseRenewal( + buf, buf_size, buf_size, &(params.core_message.nonce_values), + params.system_time, &(params.timer_limits), &(params.clock_values), + &(params.playback_timer)); + EXPECT_EQ(ODK_STALE_RENEWAL, err); + delete[] buf; +} + +TEST(OdkTest, ParsePrivisioningErrorDeviceId) { + ODK_ProvisioningResponseParams params; + ODK_SetDefaultProvisioningResponseParams(¶ms); + uint8_t* buf = nullptr; + uint32_t buf_size = 0; + ODK_BuildMessageBuffer(&(params.core_message), params.extra_fields, &buf, + &buf_size); + // temporarily mess up with device_id + params.device_id[0] = 0; + OEMCryptoResult err = ODK_ParseProvisioning( + buf, buf_size + 16, buf_size, &(params.core_message.nonce_values), + params.device_id, params.device_id_length, &(params.parsed_provisioning)); + EXPECT_EQ(ODK_ERROR_CORE_MESSAGE, err); + delete[] buf; +} + +// Serialize and de-serialize license response +TEST(OdkTest, LicenseResponseRoundtrip) { + ODK_LicenseResponseParams params; + ODK_SetDefaultLicenseResponseParams(¶ms); + // save a copy of params.request_hash as it will be zero out during the test + uint8_t request_hash_read[ODK_SHA256_HASH_SIZE]; + memcpy(request_hash_read, params.request_hash, sizeof(request_hash_read)); + auto odk_parse_func = [&](const uint8_t* buf, size_t size) { + return ODK_ParseLicense( + buf, size + kExtraPayloadSize, size, params.initial_license_load, + params.usage_entry_present, request_hash_read, &(params.timer_limits), + &(params.clock_values), &(params.core_message.nonce_values), + &(params.parsed_license)); + }; + const std::string request_hash_string( + reinterpret_cast(request_hash_read), + sizeof(request_hash_read)); + auto kdo_prepare_func = [&](const ODK_LicenseRequest& core_request, + std::string* oemcrypto_core_message) { + return CreateCoreLicenseResponse(params.parsed_license, core_request, + request_hash_string, + oemcrypto_core_message); + }; + ValidateResponse(&(params.core_message), + params.extra_fields, odk_parse_func, + kdo_prepare_func); +} + +TEST(OdkTest, RenewalResponseRoundtrip) { + ODK_RenewalResponseParams params; + ODK_SetDefaultRenewalResponseParams(¶ms); + const uint64_t playback_clock = params.playback_clock; + const uint64_t renewal_duration = params.renewal_duration; + auto odk_parse_func = [&](const uint8_t* buf, size_t size) { + OEMCryptoResult err = + ODK_ParseRenewal(buf, size, size, &(params.core_message.nonce_values), + params.system_time, &(params.timer_limits), + &(params.clock_values), &(params.playback_timer)); + + EXPECT_EQ(ODK_SET_TIMER, err); + EXPECT_EQ(renewal_duration, params.playback_timer); + EXPECT_EQ(params.clock_values.time_when_timer_expires, + params.system_time + params.playback_timer); + + return OEMCrypto_SUCCESS; + }; + auto kdo_prepare_func = [&](ODK_RenewalRequest& core_request, + std::string* oemcrypto_core_message) { + core_request.playback_time_seconds = playback_clock; + return CreateCoreRenewalResponse(core_request, renewal_duration, + oemcrypto_core_message); + }; + ValidateResponse(&(params.core_message), + params.extra_fields, odk_parse_func, + kdo_prepare_func); +} + +TEST(OdkTest, ProvisionResponseRoundtrip) { + ODK_ProvisioningResponseParams params; + ODK_SetDefaultProvisioningResponseParams(¶ms); + // save a copy of params.device_id as it will be zero out during the test + const uint32_t device_id_length = params.device_id_length; + uint8_t device_id[ODK_DEVICE_ID_LEN_MAX] = {0}; + memcpy(device_id, params.device_id, device_id_length); + + auto odk_parse_func = [&](const uint8_t* buf, size_t size) { + OEMCryptoResult err = ODK_ParseProvisioning( + buf, size + 16, size, &(params.core_message.nonce_values), device_id, + device_id_length, &(params.parsed_provisioning)); + return err; + }; + auto kdo_prepare_func = [&](ODK_ProvisioningRequest& core_request, + std::string* oemcrypto_core_message) { + core_request.device_id.assign(reinterpret_cast(device_id), + device_id_length); + return CreateCoreProvisioningResponse(params.parsed_provisioning, + core_request, oemcrypto_core_message); + }; + ValidateResponse(&(params.core_message), + params.extra_fields, odk_parse_func, + kdo_prepare_func); +} + +TEST(OdkSizeTest, LicenseRequest) { + uint8_t* message = nullptr; + size_t message_length = 0; + size_t core_message_length = 0; + uint16_t api_minor_version = ODK_MINOR_VERSION; + uint16_t api_major_version = 0; + uint32_t nonce = 0; + uint32_t session_id = 0; + ODK_NonceValues nonce_values{api_minor_version, api_major_version, nonce, + session_id}; + EXPECT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, + ODK_PrepareCoreLicenseRequest(message, message_length, + &core_message_length, &nonce_values)); + // the core_message_length should be appropriately set + EXPECT_EQ(ODK_LICENSE_REQUEST_SIZE, core_message_length); +} + +TEST(OdkSizeTest, RenewalRequest) { + uint8_t* message = nullptr; + size_t message_length = 0; + size_t core_message_length = 0; + uint16_t api_minor_version = ODK_MINOR_VERSION; + uint16_t api_major_version = ODK_MAJOR_VERSION; + uint32_t nonce = 0; + uint32_t session_id = 0; + ODK_ClockValues clock_values = {}; + clock_values.time_of_first_decrypt = 10; + clock_values.timer_status = ODK_CLOCK_TIMER_STATUS_LICENSE_LOADED; + uint64_t system_time_seconds = 15; + ODK_NonceValues nonce_values{api_minor_version, api_major_version, nonce, + session_id}; + EXPECT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, + ODK_PrepareCoreRenewalRequest(message, message_length, + &core_message_length, &nonce_values, + &clock_values, system_time_seconds)); + // the core_message_length should be appropriately set + EXPECT_EQ(ODK_RENEWAL_REQUEST_SIZE, core_message_length); +} + +TEST(OdkSizeTest, ReleaseRequest) { + uint8_t* message = nullptr; + size_t message_length = 0; + size_t core_message_length = 0; + uint16_t api_minor_version = ODK_MINOR_VERSION; + uint16_t api_major_version = 0; + uint32_t nonce = 0; + uint32_t session_id = 0; + ODK_ClockValues clock_values = {}; + clock_values.time_of_first_decrypt = 10; + clock_values.timer_status = ODK_CLOCK_TIMER_STATUS_LICENSE_INACTIVE; + uint64_t system_time_seconds = 15; + ODK_NonceValues nonce_values{api_minor_version, api_major_version, nonce, + session_id}; + EXPECT_EQ(OEMCrypto_SUCCESS, + ODK_PrepareCoreRenewalRequest(message, message_length, + &core_message_length, &nonce_values, + &clock_values, system_time_seconds)); + // Release requests do not have a core message. + EXPECT_GE(core_message_length, 0); +} + +TEST(OdkSizeTest, ProvisioningRequest) { + uint8_t* message = nullptr; + size_t message_length = 0; + size_t core_message_length = 0; + uint16_t api_minor_version = ODK_MINOR_VERSION; + uint16_t api_major_version = 0; + uint32_t nonce = 0; + uint32_t session_id = 0; + uint32_t device_id_length = 0; + ODK_NonceValues nonce_values{api_minor_version, api_major_version, nonce, + session_id}; + EXPECT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, + ODK_PrepareCoreProvisioningRequest( + message, message_length, &core_message_length, &nonce_values, + nullptr, device_id_length)); + // the core_message_length should be appropriately set + EXPECT_EQ(ODK_PROVISIONING_REQUEST_SIZE, core_message_length); +} + +// Verify the version string contains the right version numbers. +TEST(OdkTest, CheckReleaseVersion) { + // Here are the version numbers. + std::string expected_version = std::to_string(ODK_MAJOR_VERSION) + "." + + std::to_string(ODK_MINOR_VERSION); + // Here is the version string. + EXPECT_NE(std::string(ODK_RELEASE_DATE).find(expected_version), + std::string::npos) + << "Version mismatch in odk_structs.h"; +} + +} // namespace + +} // namespace wvodk_test diff --git a/oemcrypto/odk/test/odk_test_helper.cpp b/oemcrypto/odk/test/odk_test_helper.cpp new file mode 100644 index 0000000..167d9e5 --- /dev/null +++ b/oemcrypto/odk/test/odk_test_helper.cpp @@ -0,0 +1,488 @@ +// 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. + +#include "odk_test_helper.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include "OEMCryptoCENCCommon.h" +#include "gtest/gtest.h" +#include "odk_structs.h" +#include "odk_structs_priv.h" + +namespace wvodk_test { + +void ODK_SetDefaultCoreFields(ODK_CoreMessage* core_message, + uint32_t message_type) { + ASSERT_TRUE(core_message != nullptr); + core_message->message_type = message_type; + core_message->message_length = 0; + core_message->nonce_values.api_minor_version = ODK_MINOR_VERSION; + core_message->nonce_values.api_major_version = ODK_MAJOR_VERSION; + core_message->nonce_values.nonce = 0xdeadbeef; + core_message->nonce_values.session_id = 0xcafebabe; +} + +void ODK_SetDefaultLicenseResponseParams(ODK_LicenseResponseParams* params) { + ODK_SetDefaultCoreFields(&(params->core_message), ODK_License_Response_Type); + params->initial_license_load = true; + params->usage_entry_present = true; + params->parsed_license = { + .enc_mac_keys_iv = {.offset = 0, .length = 1}, + .enc_mac_keys = {.offset = 2, .length = 3}, + .pst = {.offset = 4, .length = 5}, + .srm_restriction_data = {.offset = 6, .length = 7}, + .license_type = OEMCrypto_EntitlementLicense, + .nonce_required = true, + .timer_limits = + { + .soft_enforce_rental_duration = true, + .soft_enforce_playback_duration = false, + .earliest_playback_start_seconds = 10, + .rental_duration_seconds = 11, + .total_playback_duration_seconds = 12, + .initial_renewal_duration_seconds = 13, + }, + .key_array_length = 3, + .key_array = + { + { + .key_id = {.offset = 15, .length = 16}, + .key_data_iv = {.offset = 17, .length = 18}, + .key_data = {.offset = 19, .length = 20}, + .key_control_iv = {.offset = 21, .length = 22}, + .key_control = {.offset = 23, .length = 24}, + }, + { + .key_id = {.offset = 25, .length = 26}, + .key_data_iv = {.offset = 27, .length = 28}, + .key_data = {.offset = 29, .length = 30}, + .key_control_iv = {.offset = 31, .length = 32}, + .key_control = {.offset = 33, .length = 34}, + }, + { + .key_id = {.offset = 35, .length = 36}, + .key_data_iv = {.offset = 37, .length = 38}, + .key_data = {.offset = 39, .length = 40}, + .key_control_iv = {.offset = 41, .length = 42}, + .key_control = {.offset = 43, .length = 44}, + }, + }, + }; + memset(params->request_hash, 0xaa, sizeof(params->request_hash)); + params->extra_fields = { + {ODK_SUBSTRING, &(params->parsed_license.enc_mac_keys_iv), + ".enc_mac_keys_iv"}, + {ODK_SUBSTRING, &(params->parsed_license.enc_mac_keys), ".enc_mac_keys"}, + {ODK_SUBSTRING, &(params->parsed_license.pst), ".pst"}, + {ODK_SUBSTRING, &(params->parsed_license.srm_restriction_data), + ".srm_restriction_data"}, + {ODK_UINT32, &(params->parsed_license.license_type), ".license_type"}, + {ODK_UINT32, &(params->parsed_license.nonce_required), ".nonce_required"}, + {ODK_UINT32, + &(params->parsed_license.timer_limits.soft_enforce_rental_duration), + ".soft_enforce_rental_duration"}, + {ODK_UINT32, + &(params->parsed_license.timer_limits.soft_enforce_playback_duration), + ".soft_enforce_playback_duration"}, + {ODK_UINT64, + &(params->parsed_license.timer_limits.earliest_playback_start_seconds), + ".earliest_playback_start_seconds"}, + {ODK_UINT64, + &(params->parsed_license.timer_limits.rental_duration_seconds), + ".rental_duration_seconds"}, + {ODK_UINT64, + &(params->parsed_license.timer_limits.total_playback_duration_seconds), + ".total_playback_duration_seconds"}, + {ODK_UINT64, + &(params->parsed_license.timer_limits.initial_renewal_duration_seconds), + ".initial_renewal_duration_seconds"}, + {ODK_UINT32, &(params->parsed_license.key_array_length), + ".key_array_length"}, + {ODK_SUBSTRING, &(params->parsed_license.key_array[0].key_id), ".key_id"}, + {ODK_SUBSTRING, &(params->parsed_license.key_array[0].key_data_iv), + ".key_data_iv"}, + {ODK_SUBSTRING, &(params->parsed_license.key_array[0].key_data), + ".key_data"}, + {ODK_SUBSTRING, &(params->parsed_license.key_array[0].key_control_iv), + ".key_control_iv"}, + {ODK_SUBSTRING, &(params->parsed_license.key_array[0].key_control), + ".key_control"}, + {ODK_SUBSTRING, &(params->parsed_license.key_array[1].key_id), ".key_id"}, + {ODK_SUBSTRING, &(params->parsed_license.key_array[1].key_data_iv), + ".key_data_iv"}, + {ODK_SUBSTRING, &(params->parsed_license.key_array[1].key_data), + ".key_data"}, + {ODK_SUBSTRING, &(params->parsed_license.key_array[1].key_control_iv), + ".key_control_iv"}, + {ODK_SUBSTRING, &(params->parsed_license.key_array[1].key_control), + ".key_control"}, + {ODK_SUBSTRING, &(params->parsed_license.key_array[2].key_id), ".key_id"}, + {ODK_SUBSTRING, &(params->parsed_license.key_array[2].key_data_iv), + ".key_data_iv"}, + {ODK_SUBSTRING, &(params->parsed_license.key_array[2].key_data), + ".key_data"}, + {ODK_SUBSTRING, &(params->parsed_license.key_array[2].key_control_iv), + ".key_control_iv"}, + {ODK_SUBSTRING, &(params->parsed_license.key_array[2].key_control), + ".key_control"}, + {ODK_HASH, params->request_hash, ".request_hash"}, + }; +} + +void ODK_SetDefaultRenewalResponseParams(ODK_RenewalResponseParams* params) { + ODK_SetDefaultCoreFields(&(params->core_message), ODK_Renewal_Response_Type); + params->system_time = 0xfaceb00c; + params->playback_clock = 10; + params->playback_timer = 20; + params->renewal_duration = 300; + params->extra_fields = { + {ODK_UINT64, &(params->playback_clock), "playback_clock"}, + {ODK_UINT64, &(params->renewal_duration), "renewal_duration"}, + }; + params->timer_limits = { + .soft_enforce_rental_duration = false, + .soft_enforce_playback_duration = false, + .earliest_playback_start_seconds = 0, + .rental_duration_seconds = 1000, + .total_playback_duration_seconds = 2000, + .initial_renewal_duration_seconds = 300, + }; + params->clock_values = { + .time_of_license_signed = + params->system_time - params->playback_clock - 42, + .time_of_first_decrypt = params->system_time - params->playback_clock, + .time_of_last_decrypt = params->system_time - params->playback_clock, + .time_of_renewal_request = params->playback_clock, + .time_when_timer_expires = params->system_time + params->playback_timer, + .timer_status = ODK_CLOCK_TIMER_STATUS_ACTIVE, + .status = kActive, + }; +} + +void ODK_SetDefaultProvisioningResponseParams( + ODK_ProvisioningResponseParams* params) { + ODK_SetDefaultCoreFields(&(params->core_message), + ODK_Provisioning_Response_Type); + params->device_id_length = ODK_DEVICE_ID_LEN_MAX / 2; + memset(params->device_id, 0xff, params->device_id_length); + memset(params->device_id + params->device_id_length, 0, + ODK_DEVICE_ID_LEN_MAX - params->device_id_length); + params->parsed_provisioning = { + .enc_private_key = {.offset = 0, .length = 1}, + .enc_private_key_iv = {.offset = 2, .length = 3}, + .encrypted_message_key = {.offset = 4, .length = 5}, + }; + params->extra_fields = { + {ODK_UINT32, &(params->device_id_length), "device_id_length"}, + {ODK_DEVICEID, params->device_id, "device_id"}, + {ODK_UINT32, &(params->parsed_provisioning).key_type, "key_type"}, + {ODK_SUBSTRING, &(params->parsed_provisioning).enc_private_key, + "enc_private_key"}, + {ODK_SUBSTRING, &(params->parsed_provisioning).enc_private_key_iv, + "enc_private_key_iv"}, + {ODK_SUBSTRING, &(params->parsed_provisioning).encrypted_message_key, + "encrypted_message_key"}, + }; +} + +size_t ODK_FieldLength(ODK_FieldType type) { + switch (type) { + case ODK_UINT16: + return sizeof(uint16_t); + case ODK_UINT32: + return sizeof(uint32_t); + case ODK_UINT64: + return sizeof(uint64_t); + case ODK_SUBSTRING: + return sizeof(uint32_t) + sizeof(uint32_t); + case ODK_DEVICEID: + return ODK_DEVICE_ID_LEN_MAX; + case ODK_HASH: + return ODK_SHA256_HASH_SIZE; + default: + return SIZE_MAX; + } +} + +size_t ODK_AllocSize(ODK_FieldType type) { + if (type == ODK_SUBSTRING) { + return sizeof(OEMCrypto_Substring); + } + return ODK_FieldLength(type); +} + +OEMCryptoResult ODK_WriteSingleField(uint8_t* buf, const ODK_Field* field) { + if (buf == nullptr || field == nullptr || field->value == nullptr) { + return ODK_ERROR_CORE_MESSAGE; + } + switch (field->type) { + case ODK_UINT16: { + const uint16_t u16 = htobe16(*static_cast(field->value)); + memcpy(buf, &u16, sizeof(u16)); + break; + } + case ODK_UINT32: { + const uint32_t u32 = htobe32(*static_cast(field->value)); + memcpy(buf, &u32, sizeof(u32)); + break; + } + case ODK_UINT64: { + const uint64_t u64 = htobe64(*static_cast(field->value)); + memcpy(buf, &u64, sizeof(u64)); + break; + } + case ODK_SUBSTRING: { + OEMCrypto_Substring* s = static_cast(field->value); + const uint32_t off = htobe32(s->offset); + const uint32_t len = htobe32(s->length); + memcpy(buf, &off, sizeof(off)); + memcpy(buf + sizeof(off), &len, sizeof(len)); + break; + } + case ODK_DEVICEID: + case ODK_HASH: { + const size_t field_len = ODK_FieldLength(field->type); + const uint8_t* const id = static_cast(field->value); + memcpy(buf, id, field_len); + + break; + } + default: + return ODK_ERROR_CORE_MESSAGE; + } + return OEMCrypto_SUCCESS; +} + +OEMCryptoResult ODK_ReadSingleField(const uint8_t* buf, + const ODK_Field* field) { + if (buf == nullptr || field == nullptr || field->value == nullptr) { + return ODK_ERROR_CORE_MESSAGE; + } + switch (field->type) { + case ODK_UINT16: { + memcpy(field->value, buf, sizeof(uint16_t)); + uint16_t* u16p = static_cast(field->value); + *u16p = be16toh(*u16p); + break; + } + case ODK_UINT32: { + memcpy(field->value, buf, sizeof(uint32_t)); + uint32_t* u32p = static_cast(field->value); + *u32p = be32toh(*u32p); + break; + } + case ODK_UINT64: { + memcpy(field->value, buf, sizeof(uint64_t)); + uint64_t* u64p = static_cast(field->value); + *u64p = be64toh(*u64p); + break; + } + case ODK_SUBSTRING: { + OEMCrypto_Substring* s = static_cast(field->value); + uint32_t off = 0; + uint32_t len = 0; + memcpy(&off, buf, sizeof(off)); + memcpy(&len, buf + sizeof(off), sizeof(len)); + s->offset = be32toh(off); + s->length = be32toh(len); + break; + } + case ODK_DEVICEID: + case ODK_HASH: { + const size_t field_len = ODK_FieldLength(field->type); + uint8_t* const id = static_cast(field->value); + memcpy(id, buf, field_len); + break; + } + default: + return ODK_ERROR_CORE_MESSAGE; + } + return OEMCrypto_SUCCESS; +} + +OEMCryptoResult ODK_DumpSingleField(const uint8_t* buf, + const ODK_Field* field) { + if (buf == nullptr || field == nullptr || field->value == nullptr) { + return ODK_ERROR_CORE_MESSAGE; + } + switch (field->type) { + case ODK_UINT16: { + uint16_t val; + memcpy(&val, buf, sizeof(uint16_t)); + val = be16toh(val); + std::cerr << field->name << ": " << val << " = 0x" << std::hex << val + << "\n"; + break; + } + case ODK_UINT32: { + uint32_t val; + memcpy(&val, buf, sizeof(uint32_t)); + val = be32toh(val); + std::cerr << field->name << ": " << val << " = 0x" << std::hex << val + << "\n"; + break; + } + case ODK_UINT64: { + uint64_t val; + memcpy(&val, buf, sizeof(uint64_t)); + val = be64toh(val); + std::cerr << field->name << ": " << val << " = 0x" << std::hex << val + << "\n"; + break; + } + case ODK_SUBSTRING: { + uint32_t off = 0; + uint32_t len = 0; + memcpy(&off, buf, sizeof(off)); + memcpy(&len, buf + sizeof(off), sizeof(len)); + std::cerr << field->name << ": (off=" << off << ", len=" << len << ")\n"; + break; + } + case ODK_DEVICEID: + case ODK_HASH: { + const size_t field_len = ODK_FieldLength(field->type); + std::cerr << field->name << ": "; + for (size_t i = 0; i < field_len; i++) { + std::cerr << std::hex << std::setfill('0') << std::setw(2) + << static_cast(buf[i]); + } + std::cerr << "\n"; + break; + } + default: + return ODK_ERROR_CORE_MESSAGE; + } + std::cerr << std::dec; // Return to normal. + return OEMCrypto_SUCCESS; +} + +/* + * Parameters: + * [in] size_in: buffer size + * [out] size_out: bytes processed + */ +OEMCryptoResult ODK_IterFields(ODK_FieldMode mode, uint8_t* buf, + const size_t size_in, size_t* size_out, + const std::vector& fields) { + if (buf == nullptr || size_out == nullptr) { + return ODK_ERROR_CORE_MESSAGE; + } + size_t off = 0, off2 = 0; + for (size_t i = 0; i < fields.size(); i++) { + if (__builtin_add_overflow(off, ODK_FieldLength(fields[i].type), &off2) || + off2 > size_in) { + return ODK_ERROR_CORE_MESSAGE; + } + uintptr_t base = reinterpret_cast(buf); + if (__builtin_add_overflow(base, off, &base)) { + return ODK_ERROR_CORE_MESSAGE; + } + uint8_t* const buf_off = buf + off; + switch (mode) { + case ODK_WRITE: + ODK_WriteSingleField(buf_off, &fields[i]); + break; + case ODK_READ: + ODK_ReadSingleField(buf_off, &fields[i]); + break; + case ODK_DUMP: + ODK_DumpSingleField(buf_off, &fields[i]); + break; + default: + return ODK_ERROR_CORE_MESSAGE; + } + off = off2; + } + *size_out = off; + if (*size_out > size_in) { + return ODK_ERROR_CORE_MESSAGE; + } + return OEMCrypto_SUCCESS; +} + +void ODK_ExpectEqualBuf(const void* s1, const void* s2, size_t n, + const std::vector& fields) { + if (memcmp(s1, s2, n) != 0) { + const void* buffers[] = {s1, s2}; + for (int i = 0; i < 2; i++) { + char _tmp[] = "/tmp/fileXXXXXX"; + const int temp_fd = mkstemp(_tmp); + if (temp_fd >= 0) { + close(temp_fd); + } else { + std::cerr << "Failed to open temp file." << std::endl; + break; + } + std::string tmp(_tmp); + std::fstream out(tmp, std::ios::out | std::ios::binary); + out.write(static_cast(buffers[i]), n); + out.close(); + std::cerr << "buffer " << i << " dumped to " << tmp << std::endl; + size_t bytes_written; + uint8_t* buf = + const_cast(reinterpret_cast(buffers[i])); + ODK_IterFields(ODK_DUMP, buf, n, &bytes_written, fields); + } + FAIL(); + } +} + +void ODK_ResetOdkFields(std::vector* fields) { + if (fields == nullptr) { + return; + } + for (auto& field : *fields) { + if (field.value != nullptr) { + const size_t size = ODK_AllocSize(field.type); + memset(field.value, 0, size); + } + } +} + +void ODK_BuildMessageBuffer(ODK_CoreMessage* core_message, + const std::vector& extra_fields, + uint8_t** buf, uint32_t* buf_size) { + ASSERT_TRUE(core_message != nullptr); + ASSERT_TRUE(buf_size != nullptr); + std::vector total_fields = { + {ODK_UINT32, &(core_message->message_type), "message_type"}, + {ODK_UINT32, &(core_message->message_length), "message_size"}, + {ODK_UINT16, &(core_message->nonce_values.api_minor_version), + "api_minor_version"}, + {ODK_UINT16, &(core_message->nonce_values.api_major_version), + "api_major_version"}, + {ODK_UINT32, &(core_message->nonce_values.nonce), "nonce"}, + {ODK_UINT32, &(core_message->nonce_values.session_id), "session_id"}, + }; + + uint32_t header_size = 0; + for (auto& field : total_fields) { + header_size += ODK_FieldLength(field.type); + } + + total_fields.insert(total_fields.end(), extra_fields.begin(), + extra_fields.end()); + for (auto& field : total_fields) { + *buf_size += ODK_FieldLength(field.type); + } + // update message_size + *(reinterpret_cast(total_fields[1].value)) = *buf_size; + + *buf = new uint8_t[*buf_size]{}; + size_t bytes_written = 0; + // serialize ODK fields to message buffer + EXPECT_EQ(OEMCrypto_SUCCESS, ODK_IterFields(ODK_WRITE, *buf, SIZE_MAX, + &bytes_written, total_fields)); + EXPECT_EQ(bytes_written, *buf_size); +} + +} // namespace wvodk_test diff --git a/oemcrypto/odk/test/odk_test_helper.h b/oemcrypto/odk/test/odk_test_helper.h new file mode 100644 index 0000000..ad29e89 --- /dev/null +++ b/oemcrypto/odk/test/odk_test_helper.h @@ -0,0 +1,99 @@ +/* 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 WIDEVINE_ODK_TEST_ODK_TEST_HELPER_H_ +#define WIDEVINE_ODK_TEST_ODK_TEST_HELPER_H_ + +#include +#include +#include + +#include "odk_structs.h" +#include "odk_structs_priv.h" + +namespace wvodk_test { + +enum ODK_FieldType { + ODK_UINT16, + ODK_UINT32, + ODK_UINT64, + ODK_SUBSTRING, + ODK_DEVICEID, + ODK_HASH, + ODK_NUMTYPES, +}; + +enum ODK_FieldMode { + ODK_READ, + ODK_WRITE, + ODK_DUMP, +}; + +struct ODK_Field { + ODK_FieldType type; + void* value; + std::string name; +}; + +struct ODK_LicenseResponseParams { + ODK_CoreMessage core_message; + bool initial_license_load; + bool usage_entry_present; + uint8_t request_hash[ODK_SHA256_HASH_SIZE]; + ODK_TimerLimits timer_limits; + ODK_ClockValues clock_values; + ODK_ParsedLicense parsed_license; + std::vector extra_fields; +}; + +struct ODK_RenewalResponseParams { + ODK_CoreMessage core_message; + uint64_t system_time; + uint64_t playback_clock; + uint64_t renewal_duration; + ODK_TimerLimits timer_limits; + ODK_ClockValues clock_values; + uint64_t playback_timer; + std::vector extra_fields; +}; + +struct ODK_ProvisioningResponseParams { + ODK_CoreMessage core_message; + uint8_t device_id[ODK_DEVICE_ID_LEN_MAX]; + uint32_t device_id_length; + ODK_ParsedProvisioning parsed_provisioning; + std::vector extra_fields; +}; + +/* Default values in core_message for testing */ +void ODK_SetDefaultCoreFields(ODK_CoreMessage* core_message, + uint32_t message_type); +void ODK_SetDefaultLicenseResponseParams(ODK_LicenseResponseParams* params); +void ODK_SetDefaultRenewalResponseParams(ODK_RenewalResponseParams* params); +void ODK_SetDefaultProvisioningResponseParams( + ODK_ProvisioningResponseParams* params); + +size_t ODK_FieldLength(ODK_FieldType type); +size_t ODK_AllocSize(ODK_FieldType type); + +/* Copy ODK_Field to buf */ +OEMCryptoResult ODK_WriteSingleField(uint8_t* buf, const ODK_Field* field); +/* Load buf to ODK_Field */ +OEMCryptoResult ODK_ReadSingleField(const uint8_t* buf, const ODK_Field* field); +OEMCryptoResult ODK_DumpSingleField(const uint8_t* buf, const ODK_Field* field); +OEMCryptoResult ODK_IterFields(ODK_FieldMode mode, uint8_t* buf, + const size_t size_in, size_t* size_out, + const std::vector& fields); +void ODK_ExpectEqualBuf(const void* s1, const void* s2, size_t n, + const std::vector& fields); +void ODK_ResetOdkFields(std::vector* fields); + +/* Serialize core_message and extra_fields into buf */ +void ODK_BuildMessageBuffer(ODK_CoreMessage* core_message, + const std::vector& extra_fields, + uint8_t** buf, uint32_t* buf_size); + +} /* namespace wvodk_test */ + +#endif /* WIDEVINE_ODK_TEST_ODK_TEST_HELPER_H_ */ diff --git a/oemcrypto/odk/test/odk_timer_test.cpp b/oemcrypto/odk/test/odk_timer_test.cpp new file mode 100644 index 0000000..c7f00ce --- /dev/null +++ b/oemcrypto/odk/test/odk_timer_test.cpp @@ -0,0 +1,1236 @@ +/* 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. + */ + +#include "OEMCryptoCENCCommon.h" +#include "gtest/gtest.h" +#include "odk.h" +#include "odk_structs_priv.h" + +namespace { + +// The rental clock starts when the request is signed. If any test fails +// because a time is off by exactly 1000, that is a strong indication that we +// confused rental and system clocks. The system clock should only be used +// internally on the device, because it might not be synchronized from one +// device to another. Rental clock is used in the license message from the +// server. +constexpr uint64_t kRentalClockStart = 1000u; + +// The renewal grace period is used by the server and the CDM layer to allow +// time between when the renewal is requested and when playback is cutoff if the +// renewal is not loaded. +constexpr uint64_t kGracePeriod = 5u; + +TEST(OdkTimerBasicTest, NullTest) { + // Assert that nullptr does not cause a core dump. + ODK_InitializeClockValues(nullptr, 0u); + ODK_ReloadClockValues(nullptr, 0u, 0u, 0u, kActive, 0u); + ODK_AttemptFirstPlayback(0u, nullptr, nullptr, nullptr); + ODK_UpdateLastPlaybackTime(0, nullptr, nullptr); + ASSERT_TRUE(true); +} + +TEST(OdkTimerBasicTest, Init) { + // Verify that basic initialization sets all of the fields. + ODK_ClockValues clock_values; + uint64_t time = 42; + ODK_InitializeClockValues(&clock_values, time); + EXPECT_EQ(clock_values.time_of_license_signed, time); + EXPECT_EQ(clock_values.time_of_first_decrypt, 0u); + EXPECT_EQ(clock_values.time_of_last_decrypt, 0u); + EXPECT_EQ(clock_values.time_when_timer_expires, 0u); + EXPECT_EQ(clock_values.timer_status, + ODK_CLOCK_TIMER_STATUS_LICENSE_NOT_LOADED); + EXPECT_EQ(clock_values.status, kUnused); +} + +TEST(OdkTimerBasicTest, Reload) { + // Verify that reloading clock values uses the same values + // for fields that can be saved, and sets others to 0. + ODK_ClockValues clock_values; + uint64_t time = 42u; + uint64_t lic_signed = 1u; + uint64_t first_decrypt = 2u; + uint64_t last_decrypt = 3u; + enum OEMCrypto_Usage_Entry_Status status = kInactiveUsed; + ODK_ReloadClockValues(&clock_values, lic_signed, first_decrypt, last_decrypt, + status, time); + EXPECT_EQ(clock_values.time_of_license_signed, lic_signed); + EXPECT_EQ(clock_values.time_of_first_decrypt, first_decrypt); + EXPECT_EQ(clock_values.time_of_last_decrypt, last_decrypt); + EXPECT_EQ(clock_values.time_when_timer_expires, 0u); + EXPECT_EQ(clock_values.timer_status, + ODK_CLOCK_TIMER_STATUS_LICENSE_NOT_LOADED); + EXPECT_EQ(clock_values.status, status); +} + +// All of the following test cases are derived from this base class. It +// simulates loading a license, attempting playback, and reloading a license. +class ODKTimerTest : public ::testing::Test { + public: + ODKTimerTest() { + // These are reasonable initial values for most tests. This is an unlimited + // license. Most of the tests below will add some restrictions by changing + // these values, and then verify that the ODK library behaves correctly. + timer_limits_.soft_enforce_rental_duration = true; + timer_limits_.soft_enforce_playback_duration = true; + timer_limits_.earliest_playback_start_seconds = 0u; + // A duration of 0 means infinite. + timer_limits_.rental_duration_seconds = 0u; + timer_limits_.total_playback_duration_seconds = 0u; + timer_limits_.initial_renewal_duration_seconds = 0u; + + // This is when we will attempt the first valid playback. + start_of_playback_ = + GetSystemTime(timer_limits_.earliest_playback_start_seconds + 10); + } + + protected: + void SetUp() override { + ::testing::Test::SetUp(); + // Start rental clock at kRentalClockStart. This happens when the license + // request is signed. + ODK_InitializeClockValues(&clock_values_, kRentalClockStart); + EXPECT_EQ(clock_values_.time_of_license_signed, kRentalClockStart); + } + + // Simulate loading or reloading a license in a new session. An offline + // license should have several of the clock_value's fields saved into the + // usage table. When it is reloaded those values should be reloaded. From + // these fields, the ODK function can tell if this is the first playback for + // the license, or just the first playback for this session. The key fields + // that should be saved are the status, and the times for license signed, and + // first and last playback times. + void ReloadLicense(uint64_t system_time) { + ODK_ClockValues old_clock_values = clock_values_; + // First clear out the old clock values. + memset(&clock_values_, 0, sizeof(clock_values_)); + // When the session is opened, the clock values are initialized. + ODK_InitializeClockValues(&clock_values_, 0); + // When the usage entry is reloaded, the clock values are reloaded. + ODK_ReloadClockValues(&clock_values_, + old_clock_values.time_of_license_signed, + old_clock_values.time_of_first_decrypt, + old_clock_values.time_of_last_decrypt, + old_clock_values.status, system_time); + EXPECT_EQ(clock_values_.timer_status, + ODK_CLOCK_TIMER_STATUS_LICENSE_NOT_LOADED); + // These shall not change: + EXPECT_EQ(clock_values_.time_of_license_signed, kRentalClockStart); + EXPECT_EQ(clock_values_.time_of_first_decrypt, + old_clock_values.time_of_first_decrypt); + EXPECT_EQ(clock_values_.time_of_last_decrypt, + old_clock_values.time_of_last_decrypt); + // ODK_ParseLicense sets the new timer state. + clock_values_.timer_status = ODK_CLOCK_TIMER_STATUS_LICENSE_LOADED; + } + + // Simulate loading or reloading a license, then verify that we are allowed + // playback from |start| to |stop|. If |cutoff| is not 0, then expect the + // timer to expire at that time. If |cutoff| is 0, expect the timer is not + // set. If you refer to the diagrams in "License Duration and Renewal", this + // tests a cyan bar with a green check mark. + // When nonzero |start|, |stop|, and |cutoff| are all system times. + void LoadAndAllowPlayback(uint64_t start, uint64_t stop, uint64_t cutoff) { + ReloadLicense(start); + AllowPlayback(start, stop, cutoff); + } + + // Verify that we are allowed playback from |start| to |stop|. If |cutoff| is + // not 0, then expect the timer to expire at that time. If |cutoff| is 0, + // expect the timer is not set. If you refer to the diagrams in "License + // Duration and Renewal", this tests a cyan bar with a green check mark. + // When nonzero |start|, |stop|, and |cutoff| are all system times. + void AllowPlayback(uint64_t start, uint64_t stop, uint64_t cutoff) { + ASSERT_LT(start, stop); + if (cutoff > 0) ASSERT_LE(stop, cutoff); + uint64_t timer_value; + const OEMCryptoResult result = ODK_AttemptFirstPlayback( + start, &timer_limits_, &clock_values_, &timer_value); + // After first playback, the license is active. + EXPECT_EQ(clock_values_.status, kActive); + EXPECT_EQ(clock_values_.time_when_timer_expires, cutoff); + if (cutoff > 0) { // If we expect the timer to be set. + EXPECT_EQ(result, ODK_SET_TIMER); + EXPECT_EQ(timer_value, cutoff - start); + } else { + EXPECT_EQ(result, ODK_DISABLE_TIMER); + } + CheckClockValues(start); + const uint64_t mid = (start + stop) / 2; + EXPECT_EQ(ODK_UpdateLastPlaybackTime(mid, &timer_limits_, &clock_values_), + OEMCrypto_SUCCESS); + CheckClockValues(mid); + EXPECT_EQ(ODK_UpdateLastPlaybackTime(stop, &timer_limits_, &clock_values_), + OEMCrypto_SUCCESS); + CheckClockValues(stop); + } + + // Simulate loading or reloading a license, then attempt to play from |start| + // to |cutoff|. Verify that we are allowed playback from |start| to |cutoff|, + // but playback is not allowed after |cutoff|. If you refer to the diagrams in + // "License Duration and Renewal", this tests a cyan bar with a black X. When + // nonzero |start|, and |cutoff| are all system times. + void LoadAndTerminatePlayback(uint64_t start, uint64_t cutoff) { + ReloadLicense(start); + TerminatePlayback(start, cutoff, cutoff + 10); + } + + // Attempt to play from |start| to |stop|. Verify that we are allowed playback + // from |start| to |cutoff|, but playback not allowed after |cutoff|. If you + // refer to the diagrams in "License Duration and Renewal", this tests a cyan + // bar with a black X. This assumes that |cutoff| is before |stop|. + // When nonzero |start|, |stop|, and |cutoff| are all system times. + void TerminatePlayback(uint64_t start, uint64_t cutoff, uint64_t stop) { + ASSERT_LT(start, cutoff); + ASSERT_LT(cutoff, stop); + AllowPlayback(start, cutoff, cutoff); + EXPECT_EQ( + ODK_UpdateLastPlaybackTime(cutoff + 1, &timer_limits_, &clock_values_), + ODK_TIMER_EXPIRED); + CheckClockValues(cutoff); + const uint64_t mid = (cutoff + stop) / 2; + EXPECT_EQ(ODK_UpdateLastPlaybackTime(mid, &timer_limits_, &clock_values_), + ODK_TIMER_EXPIRED); + // times do not change if playback was not allowed. + CheckClockValues(cutoff); + EXPECT_EQ(ODK_UpdateLastPlaybackTime(stop, &timer_limits_, &clock_values_), + ODK_TIMER_EXPIRED); + CheckClockValues(cutoff); + } + + // Verify that we are not allowed playback at the |start| time. If you refer + // to the diagrams in "License Duration and Renewal", this tests a cyan line + // followed by a black X. The parameter |start| is system time. + void ForbidPlayback(uint64_t start) { + ReloadLicense(start); + ODK_ClockValues old_clock_values = clock_values_; + uint64_t timer_value; + EXPECT_EQ(ODK_AttemptFirstPlayback(start, &timer_limits_, &clock_values_, + &timer_value), + ODK_TIMER_EXPIRED); + // These should not have changed. In particular, if the license was unused + // before, it should reamin unused. + EXPECT_EQ(clock_values_.time_of_license_signed, + old_clock_values.time_of_license_signed); + EXPECT_EQ(clock_values_.time_of_first_decrypt, + old_clock_values.time_of_first_decrypt); + EXPECT_EQ(clock_values_.time_of_last_decrypt, + old_clock_values.time_of_last_decrypt); + EXPECT_EQ(clock_values_.status, old_clock_values.status); + } + + // Verify that the clock values are correct. + void CheckClockValues(uint64_t time_of_last_decrypt) { + EXPECT_EQ(clock_values_.time_of_license_signed, kRentalClockStart); + EXPECT_EQ(clock_values_.time_of_first_decrypt, start_of_playback_); + EXPECT_EQ(clock_values_.time_of_last_decrypt, time_of_last_decrypt); + EXPECT_EQ(clock_values_.status, kActive); + } + + // Convert from rental time to system time. By "system time", we mean + // OEMCrypto's montonic clock. The spec does not specify a starting time for + // this clock. + uint64_t GetSystemTime(uint64_t rental_clock) { + return kRentalClockStart + rental_clock; + } + + // The end of the playback window. (using system clock) + // This is not useful if the playback duration is 0. + uint64_t EndOfPlaybackWindow() { + return start_of_playback_ + timer_limits_.total_playback_duration_seconds; + } + + // The end of the rental window. (using system clock) + // This is not useful if the rental duration is 0. + uint64_t EndOfRentalWindow() { + return GetSystemTime(timer_limits_.earliest_playback_start_seconds) + + timer_limits_.rental_duration_seconds; + } + + ODK_TimerLimits timer_limits_; + ODK_ClockValues clock_values_; + // The start of playback. This is set to the planned start at the beginning of + // the test. (using system clock) + uint64_t start_of_playback_; +}; + +TEST_F(ODKTimerTest, SimplePlayback) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 100, 0); +} + +// This tests that we are not allowed to start playback before the rental window +// opens. +TEST_F(ODKTimerTest, EarlyTest) { + timer_limits_.earliest_playback_start_seconds = 100u; + // This is earlier than we are allowed. + const uint64_t bad_start_time = + GetSystemTime(timer_limits_.earliest_playback_start_seconds - 10); + // An attempt to start playback before the timer starts is an error. + // We use the TIMER_EXPIRED error to mean both early or late. + ForbidPlayback(bad_start_time); + // And times were not updated: + EXPECT_EQ(clock_values_.status, kUnused); + // This is when we will attempt the first valid playback. + start_of_playback_ = + GetSystemTime(timer_limits_.earliest_playback_start_seconds + 10); + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 100, 0); +} + +// This runs the same test as above, but explicitly gives the ODK library a null +// pointer for the timer value. A null pointer is allowed for OEMCrypto +// implementations that do not manage their own timer. +TEST_F(ODKTimerTest, NullTimer) { + timer_limits_.earliest_playback_start_seconds = 100u; + // This is the earlier, invalid start time. + const uint64_t bad_start_time = + GetSystemTime(timer_limits_.earliest_playback_start_seconds - 10); + timer_limits_.rental_duration_seconds = 300; + timer_limits_.soft_enforce_rental_duration = false; + ReloadLicense(GetSystemTime(bad_start_time)); + + // If OEMCrypto passes in a null pointer, then the ODK should allow this. + uint64_t* timer_value_pointer = nullptr; + EXPECT_EQ(ODK_AttemptFirstPlayback(bad_start_time, &timer_limits_, + &clock_values_, timer_value_pointer), + ODK_TIMER_EXPIRED); + start_of_playback_ = + GetSystemTime(timer_limits_.earliest_playback_start_seconds + 10); + EXPECT_EQ(ODK_AttemptFirstPlayback(start_of_playback_, &timer_limits_, + &clock_values_, timer_value_pointer), + ODK_SET_TIMER); + CheckClockValues(start_of_playback_); +} + +/*****************************************************************************/ +// Note on Use Case tests. The test classes below correspond to the use cases +// in the doucment "License Duration and Renewal.". Each diagram in that +// document has a test class below to verify the use case is supported. +// +// In the document, we use realistic rental times in hours or days. In these +// tests, we will use round numbers so that it is easier to read. For example, +// instead of a seven day rental duration, we will use a 700 rental duration. +/*****************************************************************************/ + +/*****************************************************************************/ +// Streaming is the simplest use case. The user has three hours to watch the +// movie from the time of the rental. (See above for note on Use Case tests) +class ODKUseCase_Streaming : public ODKTimerTest { + public: + ODKUseCase_Streaming() { + // Rental duration = 3 hours hard. (use 300 for readability) + // Playback duration = 0 (unlimited) + timer_limits_.soft_enforce_rental_duration = false; + timer_limits_.rental_duration_seconds = 300; + timer_limits_.total_playback_duration_seconds = 0; + } +}; + +// Playback within rental duration. +TEST_F(ODKUseCase_Streaming, Case1) { + // Allow playback within the rental window. + LoadAndAllowPlayback(start_of_playback_, EndOfRentalWindow(), + EndOfRentalWindow()); +} + +// Playback exceeds rental duration. +TEST_F(ODKUseCase_Streaming, Case2) { + // Allow playback within the rental window, but not beyond. + LoadAndTerminatePlayback(start_of_playback_, EndOfRentalWindow()); +} + +// Playback with stops/restarts within rental duration, last one exceeds rental +// duration. +TEST_F(ODKUseCase_Streaming, Case3) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, + EndOfRentalWindow()); + LoadAndAllowPlayback(start_of_playback_ + 110, start_of_playback_ + 120, + EndOfRentalWindow()); + LoadAndTerminatePlayback(start_of_playback_ + 200, EndOfRentalWindow()); +} + +// Playback within rental duration, restart exceeds rental duration. +TEST_F(ODKUseCase_Streaming, Case4) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, + EndOfRentalWindow()); + ForbidPlayback(EndOfRentalWindow() + 10); +} + +// Initial playback exceeds rental duration. +TEST_F(ODKUseCase_Streaming, Case5) { + ForbidPlayback(EndOfRentalWindow() + 10); +} + +/*****************************************************************************/ +// Streaming Quick Start. The user must start watching within 30 seconds, and +// then has three hours to finish. (See above for note on Use Case tests) +class ODKUseCase_StreamingQuickStart : public ODKTimerTest { + public: + ODKUseCase_StreamingQuickStart() { + // Rental duration = 30 seconds, soft. + // Playback duration = 3 hours (use 300 for readability) + timer_limits_.soft_enforce_rental_duration = true; + timer_limits_.rental_duration_seconds = 30; + timer_limits_.total_playback_duration_seconds = 300; + timer_limits_.soft_enforce_playback_duration = false; + + // A valid start of playback time. + start_of_playback_ = + GetSystemTime(timer_limits_.rental_duration_seconds - 10); + } +}; + +// Playback starts within rental duration, continues within playback duration. +TEST_F(ODKUseCase_StreamingQuickStart, Case1) { + // As seen in the drawing, the playback window exceeds the rental window. + EXPECT_LE(EndOfRentalWindow(), EndOfPlaybackWindow()); + // Allow playback within the playback window. + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 100, + EndOfPlaybackWindow()); +} + +// Playback exceeds playback duration. +TEST_F(ODKUseCase_StreamingQuickStart, Case2) { + // Allow playback within the playback window, but not beyond. + LoadAndTerminatePlayback(start_of_playback_, EndOfPlaybackWindow()); +} + +// Playback with stops/restarts within playback duration, last one exceeds +// playback duration. +TEST_F(ODKUseCase_StreamingQuickStart, Case3) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, + EndOfPlaybackWindow()); + LoadAndAllowPlayback(start_of_playback_ + 110, start_of_playback_ + 120, + EndOfPlaybackWindow()); + LoadAndTerminatePlayback(start_of_playback_ + 200, EndOfPlaybackWindow()); +} + +// Initial playback exceeds rental duration. +TEST_F(ODKUseCase_StreamingQuickStart, Case4) { + ForbidPlayback(EndOfRentalWindow() + 10); +} + +/*****************************************************************************/ +// Seven Day / Two Day. The user must start watching within 7 days. Once +// started, the user has two days to finish the video. Playback is cutoff by the +// smaller of the two time limits. The first four cases start on day +// three. (See above for note on Use Case tests) +class ODKUseCase_SevenHardTwoHard_Start3 : public ODKTimerTest { + public: + ODKUseCase_SevenHardTwoHard_Start3() { + // Rental duration = 700, hard + // Playback duration = 200, hard + timer_limits_.rental_duration_seconds = 700; + timer_limits_.soft_enforce_rental_duration = false; + timer_limits_.total_playback_duration_seconds = 200; + timer_limits_.soft_enforce_playback_duration = false; + + start_of_playback_ = GetSystemTime(300); + } +}; + +// Playback within playback and rental duration. +TEST_F(ODKUseCase_SevenHardTwoHard_Start3, Case1) { + // As seen in the drawing, the playback window is within the rental window. + EXPECT_LT(EndOfPlaybackWindow(), EndOfRentalWindow()); + // Allow playback within the rental window. + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 100, + EndOfPlaybackWindow()); +} + +// Playback exceeds playback duration. +TEST_F(ODKUseCase_SevenHardTwoHard_Start3, Case2) { + // Allow playback within the playback window, but not beyond. + LoadAndTerminatePlayback(start_of_playback_, EndOfPlaybackWindow()); +} + +// Playback with stops/restarts within playback duration, last one exceeds +// playback duration. +TEST_F(ODKUseCase_SevenHardTwoHard_Start3, Case3) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, + EndOfPlaybackWindow()); + LoadAndAllowPlayback(start_of_playback_ + 75, start_of_playback_ + 100, + EndOfPlaybackWindow()); + LoadAndTerminatePlayback(start_of_playback_ + 125, EndOfPlaybackWindow()); +} + +// Restart exceeds playback duration. +TEST_F(ODKUseCase_SevenHardTwoHard_Start3, Case4) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, + EndOfPlaybackWindow()); + ForbidPlayback(EndOfPlaybackWindow() + 10); +} + +// The next four cases start on day six. +class ODKUseCase_SevenHardTwoHard_Start6 + : public ODKUseCase_SevenHardTwoHard_Start3 { + public: + ODKUseCase_SevenHardTwoHard_Start6() { + start_of_playback_ = GetSystemTime(600); + } +}; + +// Playback exceeds rental duration. +TEST_F(ODKUseCase_SevenHardTwoHard_Start6, Case5) { + // As seen in the drawing, the playback window is exceeds the rental window. + EXPECT_GT(EndOfPlaybackWindow(), EndOfRentalWindow()); + // Allow playback within the rental window. + LoadAndTerminatePlayback(start_of_playback_, EndOfRentalWindow()); +} + +// Playback with stops/restarts within playback duration, last one is terminated +// at the end of the rental duration. +TEST_F(ODKUseCase_SevenHardTwoHard_Start6, Case6) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, + EndOfRentalWindow()); + LoadAndAllowPlayback(start_of_playback_ + 50, start_of_playback_ + 75, + EndOfRentalWindow()); + // Allow playback that starts within rental window, but terminate at end of + // rental window. + LoadAndTerminatePlayback(start_of_playback_ + 90, EndOfRentalWindow()); +} + +// Playback within playback duration, restart exceeds rental duration. +TEST_F(ODKUseCase_SevenHardTwoHard_Start6, Case7) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, + EndOfRentalWindow()); + // Restart does not work after end of playback window. + ForbidPlayback(EndOfRentalWindow() + 10); +} + +// Playback exceeds rental and playback duration. +TEST_F(ODKUseCase_SevenHardTwoHard_Start6, Case8) { + LoadAndTerminatePlayback(start_of_playback_, EndOfRentalWindow()); +} + +// First playback cannot exceed rental duration. +TEST_F(ODKUseCase_SevenHardTwoHard_Start6, Case9) { + ForbidPlayback(EndOfRentalWindow() + 10); +} + +/*****************************************************************************/ +// Seven Day / Two Day. The user must start watching within 7 days. Once +// started, the user has two days to finish the video. Playback is cutoff by the +// rental duration time limits. The first four cases start on day three. (See +// above for note on Use Case tests) +class ODKUseCase_SevenHardTwoSoft_Start3 : public ODKTimerTest { + public: + ODKUseCase_SevenHardTwoSoft_Start3() { + // Rental duration = 700, hard + // Playback duration = 200, hard + timer_limits_.rental_duration_seconds = 700; + timer_limits_.soft_enforce_rental_duration = false; + timer_limits_.total_playback_duration_seconds = 200; + timer_limits_.soft_enforce_playback_duration = true; + + start_of_playback_ = GetSystemTime(300); + } +}; + +// Playback within playback and rental duration. +TEST_F(ODKUseCase_SevenHardTwoSoft_Start3, Case1) { + // As seen in the drawing, the playback window is within the rental window. + EXPECT_LT(EndOfPlaybackWindow(), EndOfRentalWindow()); + // Allow playback within the rental window. + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 100, + EndOfRentalWindow()); +} + +// Playback exceeds playback duration. +TEST_F(ODKUseCase_SevenHardTwoSoft_Start3, Case2) { + // Allow playback within the playback window, and a little after. + // Timer expires at end of rental window. + LoadAndAllowPlayback(start_of_playback_, EndOfPlaybackWindow() + 50, + EndOfRentalWindow()); +} + +// Playback with stops/restarts within playback duration, last one exceeds +// playback duration. +TEST_F(ODKUseCase_SevenHardTwoSoft_Start3, Case3) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, + EndOfRentalWindow()); + LoadAndAllowPlayback(start_of_playback_ + 75, start_of_playback_ + 100, + EndOfRentalWindow()); + LoadAndAllowPlayback(start_of_playback_ + 125, EndOfPlaybackWindow() + 50, + EndOfRentalWindow()); +} + +// Restart exceeds playback duration. +TEST_F(ODKUseCase_SevenHardTwoSoft_Start3, Case4) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, + EndOfRentalWindow()); + ForbidPlayback(EndOfPlaybackWindow() + 10); +} + +// The next four cases start on day six. +class ODKUseCase_SevenHardTwoSoft_Start6 + : public ODKUseCase_SevenHardTwoSoft_Start3 { + public: + ODKUseCase_SevenHardTwoSoft_Start6() { + start_of_playback_ = GetSystemTime(600); + } +}; + +// Playback exceeds rental duration. +TEST_F(ODKUseCase_SevenHardTwoSoft_Start6, Case5) { + // As seen in the drawing, the playback window is exceeds the rental window. + EXPECT_GT(EndOfPlaybackWindow(), EndOfRentalWindow()); + // Allow playback within the rental window. + LoadAndTerminatePlayback(start_of_playback_, EndOfRentalWindow()); +} + +// Playback with stops/restarts within playback duration, last one exceeds +// rental duration. +TEST_F(ODKUseCase_SevenHardTwoSoft_Start6, Case6) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, + EndOfRentalWindow()); + LoadAndAllowPlayback(start_of_playback_ + 50, start_of_playback_ + 75, + EndOfRentalWindow()); + LoadAndTerminatePlayback(start_of_playback_ + 90, EndOfRentalWindow()); +} + +// Playback within playback duration, restart exceeds rental duration. +TEST_F(ODKUseCase_SevenHardTwoSoft_Start6, Case7) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, + EndOfRentalWindow()); + // Restart does not work after end of playback window. + ForbidPlayback(EndOfRentalWindow() + 10); +} + +// Playback exceeds rental and playback duration. +TEST_F(ODKUseCase_SevenHardTwoSoft_Start6, Case8) { + LoadAndTerminatePlayback(start_of_playback_, EndOfRentalWindow()); +} + +// First playback cannot exceed rental duration. +TEST_F(ODKUseCase_SevenHardTwoSoft_Start6, Case9) { + ForbidPlayback(EndOfRentalWindow() + 10); +} + +/*****************************************************************************/ +// Seven Day / Two Day. The user must start watching within 7 days. Once +// started, the user has two days to finish the video. Playback is cutoff by the +// playback duration. The first four cases start on day three. (See above for +// note on Use Case tests) +class ODKUseCase_SevenSoftTwoHard_Start3 : public ODKTimerTest { + public: + ODKUseCase_SevenSoftTwoHard_Start3() { + // Rental duration = 700, hard + // Playback duration = 300, hard + timer_limits_.rental_duration_seconds = 700; + timer_limits_.soft_enforce_rental_duration = true; + timer_limits_.total_playback_duration_seconds = 200; + timer_limits_.soft_enforce_playback_duration = false; + + start_of_playback_ = GetSystemTime(300); + } +}; + +// Playback within playback and rental duration. +TEST_F(ODKUseCase_SevenSoftTwoHard_Start3, Case1) { + // As seen in the drawing, the playback window is within the rental window. + EXPECT_LT(EndOfPlaybackWindow(), EndOfRentalWindow()); + // Allow playback within the rental window. + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 100, + EndOfPlaybackWindow()); +} + +// Playback exceeds playback duration. +TEST_F(ODKUseCase_SevenSoftTwoHard_Start3, Case2) { + // Allow playback within the playback window, but not beyond. + LoadAndTerminatePlayback(start_of_playback_, EndOfPlaybackWindow()); +} + +// Playback with stops/restarts within playback duration, last one exceeds +// playback duration. +TEST_F(ODKUseCase_SevenSoftTwoHard_Start3, Case3) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, + EndOfPlaybackWindow()); + LoadAndAllowPlayback(start_of_playback_ + 75, start_of_playback_ + 100, + EndOfPlaybackWindow()); + LoadAndTerminatePlayback(start_of_playback_ + 125, EndOfPlaybackWindow()); +} + +// Restart exceeds playback duration. +TEST_F(ODKUseCase_SevenSoftTwoHard_Start3, Case4) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, + EndOfPlaybackWindow()); + ForbidPlayback(EndOfPlaybackWindow() + 10); +} + +// The next four cases start on day six. +class ODKUseCase_SevenSoftTwoHard_Start6 + : public ODKUseCase_SevenSoftTwoHard_Start3 { + public: + ODKUseCase_SevenSoftTwoHard_Start6() { + start_of_playback_ = GetSystemTime(600); + } +}; + +// Playback exceeds rental duration. +TEST_F(ODKUseCase_SevenSoftTwoHard_Start6, Case5) { + // As seen in the drawing, the playback window exceeds the rental window. + EXPECT_GT(EndOfPlaybackWindow(), EndOfRentalWindow()); + // Allow playback to continue beyond the rental window, but not beyond the + // playback window. + LoadAndTerminatePlayback(start_of_playback_, EndOfPlaybackWindow()); +} + +// Playback with stops/restarts within playback duration, last one exceeds +// rental duration. +TEST_F(ODKUseCase_SevenSoftTwoHard_Start6, Case6) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, + EndOfPlaybackWindow()); + LoadAndAllowPlayback(start_of_playback_ + 50, start_of_playback_ + 75, + EndOfPlaybackWindow()); + LoadAndTerminatePlayback(start_of_playback_ + 90, EndOfPlaybackWindow()); +} + +// Restart exceeds rental duration, playback exceeds playback duration. +TEST_F(ODKUseCase_SevenSoftTwoHard_Start6, Case7) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, + EndOfPlaybackWindow()); + LoadAndTerminatePlayback(EndOfRentalWindow() + 10, EndOfPlaybackWindow()); +} + +// Playback exceeds rental and playback duration. +TEST_F(ODKUseCase_SevenSoftTwoHard_Start6, Case8) { + LoadAndTerminatePlayback(start_of_playback_, EndOfPlaybackWindow()); +} + +// First playback cannot exceed rental duration. +TEST_F(ODKUseCase_SevenSoftTwoHard_Start6, Case9) { + ForbidPlayback(EndOfRentalWindow() + 10); +} + +/*****************************************************************************/ +// Seven Day / Two Day. The user must start watching within 7 days. Once +// started, the user has two days to finish the video. Playback is not cutoff, +// but restarts are not allowed after playback duration. The first four cases +// start on day three. (See above for note on Use Case tests) +class ODKUseCase_SevenSoftTwoSoft_Start3 : public ODKTimerTest { + public: + ODKUseCase_SevenSoftTwoSoft_Start3() { + // Rental duration = 700, hard + // Playback duration = 200, hard + timer_limits_.rental_duration_seconds = 700; + timer_limits_.soft_enforce_rental_duration = true; + timer_limits_.total_playback_duration_seconds = 200; + timer_limits_.soft_enforce_playback_duration = true; + + start_of_playback_ = GetSystemTime(300); + } +}; + +// Playback within playback and rental duration. +TEST_F(ODKUseCase_SevenSoftTwoSoft_Start3, Case1) { + // As seen in the drawing, the playback window is within the rental window. + EXPECT_LT(EndOfPlaybackWindow(), EndOfRentalWindow()); + // Allow playback within the rental window. + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 100, 0); +} + +// Playback exceeds playback duration. +TEST_F(ODKUseCase_SevenSoftTwoSoft_Start3, Case2) { + // Allow playback within the playback window, and beyond. No timer limit. + LoadAndAllowPlayback(start_of_playback_, EndOfPlaybackWindow() + 50, 0); +} + +// Playback with stops/restarts within playback duration, last one exceeds +// playback duration. +TEST_F(ODKUseCase_SevenSoftTwoSoft_Start3, Case3) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, 0); + LoadAndAllowPlayback(start_of_playback_ + 75, start_of_playback_ + 100, 0); + LoadAndAllowPlayback(start_of_playback_ + 125, EndOfPlaybackWindow() + 50, 0); +} + +// Restart exceeds playback duration. +TEST_F(ODKUseCase_SevenSoftTwoSoft_Start3, Case4) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, 0); + ForbidPlayback(EndOfPlaybackWindow() + 10); +} + +// The next four cases start on day six. +class ODKUseCase_SevenSoftTwoSoft_Start6 + : public ODKUseCase_SevenSoftTwoSoft_Start3 { + public: + ODKUseCase_SevenSoftTwoSoft_Start6() { + start_of_playback_ = GetSystemTime(600); + } +}; + +// Playback exceeds rental duration. +TEST_F(ODKUseCase_SevenSoftTwoSoft_Start6, Case5) { + // As seen in the drawing, the playback window exceeds the rental window. + EXPECT_GT(EndOfPlaybackWindow(), EndOfRentalWindow() + 25); + // Allow playback past the rental window, but within the playback window. + LoadAndAllowPlayback(start_of_playback_, EndOfRentalWindow() + 25, 0); +} + +// Playback with stops/restarts within playback duration, last one exceeds +// rental and playback duration. +TEST_F(ODKUseCase_SevenSoftTwoSoft_Start6, Case6) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, 0); + LoadAndAllowPlayback(start_of_playback_ + 50, start_of_playback_ + 75, 0); + LoadAndAllowPlayback(start_of_playback_ + 100, EndOfPlaybackWindow() + 50, 0); +} + +// Playback with stops/restarts within playback duration, last one exceeds +// playback duration. +TEST_F(ODKUseCase_SevenSoftTwoSoft_Start6, Case7) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, 0); + // Allow playback to start after end of rental window, and continue after + // playback window. + LoadAndAllowPlayback(EndOfRentalWindow() + 10, EndOfPlaybackWindow() + 10, 0); + // But forbid restart after playback window. + ForbidPlayback(EndOfPlaybackWindow() + 20); +} + +// Playback exceeds rental and playback duration. +TEST_F(ODKUseCase_SevenSoftTwoSoft_Start6, Case8) { + LoadAndAllowPlayback(start_of_playback_, EndOfPlaybackWindow() + 100, 0); +} + +// First playback cannot exceed rental duration. +TEST_F(ODKUseCase_SevenSoftTwoSoft_Start6, Case9) { + ForbidPlayback(EndOfRentalWindow() + 10); +} + +class RenewalTest : public ODKTimerTest { + protected: + // Verify that a renewal can be processed and playback may continue from + // |start| to |stop|. If |cutoff| is not 0, then expect the timer to expire + // at that time. If |cutoff| is 0, expect the timer is not set. If you refer + // to the diagrams in "License Duration and Renewal", this tests a cyan bar + // with a green check mark. + void RenewAndContinue(uint64_t start, uint64_t stop, uint64_t cutoff, + uint64_t renewal_duration_seconds) { + uint64_t timer_value; + RenewAndContinue(start, stop, cutoff, renewal_duration_seconds, + &timer_value); + } + + // Verify that a renewal can be processed and playback may continue from + // |start| to |stop|. If |cutoff| is not 0, then expect the timer to expire + // at that time. If |cutoff| is 0, expect the timer is not set. If you refer + // to the diagrams in "License Duration and Renewal", this tests a cyan bar + // with a green check mark. + // This does the work of the function above, except it allows the caller to + // explicitly set the timer_value_pointer. The timer_value_pointer can be a + // nullptr. + void RenewAndContinue(uint64_t start, uint64_t stop, uint64_t cutoff, + uint64_t renewal_duration_seconds, + uint64_t* timer_value_pointer) { + ASSERT_LT(start, stop); + if (cutoff > 0) ASSERT_LE(stop, cutoff); + // We'll fake instantaneous renewal requests. Flight time not important. + clock_values_.time_of_renewal_request = start; + const OEMCryptoResult result = ODK_ComputeRenewalDuration( + &timer_limits_, &clock_values_, start, renewal_duration_seconds, + timer_value_pointer); + // After first playback, the license is active. + EXPECT_EQ(clock_values_.status, kActive); + EXPECT_EQ(clock_values_.time_when_timer_expires, cutoff); + if (cutoff > 0) { // If we expect the timer to be set. + EXPECT_EQ(result, ODK_SET_TIMER); + if (timer_value_pointer != nullptr) + EXPECT_EQ(*timer_value_pointer, cutoff - start); + } else { + EXPECT_EQ(result, ODK_DISABLE_TIMER); + } + EXPECT_EQ(ODK_UpdateLastPlaybackTime(start, &timer_limits_, &clock_values_), + OEMCrypto_SUCCESS); + CheckClockValues(start); + const uint64_t mid = (start + stop) / 2; + EXPECT_EQ(ODK_UpdateLastPlaybackTime(mid, &timer_limits_, &clock_values_), + OEMCrypto_SUCCESS); + CheckClockValues(mid); + EXPECT_EQ(ODK_UpdateLastPlaybackTime(stop, &timer_limits_, &clock_values_), + OEMCrypto_SUCCESS); + CheckClockValues(stop); + } + + // Verify that a renewal can be processed and attempt to play from |start| to + // after |cutoff|. Verify that we are allowed playback from |start| to + // |cutoff|, but playback not allowed after |cutoff|. If you refer to the + // diagrams in "License Duration and Renewal", this tests a cyan bar with a + // black X. + void RenewAndTerminate(uint64_t start, uint64_t cutoff, + uint64_t renewal_duration_seconds) { + RenewAndTerminate(start, cutoff, cutoff + 100, renewal_duration_seconds); + } + + // Verify that a renewal can be processed and attempt to play from |start| to + // |stop|. Verify that we are allowed playback from |start| to |cutoff|, but + // playback not allowed after |cutoff|. If you refer to the diagrams in + // "License Duration and Renewal", this tests a cyan bar with a black X. This + // assumes that |cutoff| is between |start| and |stop|. + void RenewAndTerminate(uint64_t start, uint64_t cutoff, uint64_t stop, + uint64_t renewal_duration_seconds) { + ASSERT_LT(start, cutoff); + ASSERT_LT(cutoff, stop); + RenewAndContinue(start, cutoff, cutoff, renewal_duration_seconds); + EXPECT_EQ( + ODK_UpdateLastPlaybackTime(cutoff + 1, &timer_limits_, &clock_values_), + ODK_TIMER_EXPIRED); + CheckClockValues(cutoff); + const uint64_t mid = (cutoff + stop) / 2; + EXPECT_EQ(ODK_UpdateLastPlaybackTime(mid, &timer_limits_, &clock_values_), + ODK_TIMER_EXPIRED); + CheckClockValues( + cutoff); // times do not change if playback was not allowed. + EXPECT_EQ(ODK_UpdateLastPlaybackTime(stop, &timer_limits_, &clock_values_), + ODK_TIMER_EXPIRED); + CheckClockValues(cutoff); + } + + // Verify that a renewal can be processed and playback may start from + // |start| to |stop|. If |cutoff| is not 0, then expect the timer to expire + // at that time. If |cutoff| is 0, expect the timer is not set. If you refer + // to the diagrams in "License Duration and Renewal", this tests a cyan bar + // with a green check mark. This is different from the previous functions, + // because the renewal is loaded before the first playback. + void RenewAndStart(uint64_t start, uint64_t stop, uint64_t cutoff, + uint64_t renewal_duration_seconds) { + ASSERT_LT(start, stop); + if (cutoff > 0) ASSERT_LE(stop, cutoff); + // We'll fake instantaneous renewal requests. Flight time not important. + clock_values_.time_of_renewal_request = start; + uint64_t timer_value; + const OEMCryptoResult result = + ODK_ComputeRenewalDuration(&timer_limits_, &clock_values_, start, + renewal_duration_seconds, &timer_value); + EXPECT_EQ(clock_values_.time_when_timer_expires, cutoff); + if (cutoff > 0) { // If we expect the timer to be set. + EXPECT_EQ(result, ODK_SET_TIMER); + EXPECT_EQ(timer_value, cutoff - start); + } else { + EXPECT_EQ(result, ODK_DISABLE_TIMER); + } + AllowPlayback(start, stop, cutoff); + } +}; + +// License with Renewal, limited by playback duration. (See above for note on +// Use Case tests) These tests are parameterized. If the parameter is 0, we +// limit the playback duration. If the parameter is 1, we limit the rental +// duration. The behavior is basically the same. +class ODKUseCase_LicenseWithRenewal + : public RenewalTest, + public ::testing::WithParamInterface { + public: + ODKUseCase_LicenseWithRenewal() { + // Either Playback or rental duration = 2 days hard + if (GetParam() == 0) { + timer_limits_.soft_enforce_rental_duration = false; + timer_limits_.rental_duration_seconds = 2000; + } else { + timer_limits_.soft_enforce_playback_duration = false; + timer_limits_.total_playback_duration_seconds = 2000; + } + timer_limits_.initial_renewal_duration_seconds = 50; + } + + void SetUp() override { + RenewalTest::SetUp(); + renewal_interval_ = + timer_limits_.initial_renewal_duration_seconds - kGracePeriod; + uint64_t next_renewal = start_of_playback_ + renewal_interval_; + // Allow playback within the initial renewal window. + LoadAndAllowPlayback(start_of_playback_, next_renewal, + next_renewal + kGracePeriod); + } + + uint64_t playback_end_restriction() { + if (GetParam() == 0) { + return EndOfRentalWindow(); + } else { + return EndOfPlaybackWindow(); + } + } + + protected: + // How long to wait before we load the next renewal. i.e. cutoff - + // kGracePeriod. This is because the renewal_duration includes the grace + // period. + uint64_t renewal_interval_; +}; + +// Playback within rental duration and renewal duration. +TEST_P(ODKUseCase_LicenseWithRenewal, Case1) { + uint64_t next_renewal = start_of_playback_ + renewal_interval_; + for (int i = 0; i < 4; i++) { + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndContinue( + current_renewal, // start: when renewal is loaded. + next_renewal, // stop: expect play allowed. + next_renewal + kGracePeriod, // cutoff: when timer expires. + timer_limits_.initial_renewal_duration_seconds); + } +} + +// Playback within rental duration, last renewal_interval_ exceeds renewal +// duration. +TEST_P(ODKUseCase_LicenseWithRenewal, Case2) { + uint64_t next_renewal = start_of_playback_ + renewal_interval_; + for (int i = 0; i < 4; i++) { + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndContinue( + current_renewal, // start: when renewal is loaded. + next_renewal, // stop: expect play allowed. + next_renewal + kGracePeriod, // cutoff: when timer expires. + timer_limits_.initial_renewal_duration_seconds); + } + // We attempt to continue playing beyond the renewal renewal_interval_. + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndTerminate(current_renewal, // start: when renewal is loaded. + next_renewal + kGracePeriod, // cutoff: when timer expires. + timer_limits_.initial_renewal_duration_seconds); +} + +// Playback interrupted by late renewal. +TEST_P(ODKUseCase_LicenseWithRenewal, Case3) { + uint64_t next_renewal = start_of_playback_ + renewal_interval_; + for (int i = 0; i < 4; i++) { + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndContinue( + current_renewal, // start: when renewal is loaded. + next_renewal, // stop: expect play allowed. + next_renewal + kGracePeriod, // cutoff: when timer expires. + timer_limits_.initial_renewal_duration_seconds); + } + // We attempt to continue playing beyond the renewal renewal_interval_. + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndTerminate(current_renewal, // start: when renewal is loaded. + next_renewal + kGracePeriod, // stop: when timer expires. + timer_limits_.initial_renewal_duration_seconds); + + const uint64_t late_renewal = next_renewal + kGracePeriod + 10; + next_renewal = late_renewal + renewal_interval_; + RenewAndContinue(late_renewal, // start: when renewal is loaded. + next_renewal, // stop: expect play allowed. + next_renewal + kGracePeriod, // cutoff: when timer expires. + timer_limits_.initial_renewal_duration_seconds); +} + +// Playback & restart within playback duration and renewal duration. Note: this +// simulates reloading a persistent license, and then reloading the last +// renewal. +TEST_P(ODKUseCase_LicenseWithRenewal, Case4) { + uint64_t next_renewal = start_of_playback_ + renewal_interval_; + for (int i = 0; i < 2; i++) { + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndContinue( + current_renewal, // start: when renewal is loaded. + next_renewal, // stop: expect play allowed. + next_renewal + kGracePeriod, // cutoff: when timer expires. + timer_limits_.initial_renewal_duration_seconds); + } + // Reload license after timer expired. + uint64_t reload_time = next_renewal + kGracePeriod + 100; + next_renewal = reload_time + renewal_interval_; + ReloadLicense(reload_time); + RenewAndStart(reload_time, next_renewal, next_renewal + kGracePeriod, + timer_limits_.initial_renewal_duration_seconds); + for (int i = 0; i < 2; i++) { + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndContinue( + current_renewal, // start: when renewal is loaded. + next_renewal, // stop: expect play allowed. + next_renewal + kGracePeriod, // cutoff: when timer expires. + timer_limits_.initial_renewal_duration_seconds); + } +} + +// Playback within renewal duration, but exceeds playback duration. +TEST_P(ODKUseCase_LicenseWithRenewal, Case5) { + uint64_t next_renewal = start_of_playback_ + renewal_interval_; + do { + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndContinue( + current_renewal, // start: when renewal is loaded. + next_renewal, // stop: expect play allowed. + next_renewal + kGracePeriod, // cutoff: when timer expires. + timer_limits_.initial_renewal_duration_seconds); + } while ((next_renewal + renewal_interval_ + kGracePeriod) < + playback_end_restriction()); + // Attempt playing beyond the playback window. + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndTerminate(current_renewal, // start: when renewal is loaded. + playback_end_restriction(), // stop: when timer expires. + timer_limits_.initial_renewal_duration_seconds); +} + +// Renewal duration increases over time. +TEST_P(ODKUseCase_LicenseWithRenewal, Case6) { + uint64_t next_renewal = start_of_playback_ + renewal_interval_; + do { + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndContinue( + current_renewal, // start: when renewal is loaded. + next_renewal, // stop: expect play allowed. + next_renewal + kGracePeriod, // cutoff: when timer expires. + renewal_interval_ + kGracePeriod); + // Renewal_Interval_ increases with each renewal. + renewal_interval_ += 100; + } while (next_renewal + renewal_interval_ + kGracePeriod < + playback_end_restriction()); + // Attempt playing beyond the playback window: + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndTerminate(current_renewal, // start: when renewal is loaded. + playback_end_restriction(), // cutoff: when timer expires. + renewal_interval_ + kGracePeriod); +} + +// Increasing renewal duration, playback exceeds last renewal. +// This is a mix between case 2 and case 6. +TEST_P(ODKUseCase_LicenseWithRenewal, Case7) { + uint64_t next_renewal = start_of_playback_ + renewal_interval_; + for (int i = 0; i < 4; i++) { + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndContinue( + current_renewal, // start: when renewal is loaded. + next_renewal, // stop: expect play allowed. + next_renewal + kGracePeriod, // cutoff: when timer expires. + renewal_interval_ + kGracePeriod); + // Renewal_Interval_ increases with each renewal. + renewal_interval_ += 100; + } + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndTerminate(current_renewal, // start: when renewal is loaded. + next_renewal + kGracePeriod, // cutoff: when timer expires. + renewal_interval_ + kGracePeriod); +} + +// This is just like Case1, except we use a null pointer for the timer values in +// RenewAndContinue. It is not shown in the use case document. +TEST_P(ODKUseCase_LicenseWithRenewal, NullPointerTest) { + uint64_t next_renewal = start_of_playback_ + renewal_interval_; + const uint64_t start = next_renewal; + const uint64_t stop = start + renewal_interval_; + const uint64_t cutoff = stop + kGracePeriod; + const uint64_t renewal_duration_seconds = + timer_limits_.initial_renewal_duration_seconds; + uint64_t* timer_value_pointer = nullptr; + RenewAndContinue(start, stop, cutoff, renewal_duration_seconds, + timer_value_pointer); +} + +INSTANTIATE_TEST_CASE_P(RestrictRenewal, ODKUseCase_LicenseWithRenewal, + ::testing::Values(0, 1)); + +// Limited Duration License. (See above for notes on Use Case tests). The user +// has 15 minutes to begin watching the movie. If a renewal is not received, +// playback is terminated after 30 seconds. If a renewal is received, playback +// may continue for two hours from playback start. +class ODKUseCase_LimitedDurationLicense : public RenewalTest { + public: + ODKUseCase_LimitedDurationLicense() { + renewal_delay_ = 30u; + time_of_renewal_ = start_of_playback_ + renewal_delay_; + + timer_limits_.soft_enforce_rental_duration = true; + timer_limits_.rental_duration_seconds = 150; // 15 minutes. + timer_limits_.soft_enforce_playback_duration = false; + timer_limits_.total_playback_duration_seconds = 2000; // Two hours. + timer_limits_.initial_renewal_duration_seconds = + renewal_delay_ + kGracePeriod; + } + uint64_t renewal_delay_; + uint64_t time_of_renewal_; +}; + +// Playback started within rental window and continues. +TEST_F(ODKUseCase_LimitedDurationLicense, Case1) { + // Allow playback within the initial renewal window. + LoadAndAllowPlayback(start_of_playback_, time_of_renewal_, + time_of_renewal_ + kGracePeriod); + const uint64_t renewal_duration = 2000; // two hour renewal. + const uint64_t play_for_one_hour = GetSystemTime(1000); + RenewAndContinue(time_of_renewal_, // start: when renewal is loaded. + play_for_one_hour, // stop: expect play allowed. + EndOfPlaybackWindow(), // cutoff: when timer expires. + renewal_duration); +} + +// Playback started after rental duration. +TEST_F(ODKUseCase_LimitedDurationLicense, Case2) { + start_of_playback_ = EndOfRentalWindow() + 1; + ForbidPlayback(start_of_playback_); +} + +// Playback started within rental window but renewal not received. +TEST_F(ODKUseCase_LimitedDurationLicense, Case3) { + // Allow playback within the initial renewal window. + LoadAndTerminatePlayback(start_of_playback_, time_of_renewal_ + kGracePeriod); +} + +// Playback started within rental window, renewal is received, and playback +// continues. +TEST_F(ODKUseCase_LimitedDurationLicense, Case4) { + // Allow playback within the initial renewal window. + LoadAndAllowPlayback(start_of_playback_, time_of_renewal_, + time_of_renewal_ + kGracePeriod); + const uint64_t renewal_duration = 2000; // two hour renewal. + RenewAndTerminate(time_of_renewal_, // start: when renewal is loaded. + EndOfPlaybackWindow(), // stop: when timer expires. + renewal_duration); +} + +// Playback may be restarted. +TEST_F(ODKUseCase_LimitedDurationLicense, Case5) { + // Allow playback within the initial renewal window. + LoadAndAllowPlayback(start_of_playback_, time_of_renewal_, + time_of_renewal_ + kGracePeriod); + const uint64_t renewal_duration = 2000; // two hour renewal. + const uint64_t play_for_one_hour = GetSystemTime(1000); + RenewAndContinue(time_of_renewal_, // start: when renewal is loaded. + play_for_one_hour, // stop: expect play allowed. + EndOfPlaybackWindow(), // cutoff: when timer expires. + renewal_duration); + + const uint64_t reload_time = play_for_one_hour + 100; + ReloadLicense(reload_time); + // Simulate reloading the license, and then reloading the renewal, and then + // restarting playback. That is allowed, and playback shall be terminated at + // the end of the original playback window. + RenewAndStart(reload_time, EndOfPlaybackWindow(), EndOfPlaybackWindow(), + renewal_duration); + // But not one second more. + EXPECT_EQ(ODK_UpdateLastPlaybackTime(EndOfPlaybackWindow() + 1, + &timer_limits_, &clock_values_), + ODK_TIMER_EXPIRED); + CheckClockValues(EndOfPlaybackWindow()); +} + +// Verify that the backwards compatible function, ODK_InitializeV15Values, can +// set up the clock values and timer limits. +TEST_F(RenewalTest, V15Test) { + const uint32_t key_duration = 25; + ODK_NonceValues nonce_values; + const uint64_t license_loaded = GetSystemTime(10); + EXPECT_EQ( + ODK_InitializeV15Values(&timer_limits_, &clock_values_, &nonce_values, + key_duration, license_loaded), + OEMCrypto_SUCCESS); + const uint64_t later_on = GetSystemTime(200); + TerminatePlayback(license_loaded, license_loaded + key_duration, later_on); + const uint32_t new_key_duration = 100; + RenewAndTerminate(later_on, later_on + new_key_duration, new_key_duration); +} +} // namespace diff --git a/oemcrypto/ref/Android.bp b/oemcrypto/ref/Android.bp new file mode 100644 index 0000000..e435787 --- /dev/null +++ b/oemcrypto/ref/Android.bp @@ -0,0 +1,37 @@ +cc_library_shared { + name: "libcasoemcrypto", + proprietary: true, + srcs: [ + "src/keys.cpp", + "src/oem_cert.cpp", + "src/oemcrypto_auth_ref.cpp", + "src/oemcrypto_engine_device_properties.cpp", + "src/oemcrypto_engine_ref.cpp", + "src/oemcrypto_entitled_key_session.cpp", + "src/oemcrypto_keybox_ref.cpp", + "src/oemcrypto_key_ref.cpp", + "src/oemcrypto_nonce_table.cpp", + "src/oemcrypto_ref.cpp", + "src/oemcrypto_rsa_key_shared.cpp", + "src/oemcrypto_session.cpp", + "src/oemcrypto_session_key_table.cpp", + "src/oemcrypto_usage_table_ref.cpp", + "src/wvcrc.cpp", + "src/ts_parser.cpp", + ], + include_dirs: [ + "vendor/widevine/libwvmediacas/oemcrypto/odk/include", + ], + static_libs: [ + "//vendor/widevine/libwvmediacas/wvutil:libcasutil", + "libwvcas_odk", + ], + shared_libs: [ + "libcrypto", + "libutils", + "liblog", + ], + header_libs: [ + "//vendor/widevine/libwvmediacas/oemcrypto:oemcastroheaders", + ], +} diff --git a/oemcrypto/ref/README.md b/oemcrypto/ref/README.md new file mode 100644 index 0000000..cf8412d --- /dev/null +++ b/oemcrypto/ref/README.md @@ -0,0 +1,5 @@ +# Reference OEMCrypto + +This directory contains a testing-only implementation of OEMCrypto. **This +implementation is *NOT* suitable for production use and must *NOT* be released +on devices.** diff --git a/oemcrypto/ref/src/keys.cpp b/oemcrypto/ref/src/keys.cpp new file mode 100644 index 0000000..380e032 --- /dev/null +++ b/oemcrypto/ref/src/keys.cpp @@ -0,0 +1,10 @@ +// If you are using a baked-in certificate instead of supporting keyboxes, +// you should have received a keys.cpp from Widevine that replaces this file. +// +// If you are not using a baked-in certificate, then you may ignore this file, +// as it intentionally defines an empty key. + +#include "keys.h" + +const uint8_t kPrivateKey[] = { 0, }; +const size_t kPrivateKeySize = 0; diff --git a/oemcrypto/ref/src/keys.h b/oemcrypto/ref/src/keys.h new file mode 100644 index 0000000..e6c0145 --- /dev/null +++ b/oemcrypto/ref/src/keys.h @@ -0,0 +1,11 @@ +// This header is used to access the baked-in certificate if one is in use. +#ifndef KEYS_H_ +#define KEYS_H_ + +#include +#include + +extern const uint8_t kPrivateKey[]; +extern const size_t kPrivateKeySize; + +#endif // KEYS_H_ diff --git a/oemcrypto/ref/src/oem_cert.cpp b/oemcrypto/ref/src/oem_cert.cpp new file mode 100644 index 0000000..0b7ab7f --- /dev/null +++ b/oemcrypto/ref/src/oem_cert.cpp @@ -0,0 +1,334 @@ +// This file contains the test OEM cert. + +#include "oem_cert.h" + +namespace wvoec_ref { + +const uint32_t kOEMSystemId_Prod = 7913; + +// From file test_rsa_key_2_carmichael.pk8 in team shared drive. +// Size is 1216. +const uint8_t kOEMPrivateKey_Prod[] = { + 0x30, 0x82, 0x04, 0xbc, 0x02, 0x01, 0x00, 0x30, 0x0d, 0x06, 0x09, 0x2a, + 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x04, 0x82, + 0x04, 0xa6, 0x30, 0x82, 0x04, 0xa2, 0x02, 0x01, 0x00, 0x02, 0x82, 0x01, + 0x01, 0x00, 0xa7, 0x00, 0x36, 0x60, 0x65, 0xdc, 0xbd, 0x54, 0x5a, 0x2a, + 0x40, 0xb4, 0xe1, 0x15, 0x94, 0x58, 0x11, 0x4f, 0x94, 0x58, 0xdd, 0xde, + 0xa7, 0x1f, 0x3c, 0x2c, 0xe0, 0x88, 0x09, 0x29, 0x61, 0x57, 0x67, 0x5e, + 0x56, 0x7e, 0xee, 0x27, 0x8f, 0x59, 0x34, 0x9a, 0x2a, 0xaa, 0x9d, 0xb4, + 0x4e, 0xfa, 0xa7, 0x6a, 0xd4, 0xc9, 0x7a, 0x53, 0xc1, 0x4e, 0x9f, 0xe3, + 0x34, 0xf7, 0x3d, 0xb7, 0xc9, 0x10, 0x47, 0x4f, 0x28, 0xda, 0x3f, 0xce, + 0x31, 0x7b, 0xfd, 0x06, 0x10, 0xeb, 0xf7, 0xbe, 0x92, 0xf9, 0xaf, 0xfb, + 0x3e, 0x68, 0xda, 0xee, 0x1a, 0x64, 0x4c, 0xf3, 0x29, 0xf2, 0x73, 0x9e, + 0x39, 0xd8, 0xf6, 0x6f, 0xd8, 0xb2, 0x80, 0x82, 0x71, 0x8e, 0xb5, 0xa4, + 0xf2, 0xc2, 0x3e, 0xcd, 0x0a, 0xca, 0xb6, 0x04, 0xcd, 0x9a, 0x13, 0x8b, + 0x54, 0x73, 0x54, 0x25, 0x54, 0x8c, 0xbe, 0x98, 0x7a, 0x67, 0xad, 0xda, + 0xb3, 0x4e, 0xb3, 0xfa, 0x82, 0xa8, 0x4a, 0x67, 0x98, 0x56, 0x57, 0x54, + 0x71, 0xcd, 0x12, 0x7f, 0xed, 0xa3, 0x01, 0xc0, 0x6a, 0x8b, 0x24, 0x03, + 0x96, 0x88, 0xbe, 0x97, 0x66, 0x2a, 0xbc, 0x53, 0xc9, 0x83, 0x06, 0x51, + 0x5a, 0x88, 0x65, 0x13, 0x18, 0xe4, 0x3a, 0xed, 0x6b, 0xf1, 0x61, 0x5b, + 0x4c, 0xc8, 0x1e, 0xf4, 0xc2, 0xae, 0x08, 0x5e, 0x2d, 0x5f, 0xf8, 0x12, + 0x7f, 0xa2, 0xfc, 0xbb, 0x21, 0x18, 0x30, 0xda, 0xfe, 0x40, 0xfb, 0x01, + 0xca, 0x2e, 0x37, 0x0e, 0xce, 0xdd, 0x76, 0x87, 0x82, 0x46, 0x0b, 0x3a, + 0x77, 0x8f, 0xc0, 0x72, 0x07, 0x2c, 0x7f, 0x9d, 0x1e, 0x86, 0x5b, 0xed, + 0x27, 0x29, 0xdf, 0x03, 0x97, 0x62, 0xef, 0x44, 0xd3, 0x5b, 0x3d, 0xdb, + 0x9c, 0x5e, 0x1b, 0x7b, 0x39, 0xb4, 0x0b, 0x6d, 0x04, 0x6b, 0xbb, 0xbb, + 0x2c, 0x5f, 0xcf, 0xb3, 0x7a, 0x05, 0x02, 0x03, 0x01, 0x00, 0x01, 0x02, + 0x82, 0x01, 0x00, 0x0a, 0xf9, 0x4a, 0x19, 0x72, 0x88, 0x1b, 0x4e, 0xd8, + 0x2f, 0xef, 0x99, 0x93, 0x32, 0xda, 0x51, 0x21, 0x2e, 0x14, 0x06, 0xf4, + 0xe9, 0x65, 0x1c, 0xf9, 0xd4, 0xcf, 0x1a, 0x51, 0x53, 0xcd, 0x48, 0x33, + 0x8c, 0x30, 0xed, 0xdd, 0x53, 0x6f, 0x29, 0x82, 0xf9, 0xe0, 0x74, 0xde, + 0xb1, 0x13, 0x01, 0x88, 0x8f, 0xce, 0x14, 0xc1, 0x3b, 0x90, 0xb7, 0xcc, + 0x6c, 0xdf, 0x35, 0xa1, 0xf2, 0x1a, 0x3d, 0xbe, 0x19, 0xd7, 0x0a, 0xe4, + 0x67, 0x75, 0xbb, 0xfa, 0x87, 0xf4, 0x03, 0xb5, 0x7f, 0x69, 0xe4, 0x0b, + 0x6a, 0xdc, 0x92, 0x82, 0x54, 0x64, 0x1a, 0x94, 0x2d, 0xe4, 0x63, 0x40, + 0xb2, 0xb4, 0x85, 0x6b, 0xc8, 0x34, 0xba, 0xa2, 0x14, 0x30, 0x47, 0x1a, + 0xeb, 0x90, 0x62, 0x30, 0x43, 0x44, 0x02, 0xc7, 0x0c, 0x30, 0xc0, 0x7f, + 0xa9, 0x47, 0xae, 0xde, 0x68, 0x27, 0x92, 0xaa, 0x11, 0x95, 0xf5, 0x6f, + 0xfc, 0x19, 0x8b, 0x49, 0xa0, 0x77, 0x9d, 0xc6, 0x13, 0x5d, 0x73, 0xff, + 0x45, 0xa2, 0x4c, 0x3b, 0xf3, 0xe1, 0x2d, 0xd7, 0xc4, 0x70, 0xe2, 0x6c, + 0x37, 0x99, 0x4c, 0x7a, 0xa9, 0x27, 0xf8, 0x3a, 0xd6, 0xfd, 0xc5, 0xd8, + 0xfa, 0x2d, 0x0e, 0x71, 0x4b, 0x85, 0x7e, 0xce, 0xcb, 0x1c, 0x79, 0x71, + 0xbd, 0xff, 0x63, 0x03, 0x6b, 0x58, 0x68, 0xe0, 0x14, 0xca, 0x5e, 0x85, + 0xfd, 0xd0, 0xb7, 0xe0, 0x68, 0x14, 0xff, 0x2c, 0x82, 0x22, 0x26, 0x8a, + 0x3f, 0xbf, 0xb0, 0x2a, 0x90, 0xff, 0xc7, 0x72, 0xfc, 0x66, 0x51, 0x3e, + 0x51, 0x9f, 0x82, 0x68, 0x0e, 0xf3, 0x65, 0x74, 0x88, 0xab, 0xb7, 0xe5, + 0x97, 0x5f, 0x0f, 0x3e, 0xe5, 0x3a, 0xbc, 0xa4, 0xa1, 0x50, 0xdd, 0x5c, + 0x94, 0x4b, 0x0c, 0x70, 0x71, 0x48, 0x4e, 0xd0, 0xec, 0x46, 0x8f, 0xdf, + 0xa2, 0x9a, 0xfe, 0xd8, 0x35, 0x1a, 0x2f, 0x02, 0x81, 0x81, 0x00, 0xcf, + 0x73, 0x8c, 0xbe, 0x6d, 0x45, 0x2d, 0x0c, 0x0b, 0x5d, 0x5c, 0x6c, 0x75, + 0x78, 0xcc, 0x35, 0x48, 0xb6, 0x98, 0xf1, 0xb9, 0x64, 0x60, 0x8c, 0x43, + 0xeb, 0x85, 0xab, 0x04, 0xb6, 0x7d, 0x1b, 0x71, 0x75, 0x06, 0xe2, 0xda, + 0x84, 0x68, 0x2e, 0x7f, 0x4c, 0xe3, 0x73, 0xb4, 0xde, 0x51, 0x4b, 0xb6, + 0x51, 0x86, 0x7b, 0xd0, 0xe6, 0x4d, 0xf3, 0xd1, 0xcf, 0x1a, 0xfe, 0x7f, + 0x3a, 0x83, 0xba, 0xb3, 0xe1, 0xff, 0x54, 0x13, 0x93, 0xd7, 0x9c, 0x27, + 0x80, 0xb7, 0x1e, 0x64, 0x9e, 0xf7, 0x32, 0x2b, 0x46, 0x29, 0xf7, 0xf8, + 0x18, 0x6c, 0xf7, 0x4a, 0xbe, 0x4b, 0xee, 0x96, 0x90, 0x8f, 0xa2, 0x16, + 0x22, 0x6a, 0xcc, 0x48, 0x06, 0x74, 0x63, 0x43, 0x7f, 0x27, 0x22, 0x44, + 0x3c, 0x2d, 0x3b, 0x62, 0xf1, 0x1c, 0xb4, 0x27, 0x33, 0x85, 0x26, 0x60, + 0x48, 0x16, 0xcb, 0xef, 0xf8, 0xcd, 0x37, 0x02, 0x81, 0x81, 0x00, 0xce, + 0x15, 0x43, 0x6e, 0x4b, 0x0f, 0xf9, 0x3f, 0x87, 0xc3, 0x41, 0x45, 0x97, + 0xb1, 0x49, 0xc2, 0x19, 0x23, 0x87, 0xe4, 0x24, 0x1c, 0x64, 0xe5, 0x28, + 0xcb, 0x43, 0x10, 0x14, 0x14, 0x0e, 0x19, 0xcb, 0xbb, 0xdb, 0xfd, 0x11, + 0x9d, 0x17, 0x68, 0x78, 0x6d, 0x61, 0x70, 0x63, 0x3a, 0xa1, 0xb3, 0xf3, + 0xa7, 0x5b, 0x0e, 0xff, 0xb7, 0x61, 0x11, 0x54, 0x91, 0x99, 0xe5, 0x91, + 0x32, 0x2d, 0xeb, 0x3f, 0xd8, 0x3e, 0xf7, 0xd4, 0xcb, 0xd2, 0xa3, 0x41, + 0xc1, 0xee, 0xc6, 0x92, 0x13, 0xeb, 0x7f, 0x42, 0x58, 0xf4, 0xd0, 0xb2, + 0x74, 0x1d, 0x8e, 0x87, 0x46, 0xcd, 0x14, 0xb8, 0x16, 0xad, 0xb5, 0xbd, + 0x0d, 0x6c, 0x95, 0x5a, 0x16, 0xbf, 0xe9, 0x53, 0xda, 0xfb, 0xed, 0x83, + 0x51, 0x67, 0xa9, 0x55, 0xab, 0x54, 0x02, 0x95, 0x20, 0xa6, 0x68, 0x17, + 0x53, 0xa8, 0xea, 0x43, 0xe5, 0xb0, 0xa3, 0x02, 0x81, 0x80, 0x67, 0x9c, + 0x32, 0x83, 0x39, 0x57, 0xff, 0x73, 0xb0, 0x89, 0x64, 0x8b, 0xd6, 0xf0, + 0x0a, 0x2d, 0xe2, 0xaf, 0x30, 0x1c, 0x2a, 0x97, 0xf3, 0x90, 0x9a, 0xab, + 0x9b, 0x0b, 0x1b, 0x43, 0x79, 0xa0, 0xa7, 0x3d, 0xe7, 0xbe, 0x8d, 0x9c, + 0xeb, 0xdb, 0xad, 0x40, 0xdd, 0xa9, 0x00, 0x80, 0xb8, 0xe1, 0xb3, 0xa1, + 0x6c, 0x25, 0x92, 0xe4, 0x33, 0xb2, 0xbe, 0xeb, 0x4d, 0x74, 0x26, 0x5f, + 0x37, 0x43, 0x9c, 0x6c, 0x17, 0x76, 0x0a, 0x81, 0x20, 0x82, 0xa1, 0x48, + 0x2c, 0x2d, 0x45, 0xdc, 0x0f, 0x62, 0x43, 0x32, 0xbb, 0xeb, 0x59, 0x41, + 0xf9, 0xca, 0x58, 0xce, 0x4a, 0x66, 0x53, 0x54, 0xc8, 0x28, 0x10, 0x1e, + 0x08, 0x71, 0x16, 0xd8, 0x02, 0x71, 0x41, 0x58, 0xd4, 0x56, 0xcc, 0xf5, + 0xb1, 0x31, 0xa3, 0xed, 0x00, 0x85, 0x09, 0xbf, 0x35, 0x95, 0x41, 0x29, + 0x40, 0x19, 0x83, 0x35, 0x24, 0x69, 0x02, 0x81, 0x80, 0x55, 0x10, 0x0b, + 0xcc, 0x3b, 0xa9, 0x75, 0x3d, 0x16, 0xe1, 0xae, 0x50, 0x76, 0x63, 0x94, + 0x49, 0x4c, 0xad, 0x10, 0xcb, 0x47, 0x68, 0x7c, 0xf0, 0xe5, 0xdc, 0xb8, + 0x6a, 0xab, 0x8e, 0xf7, 0x9f, 0x08, 0x2c, 0x1b, 0x8a, 0xa2, 0xb9, 0x8f, + 0xce, 0xec, 0x5e, 0x61, 0xa8, 0xcd, 0x1c, 0x87, 0x60, 0x4a, 0xc3, 0x1a, + 0x5f, 0xdf, 0x87, 0x26, 0xc6, 0xcb, 0x7c, 0x69, 0xe4, 0x8b, 0x01, 0x06, + 0x59, 0x22, 0xfa, 0x34, 0x4b, 0x81, 0x87, 0x3c, 0x03, 0x6d, 0x02, 0x0a, + 0x77, 0xe6, 0x15, 0xd8, 0xcf, 0xa7, 0x68, 0x26, 0x6c, 0xfa, 0x2b, 0xd9, + 0x83, 0x5a, 0x2d, 0x0c, 0x3b, 0x70, 0x1c, 0xd4, 0x48, 0xbe, 0xa7, 0x0a, + 0xd9, 0xbe, 0xdc, 0xc3, 0x0c, 0x21, 0x33, 0xb3, 0x66, 0xff, 0x1c, 0x1b, + 0xc8, 0x96, 0x76, 0xe8, 0x6f, 0x44, 0x74, 0xbc, 0x9b, 0x1c, 0x7d, 0xc8, + 0xac, 0x21, 0xa8, 0x6e, 0x37, 0x02, 0x81, 0x80, 0x2c, 0x7c, 0xad, 0x1e, + 0x75, 0xf6, 0x69, 0x1d, 0xe7, 0xa6, 0xca, 0x74, 0x7d, 0x67, 0xc8, 0x65, + 0x28, 0x66, 0xc4, 0x43, 0xa6, 0xbd, 0x40, 0x57, 0xae, 0xb7, 0x65, 0x2c, + 0x52, 0xf9, 0xe4, 0xc7, 0x81, 0x7b, 0x56, 0xa3, 0xd2, 0x0d, 0xe8, 0x33, + 0x70, 0xcf, 0x06, 0x84, 0xb3, 0x4e, 0x44, 0x50, 0x75, 0x61, 0x96, 0x86, + 0x4b, 0xb6, 0x2b, 0xad, 0xf0, 0xad, 0x57, 0xd0, 0x37, 0x0d, 0x1d, 0x35, + 0x50, 0xcb, 0x69, 0x22, 0x39, 0x29, 0xb9, 0x3a, 0xd3, 0x29, 0x23, 0x02, + 0x60, 0xf7, 0xab, 0x30, 0x40, 0xda, 0x8e, 0x4d, 0x45, 0x70, 0x26, 0xf4, + 0xa2, 0x0d, 0xd0, 0x64, 0x5d, 0x47, 0x3c, 0x18, 0xf4, 0xd4, 0x52, 0x95, + 0x00, 0xae, 0x84, 0x6b, 0x47, 0xb2, 0x3c, 0x82, 0xd3, 0x72, 0x53, 0xde, + 0x72, 0x2c, 0xf7, 0xc1, 0x22, 0x36, 0xd9, 0x18, 0x56, 0xfe, 0x39, 0x28, + 0x33, 0xe0, 0xdb, 0x03 +}; + +const size_t kOEMPrivateKeySize_Prod = sizeof(kOEMPrivateKey_Prod); + +// From the team shared drive file +// oem-7913-leaf-and-intermediate-certs-test-key-2-carmichael.p7b, size 2353. +const uint8_t kOEMPublicCert_Prod[] = { + 0x30, 0x82, 0x09, 0x2d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, + 0x01, 0x07, 0x02, 0xa0, 0x82, 0x09, 0x1e, 0x30, 0x82, 0x09, 0x1a, 0x02, + 0x01, 0x01, 0x31, 0x00, 0x30, 0x0f, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, + 0xf7, 0x0d, 0x01, 0x07, 0x01, 0xa0, 0x02, 0x04, 0x00, 0xa0, 0x82, 0x08, + 0xfe, 0x30, 0x82, 0x03, 0x71, 0x30, 0x82, 0x02, 0x59, 0xa0, 0x03, 0x02, + 0x01, 0x02, 0x02, 0x11, 0x00, 0xc2, 0x8d, 0x20, 0x22, 0x82, 0x8b, 0x9e, + 0x63, 0x9d, 0x15, 0x89, 0x2c, 0xa9, 0x8f, 0xd9, 0x5d, 0x30, 0x0d, 0x06, + 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, + 0x30, 0x6b, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, + 0x02, 0x55, 0x53, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x08, + 0x0c, 0x02, 0x57, 0x41, 0x31, 0x11, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04, + 0x07, 0x0c, 0x08, 0x4b, 0x69, 0x72, 0x6b, 0x6c, 0x61, 0x6e, 0x64, 0x31, + 0x0f, 0x30, 0x0d, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x06, 0x47, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x31, 0x11, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04, + 0x0b, 0x0c, 0x08, 0x57, 0x69, 0x64, 0x65, 0x76, 0x69, 0x6e, 0x65, 0x31, + 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x0f, 0x73, 0x79, + 0x73, 0x74, 0x65, 0x6d, 0x20, 0x69, 0x64, 0x3a, 0x20, 0x37, 0x39, 0x31, + 0x33, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x38, 0x30, 0x31, 0x31, 0x31, 0x31, + 0x33, 0x32, 0x36, 0x32, 0x32, 0x5a, 0x17, 0x0d, 0x33, 0x38, 0x30, 0x31, + 0x30, 0x36, 0x31, 0x33, 0x32, 0x36, 0x32, 0x32, 0x5a, 0x30, 0x65, 0x31, + 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x09, 0x37, 0x39, + 0x31, 0x33, 0x2d, 0x6c, 0x65, 0x61, 0x66, 0x31, 0x0b, 0x30, 0x09, 0x06, + 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x0b, 0x30, 0x09, + 0x06, 0x03, 0x55, 0x04, 0x08, 0x0c, 0x02, 0x57, 0x41, 0x31, 0x11, 0x30, + 0x0f, 0x06, 0x03, 0x55, 0x04, 0x07, 0x0c, 0x08, 0x4b, 0x69, 0x72, 0x6b, + 0x6c, 0x61, 0x6e, 0x64, 0x31, 0x0f, 0x30, 0x0d, 0x06, 0x03, 0x55, 0x04, + 0x0a, 0x0c, 0x06, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x31, 0x11, 0x30, + 0x0f, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x0c, 0x08, 0x57, 0x69, 0x64, 0x65, + 0x76, 0x69, 0x6e, 0x65, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, + 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, + 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, + 0x00, 0xa7, 0x00, 0x36, 0x60, 0x65, 0xdc, 0xbd, 0x54, 0x5a, 0x2a, 0x40, + 0xb4, 0xe1, 0x15, 0x94, 0x58, 0x11, 0x4f, 0x94, 0x58, 0xdd, 0xde, 0xa7, + 0x1f, 0x3c, 0x2c, 0xe0, 0x88, 0x09, 0x29, 0x61, 0x57, 0x67, 0x5e, 0x56, + 0x7e, 0xee, 0x27, 0x8f, 0x59, 0x34, 0x9a, 0x2a, 0xaa, 0x9d, 0xb4, 0x4e, + 0xfa, 0xa7, 0x6a, 0xd4, 0xc9, 0x7a, 0x53, 0xc1, 0x4e, 0x9f, 0xe3, 0x34, + 0xf7, 0x3d, 0xb7, 0xc9, 0x10, 0x47, 0x4f, 0x28, 0xda, 0x3f, 0xce, 0x31, + 0x7b, 0xfd, 0x06, 0x10, 0xeb, 0xf7, 0xbe, 0x92, 0xf9, 0xaf, 0xfb, 0x3e, + 0x68, 0xda, 0xee, 0x1a, 0x64, 0x4c, 0xf3, 0x29, 0xf2, 0x73, 0x9e, 0x39, + 0xd8, 0xf6, 0x6f, 0xd8, 0xb2, 0x80, 0x82, 0x71, 0x8e, 0xb5, 0xa4, 0xf2, + 0xc2, 0x3e, 0xcd, 0x0a, 0xca, 0xb6, 0x04, 0xcd, 0x9a, 0x13, 0x8b, 0x54, + 0x73, 0x54, 0x25, 0x54, 0x8c, 0xbe, 0x98, 0x7a, 0x67, 0xad, 0xda, 0xb3, + 0x4e, 0xb3, 0xfa, 0x82, 0xa8, 0x4a, 0x67, 0x98, 0x56, 0x57, 0x54, 0x71, + 0xcd, 0x12, 0x7f, 0xed, 0xa3, 0x01, 0xc0, 0x6a, 0x8b, 0x24, 0x03, 0x96, + 0x88, 0xbe, 0x97, 0x66, 0x2a, 0xbc, 0x53, 0xc9, 0x83, 0x06, 0x51, 0x5a, + 0x88, 0x65, 0x13, 0x18, 0xe4, 0x3a, 0xed, 0x6b, 0xf1, 0x61, 0x5b, 0x4c, + 0xc8, 0x1e, 0xf4, 0xc2, 0xae, 0x08, 0x5e, 0x2d, 0x5f, 0xf8, 0x12, 0x7f, + 0xa2, 0xfc, 0xbb, 0x21, 0x18, 0x30, 0xda, 0xfe, 0x40, 0xfb, 0x01, 0xca, + 0x2e, 0x37, 0x0e, 0xce, 0xdd, 0x76, 0x87, 0x82, 0x46, 0x0b, 0x3a, 0x77, + 0x8f, 0xc0, 0x72, 0x07, 0x2c, 0x7f, 0x9d, 0x1e, 0x86, 0x5b, 0xed, 0x27, + 0x29, 0xdf, 0x03, 0x97, 0x62, 0xef, 0x44, 0xd3, 0x5b, 0x3d, 0xdb, 0x9c, + 0x5e, 0x1b, 0x7b, 0x39, 0xb4, 0x0b, 0x6d, 0x04, 0x6b, 0xbb, 0xbb, 0x2c, + 0x5f, 0xcf, 0xb3, 0x7a, 0x05, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x16, + 0x30, 0x14, 0x30, 0x12, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6, + 0x79, 0x04, 0x01, 0x01, 0x04, 0x04, 0x02, 0x02, 0x1e, 0xe9, 0x30, 0x0d, + 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, + 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x88, 0x95, 0xec, 0xcd, 0x8b, 0xa7, + 0x51, 0xda, 0x74, 0x81, 0xa5, 0x39, 0x62, 0x1a, 0x0e, 0x2e, 0xde, 0x3c, + 0x37, 0xea, 0xad, 0x7c, 0xee, 0x9b, 0x26, 0x8e, 0xe2, 0xd6, 0x34, 0xcd, + 0xb7, 0x70, 0xba, 0xbf, 0xa0, 0xa3, 0xfe, 0xb3, 0x4b, 0xbc, 0xf4, 0x1c, + 0x72, 0x66, 0x81, 0xd5, 0x09, 0x33, 0x78, 0x0c, 0x61, 0x21, 0xa8, 0xf1, + 0xe2, 0xc9, 0xe2, 0x83, 0xc2, 0x19, 0x02, 0xf2, 0xe8, 0xab, 0x17, 0x36, + 0x3a, 0x0b, 0x20, 0xaf, 0x0f, 0xae, 0x2e, 0x73, 0x68, 0xac, 0x15, 0xee, + 0x9c, 0xc0, 0x92, 0x03, 0x7e, 0x95, 0x63, 0xaa, 0xad, 0x15, 0x96, 0x43, + 0x20, 0x3b, 0xe5, 0x9b, 0x1f, 0xca, 0x02, 0xba, 0xf0, 0x07, 0x76, 0x80, + 0xd7, 0xa3, 0x1a, 0xeb, 0xc8, 0xdb, 0x03, 0x7b, 0x43, 0x56, 0xe5, 0x96, + 0x6b, 0x86, 0xfe, 0x08, 0x58, 0x8a, 0x84, 0xbd, 0xe9, 0x47, 0x18, 0xee, + 0xb2, 0xa8, 0x05, 0x7b, 0xf0, 0xfd, 0xaa, 0xb9, 0x85, 0xcd, 0x7a, 0x0e, + 0x6b, 0x6c, 0x9f, 0xc6, 0x75, 0xd2, 0x2a, 0xfe, 0x5b, 0xf3, 0xb7, 0x31, + 0x6c, 0xac, 0xe3, 0x00, 0x9f, 0xe7, 0xdd, 0xe3, 0x81, 0xc1, 0x36, 0xc3, + 0x1c, 0x5f, 0xdf, 0xf2, 0xc3, 0x5e, 0xfa, 0x55, 0x32, 0xd8, 0x5c, 0xa8, + 0xe5, 0xcc, 0xb6, 0x4a, 0xe9, 0xe2, 0xcc, 0x38, 0x44, 0x07, 0x46, 0x59, + 0x34, 0x84, 0x79, 0xf9, 0xee, 0x3c, 0x4b, 0x48, 0x90, 0xab, 0x73, 0xb0, + 0xa1, 0x92, 0xc3, 0xd6, 0x83, 0x87, 0x81, 0xca, 0x12, 0x81, 0xd6, 0x5d, + 0xf7, 0x6f, 0x7a, 0x35, 0x5e, 0x4f, 0x02, 0x66, 0x8a, 0x47, 0x88, 0x82, + 0xab, 0xf0, 0x12, 0x1d, 0xb9, 0x75, 0x3b, 0x7b, 0xa8, 0x36, 0x15, 0xef, + 0xa8, 0x12, 0x0e, 0x53, 0xb4, 0x83, 0x78, 0x53, 0xc0, 0x52, 0xae, 0xa6, + 0x0a, 0xa0, 0x53, 0xdc, 0x1c, 0x15, 0x22, 0xdd, 0x17, 0x98, 0x30, 0x82, + 0x05, 0x85, 0x30, 0x82, 0x03, 0x6d, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, + 0x10, 0x03, 0xb1, 0xf7, 0x58, 0xdf, 0x1d, 0xe3, 0x25, 0x00, 0x0b, 0x10, + 0x3d, 0xd5, 0xe6, 0xe4, 0x64, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, + 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x7e, 0x31, 0x0b, + 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, + 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x0c, 0x0a, 0x57, 0x61, + 0x73, 0x68, 0x69, 0x6e, 0x67, 0x74, 0x6f, 0x6e, 0x31, 0x11, 0x30, 0x0f, + 0x06, 0x03, 0x55, 0x04, 0x07, 0x0c, 0x08, 0x4b, 0x69, 0x72, 0x6b, 0x6c, + 0x61, 0x6e, 0x64, 0x31, 0x0f, 0x30, 0x0d, 0x06, 0x03, 0x55, 0x04, 0x0a, + 0x0c, 0x06, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x31, 0x11, 0x30, 0x0f, + 0x06, 0x03, 0x55, 0x04, 0x0b, 0x0c, 0x08, 0x57, 0x69, 0x64, 0x65, 0x76, + 0x69, 0x6e, 0x65, 0x31, 0x23, 0x30, 0x21, 0x06, 0x03, 0x55, 0x04, 0x03, + 0x0c, 0x1a, 0x77, 0x69, 0x64, 0x65, 0x76, 0x69, 0x6e, 0x65, 0x2e, 0x63, + 0x6f, 0x6d, 0x2f, 0x6f, 0x65, 0x6d, 0x2d, 0x72, 0x6f, 0x6f, 0x74, 0x2d, + 0x70, 0x72, 0x6f, 0x64, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x37, 0x31, 0x31, + 0x31, 0x38, 0x30, 0x31, 0x31, 0x33, 0x33, 0x35, 0x5a, 0x17, 0x0d, 0x32, + 0x37, 0x31, 0x31, 0x31, 0x38, 0x30, 0x31, 0x31, 0x33, 0x31, 0x33, 0x5a, + 0x30, 0x6b, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, + 0x02, 0x55, 0x53, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x08, + 0x0c, 0x02, 0x57, 0x41, 0x31, 0x11, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04, + 0x07, 0x0c, 0x08, 0x4b, 0x69, 0x72, 0x6b, 0x6c, 0x61, 0x6e, 0x64, 0x31, + 0x0f, 0x30, 0x0d, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x06, 0x47, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x31, 0x11, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04, + 0x0b, 0x0c, 0x08, 0x57, 0x69, 0x64, 0x65, 0x76, 0x69, 0x6e, 0x65, 0x31, + 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x0f, 0x73, 0x79, + 0x73, 0x74, 0x65, 0x6d, 0x20, 0x69, 0x64, 0x3a, 0x20, 0x37, 0x39, 0x31, + 0x33, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, + 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, + 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xae, 0xc8, + 0x71, 0xae, 0x08, 0x0c, 0x06, 0x06, 0x2d, 0x81, 0x7c, 0xa9, 0x8b, 0xb3, + 0xd6, 0x66, 0xe4, 0xf6, 0x08, 0x5e, 0x5a, 0x75, 0xe8, 0x74, 0x61, 0x7a, + 0x88, 0xca, 0x85, 0x14, 0x0d, 0x58, 0xa4, 0x09, 0x19, 0x6c, 0x60, 0xc9, + 0xad, 0x91, 0x1c, 0xbf, 0x04, 0xb3, 0x47, 0x10, 0x63, 0x7f, 0x02, 0x58, + 0xc2, 0x1e, 0xbd, 0xcc, 0x07, 0x77, 0xaa, 0x7e, 0x14, 0xa8, 0xc2, 0x01, + 0xcd, 0xe8, 0x46, 0x60, 0x53, 0x6f, 0x2f, 0xda, 0x17, 0x2d, 0x4d, 0x9d, + 0x0e, 0x5d, 0xb5, 0x50, 0x95, 0xae, 0xab, 0x6e, 0x43, 0xe3, 0xb0, 0x00, + 0x12, 0xb4, 0x05, 0x82, 0x4a, 0x2b, 0x14, 0x63, 0x0d, 0x1f, 0x06, 0x12, + 0xaa, 0xe1, 0x9d, 0xe7, 0xba, 0xda, 0xe3, 0xfc, 0x7c, 0x6c, 0x73, 0xae, + 0x56, 0xf8, 0xab, 0xf7, 0x51, 0x93, 0x31, 0xef, 0x8f, 0xe4, 0xb6, 0x01, + 0x2c, 0xeb, 0x7b, 0xe4, 0xd8, 0xb3, 0xea, 0x70, 0x37, 0x89, 0x05, 0xa9, + 0x51, 0x57, 0x72, 0x98, 0x9e, 0xa8, 0x46, 0xdb, 0xeb, 0x7a, 0x38, 0x2b, + 0x2f, 0xc0, 0x27, 0xb7, 0xc2, 0xe1, 0x9a, 0x17, 0xdf, 0xf5, 0xd6, 0x9c, + 0xd5, 0x8c, 0xb8, 0x66, 0x42, 0xd5, 0x04, 0x1e, 0x7c, 0x36, 0x4c, 0x1e, + 0x3e, 0x45, 0x51, 0x4d, 0x41, 0x72, 0x22, 0x53, 0x3d, 0xf4, 0x57, 0x7c, + 0x6c, 0x33, 0x34, 0x24, 0x45, 0xdf, 0x84, 0x87, 0x4a, 0xa6, 0xcb, 0x7c, + 0x03, 0xa3, 0xaa, 0x8e, 0x2d, 0x82, 0x01, 0x27, 0x87, 0x74, 0x82, 0x1a, + 0xbc, 0x0f, 0x76, 0x69, 0xab, 0xe0, 0x4e, 0x70, 0xbe, 0x37, 0xfc, 0xc8, + 0x2c, 0x91, 0x17, 0x4f, 0xd5, 0x26, 0x3b, 0x7b, 0x90, 0xb5, 0x2d, 0x64, + 0xba, 0xf7, 0xd2, 0x8a, 0xb4, 0x8f, 0x38, 0x9d, 0x8e, 0xba, 0xe7, 0x5c, + 0x52, 0xf1, 0x0a, 0xb8, 0xc0, 0x1b, 0xb6, 0xb1, 0x70, 0x7e, 0x47, 0x59, + 0x94, 0x59, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x10, 0x30, + 0x82, 0x01, 0x0c, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, + 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, + 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, + 0x02, 0x02, 0x04, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, + 0x04, 0x14, 0x4b, 0xcb, 0xdf, 0xaa, 0x02, 0xde, 0x8d, 0xc3, 0xe7, 0xe5, + 0x85, 0xdb, 0x2e, 0x8a, 0xbe, 0x75, 0x6b, 0x8a, 0x67, 0x58, 0x30, 0x81, + 0xb2, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x81, 0xaa, 0x30, 0x81, 0xa7, + 0x80, 0x14, 0x04, 0x94, 0x66, 0xaa, 0xf9, 0x61, 0x89, 0xb6, 0xdb, 0xb5, + 0xf7, 0x13, 0x38, 0x3d, 0x62, 0x84, 0xb8, 0x18, 0x0a, 0x8f, 0xa1, 0x81, + 0x83, 0xa4, 0x81, 0x80, 0x30, 0x7e, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, + 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x13, 0x30, 0x11, 0x06, + 0x03, 0x55, 0x04, 0x08, 0x0c, 0x0a, 0x57, 0x61, 0x73, 0x68, 0x69, 0x6e, + 0x67, 0x74, 0x6f, 0x6e, 0x31, 0x11, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04, + 0x07, 0x0c, 0x08, 0x4b, 0x69, 0x72, 0x6b, 0x6c, 0x61, 0x6e, 0x64, 0x31, + 0x0f, 0x30, 0x0d, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x06, 0x47, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x31, 0x11, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04, + 0x0b, 0x0c, 0x08, 0x57, 0x69, 0x64, 0x65, 0x76, 0x69, 0x6e, 0x65, 0x31, + 0x23, 0x30, 0x21, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x1a, 0x77, 0x69, + 0x64, 0x65, 0x76, 0x69, 0x6e, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, + 0x65, 0x6d, 0x2d, 0x72, 0x6f, 0x6f, 0x74, 0x2d, 0x70, 0x72, 0x6f, 0x64, + 0x82, 0x09, 0x00, 0xdf, 0x86, 0x05, 0x31, 0x01, 0xbe, 0x9a, 0x9a, 0x30, + 0x12, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6, 0x79, 0x04, 0x01, + 0x01, 0x04, 0x04, 0x02, 0x02, 0x1e, 0xe9, 0x30, 0x0d, 0x06, 0x09, 0x2a, + 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, + 0x02, 0x01, 0x00, 0x61, 0x3f, 0x2f, 0x43, 0xe4, 0xbe, 0x66, 0x34, 0xef, + 0x92, 0x06, 0xe9, 0x88, 0xba, 0x6a, 0x1d, 0x4f, 0x54, 0x5a, 0x97, 0xb1, + 0x75, 0xd7, 0x93, 0xf8, 0x45, 0xc6, 0x83, 0x92, 0x36, 0xfd, 0x55, 0xa9, + 0x21, 0x0b, 0xdc, 0xf6, 0xae, 0x11, 0xdc, 0x62, 0x21, 0x44, 0xbd, 0x04, + 0x1d, 0x58, 0x2c, 0x03, 0xf8, 0xe4, 0xe2, 0x1e, 0xba, 0xe6, 0xdd, 0x19, + 0xdd, 0x56, 0xfd, 0xce, 0x06, 0x73, 0x5f, 0x94, 0x1e, 0xb6, 0x03, 0xdb, + 0x3d, 0x7b, 0xab, 0xab, 0x72, 0x64, 0x7b, 0xde, 0x7d, 0x4d, 0xcf, 0x7e, + 0xf0, 0x91, 0x29, 0xc1, 0x77, 0x13, 0xc2, 0x6f, 0x80, 0xab, 0x7a, 0xa8, + 0xce, 0xb0, 0x1c, 0x2a, 0xc5, 0x9c, 0xfb, 0x0b, 0xe5, 0x9f, 0x9c, 0x1b, + 0xc9, 0x4b, 0x58, 0xdf, 0x96, 0x18, 0xf7, 0x67, 0x67, 0x89, 0xa4, 0xe9, + 0x14, 0x48, 0xac, 0xfa, 0x9d, 0x86, 0x2a, 0xeb, 0x75, 0x2c, 0x2b, 0xbf, + 0x63, 0x7d, 0xc7, 0x4e, 0x7e, 0xad, 0x39, 0x2d, 0xb4, 0x7c, 0x07, 0xa5, + 0x5a, 0xe8, 0x3a, 0xd4, 0xf5, 0x0c, 0x4f, 0xf3, 0xa2, 0x9c, 0x3c, 0x32, + 0xed, 0x9d, 0x4b, 0x49, 0x05, 0xbc, 0x1f, 0xa0, 0x13, 0xe6, 0xdd, 0x82, + 0x79, 0x06, 0x31, 0x3b, 0xc6, 0x97, 0xec, 0x8d, 0xaa, 0x4f, 0xef, 0x14, + 0x3c, 0x21, 0xf6, 0x72, 0xb2, 0x09, 0x42, 0xc7, 0x74, 0xfe, 0xef, 0x70, + 0xbd, 0xe9, 0x85, 0x41, 0x30, 0x0b, 0xb3, 0x6b, 0x59, 0x0c, 0x0f, 0x11, + 0x75, 0xd4, 0xbb, 0xb1, 0xdf, 0xb1, 0xdf, 0xb3, 0xfa, 0xb3, 0x3a, 0x43, + 0x17, 0x7d, 0x8a, 0x82, 0xae, 0xa2, 0x07, 0xf8, 0x83, 0x51, 0xfb, 0x16, + 0xfb, 0x64, 0xb6, 0x46, 0xda, 0xbe, 0x32, 0x2b, 0xc0, 0xee, 0x78, 0x2a, + 0x84, 0xa9, 0x54, 0x0a, 0xf9, 0x2d, 0x61, 0x65, 0xde, 0xa5, 0x97, 0x66, + 0x79, 0x02, 0xf8, 0x97, 0x17, 0xe2, 0xd4, 0x9f, 0x9e, 0xac, 0xcc, 0xae, + 0x99, 0x9a, 0x03, 0x04, 0xbb, 0x45, 0xfe, 0xb2, 0xf5, 0x80, 0xba, 0xbf, + 0xdd, 0x24, 0xe5, 0xe6, 0x1e, 0x5d, 0x36, 0xa5, 0x87, 0x0c, 0xdf, 0x60, + 0x81, 0x6f, 0xb7, 0x5f, 0xb9, 0x1f, 0xca, 0x75, 0x3c, 0x1a, 0x63, 0xb0, + 0xeb, 0xe6, 0x95, 0x86, 0x0d, 0xae, 0xa6, 0xc9, 0x2a, 0x94, 0xf1, 0xd0, + 0xbe, 0x75, 0xc8, 0xf8, 0x07, 0xd7, 0x88, 0xff, 0xec, 0xf9, 0xcd, 0x49, + 0xc6, 0xfe, 0x4d, 0x7f, 0x44, 0x1e, 0xd8, 0xaf, 0xa9, 0x72, 0x27, 0x98, + 0xe2, 0x5a, 0x08, 0xea, 0x55, 0xd3, 0xb3, 0xea, 0xdc, 0x76, 0x69, 0x51, + 0x10, 0x01, 0x46, 0x7d, 0x33, 0x94, 0x9c, 0x94, 0xef, 0xfe, 0x76, 0x1c, + 0xc6, 0xd7, 0x15, 0x53, 0x3e, 0x8d, 0x3d, 0x29, 0x9a, 0x58, 0x6a, 0xf1, + 0x75, 0x9e, 0xea, 0x1b, 0x4c, 0xf0, 0x47, 0x76, 0xac, 0xc6, 0xa2, 0x32, + 0x44, 0x40, 0xdf, 0xfe, 0xff, 0x9d, 0xf4, 0xe2, 0xc2, 0xfa, 0xa1, 0x5f, + 0x2e, 0x66, 0xe9, 0x97, 0xcb, 0x27, 0x26, 0x6e, 0x53, 0xe4, 0xe8, 0x86, + 0x2c, 0xea, 0xd3, 0x69, 0x6c, 0x61, 0x4f, 0xfe, 0xc1, 0xc9, 0x8b, 0x05, + 0x92, 0x6f, 0x47, 0x96, 0xce, 0xf0, 0x33, 0xfa, 0x7c, 0x78, 0x24, 0x9b, + 0xd7, 0x8d, 0x36, 0x56, 0x37, 0x86, 0xbc, 0x72, 0x5a, 0xf9, 0xb9, 0xb0, + 0x93, 0xf0, 0x81, 0x78, 0x10, 0xf2, 0xb0, 0xc2, 0x79, 0x91, 0x5e, 0xcf, + 0xbc, 0x8c, 0xf2, 0x32, 0x0f, 0xf7, 0x2d, 0x30, 0xd8, 0x13, 0x77, 0x4f, + 0x78, 0x9e, 0x40, 0x8d, 0xe6, 0x3a, 0x98, 0xb2, 0xaa, 0x13, 0x4d, 0x25, + 0x49, 0x34, 0x6c, 0x80, 0x9e, 0x19, 0x03, 0xdb, 0xcd, 0xf5, 0xb1, 0x54, + 0x74, 0x1b, 0x67, 0x3c, 0x46, 0xac, 0x3e, 0x5d, 0xa2, 0xd9, 0x13, 0x83, + 0x30, 0xeb, 0x82, 0x3b, 0x06, 0xab, 0x3c, 0x39, 0x7d, 0xd0, 0x68, 0x31, + 0x00 +}; + +const size_t kOEMPublicCertSize_Prod = sizeof(kOEMPublicCert_Prod); + +// Refer to the following in main modules. +// This level of indirection is present so new OEM Certificates can be +// added and then selected for use at compile time or run time. + +const uint32_t kOEMSystemId = kOEMSystemId_Prod; + +const uint8_t* kOEMPrivateKey = kOEMPrivateKey_Prod; +const uint8_t* kOEMPublicCert = kOEMPublicCert_Prod; + +const size_t kOEMPrivateKeySize = kOEMPrivateKeySize_Prod; +const size_t kOEMPublicCertSize = kOEMPublicCertSize_Prod; + +} // namespace wvoec_ref diff --git a/oemcrypto/ref/src/oem_cert.h b/oemcrypto/ref/src/oem_cert.h new file mode 100644 index 0000000..3d0476b --- /dev/null +++ b/oemcrypto/ref/src/oem_cert.h @@ -0,0 +1,21 @@ +// This header is used to access the OEM certificate if one is in use. +#ifndef OEM_CERT_H_ +#define OEM_CERT_H_ + +#include +#include + +namespace wvoec_ref { + +// Refer to the following in main modules +extern const uint32_t kOEMSystemId; + +extern const uint8_t* kOEMPrivateKey; +extern const uint8_t* kOEMPublicCert; + +extern const size_t kOEMPrivateKeySize; +extern const size_t kOEMPublicCertSize; + +} // namespace wvoec_ref + +#endif // OEM_CERT_H_ diff --git a/oemcrypto/ref/src/oemcrypto_auth_ref.cpp b/oemcrypto/ref/src/oemcrypto_auth_ref.cpp new file mode 100644 index 0000000..9ed3f6d --- /dev/null +++ b/oemcrypto/ref/src/oemcrypto_auth_ref.cpp @@ -0,0 +1,203 @@ +// 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. +// +// Reference implementation of OEMCrypto APIs +// +#include "oemcrypto_auth_ref.h" + +#include + +#include "keys.h" +#include "log.h" +#include "oemcrypto_rsa_key_shared.h" + +namespace { + +// A 2048 bit RSA key in PKCS#8 PrivateKeyInfo format +// This is the RSA Test Key. This key is not derived +// from any Widevine authentication root. +static const uint8_t kTestRSAPKCS8PrivateKeyInfo2_2048[] = { + 0x30, 0x82, 0x04, 0xbc, 0x02, 0x01, 0x00, 0x30, + 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, + 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x04, 0x82, + 0x04, 0xa6, 0x30, 0x82, 0x04, 0xa2, 0x02, 0x01, + 0x00, 0x02, 0x82, 0x01, 0x01, 0x00, 0xa7, 0x00, + 0x36, 0x60, 0x65, 0xdc, 0xbd, 0x54, 0x5a, 0x2a, + 0x40, 0xb4, 0xe1, 0x15, 0x94, 0x58, 0x11, 0x4f, + 0x94, 0x58, 0xdd, 0xde, 0xa7, 0x1f, 0x3c, 0x2c, + 0xe0, 0x88, 0x09, 0x29, 0x61, 0x57, 0x67, 0x5e, + 0x56, 0x7e, 0xee, 0x27, 0x8f, 0x59, 0x34, 0x9a, + 0x2a, 0xaa, 0x9d, 0xb4, 0x4e, 0xfa, 0xa7, 0x6a, + 0xd4, 0xc9, 0x7a, 0x53, 0xc1, 0x4e, 0x9f, 0xe3, + 0x34, 0xf7, 0x3d, 0xb7, 0xc9, 0x10, 0x47, 0x4f, + 0x28, 0xda, 0x3f, 0xce, 0x31, 0x7b, 0xfd, 0x06, + 0x10, 0xeb, 0xf7, 0xbe, 0x92, 0xf9, 0xaf, 0xfb, + 0x3e, 0x68, 0xda, 0xee, 0x1a, 0x64, 0x4c, 0xf3, + 0x29, 0xf2, 0x73, 0x9e, 0x39, 0xd8, 0xf6, 0x6f, + 0xd8, 0xb2, 0x80, 0x82, 0x71, 0x8e, 0xb5, 0xa4, + 0xf2, 0xc2, 0x3e, 0xcd, 0x0a, 0xca, 0xb6, 0x04, + 0xcd, 0x9a, 0x13, 0x8b, 0x54, 0x73, 0x54, 0x25, + 0x54, 0x8c, 0xbe, 0x98, 0x7a, 0x67, 0xad, 0xda, + 0xb3, 0x4e, 0xb3, 0xfa, 0x82, 0xa8, 0x4a, 0x67, + 0x98, 0x56, 0x57, 0x54, 0x71, 0xcd, 0x12, 0x7f, + 0xed, 0xa3, 0x01, 0xc0, 0x6a, 0x8b, 0x24, 0x03, + 0x96, 0x88, 0xbe, 0x97, 0x66, 0x2a, 0xbc, 0x53, + 0xc9, 0x83, 0x06, 0x51, 0x5a, 0x88, 0x65, 0x13, + 0x18, 0xe4, 0x3a, 0xed, 0x6b, 0xf1, 0x61, 0x5b, + 0x4c, 0xc8, 0x1e, 0xf4, 0xc2, 0xae, 0x08, 0x5e, + 0x2d, 0x5f, 0xf8, 0x12, 0x7f, 0xa2, 0xfc, 0xbb, + 0x21, 0x18, 0x30, 0xda, 0xfe, 0x40, 0xfb, 0x01, + 0xca, 0x2e, 0x37, 0x0e, 0xce, 0xdd, 0x76, 0x87, + 0x82, 0x46, 0x0b, 0x3a, 0x77, 0x8f, 0xc0, 0x72, + 0x07, 0x2c, 0x7f, 0x9d, 0x1e, 0x86, 0x5b, 0xed, + 0x27, 0x29, 0xdf, 0x03, 0x97, 0x62, 0xef, 0x44, + 0xd3, 0x5b, 0x3d, 0xdb, 0x9c, 0x5e, 0x1b, 0x7b, + 0x39, 0xb4, 0x0b, 0x6d, 0x04, 0x6b, 0xbb, 0xbb, + 0x2c, 0x5f, 0xcf, 0xb3, 0x7a, 0x05, 0x02, 0x03, + 0x01, 0x00, 0x01, 0x02, 0x82, 0x01, 0x00, 0x5e, + 0x79, 0x65, 0x49, 0xa5, 0x76, 0x79, 0xf9, 0x05, + 0x45, 0x0f, 0xf4, 0x03, 0xbd, 0xa4, 0x7d, 0x29, + 0xd5, 0xde, 0x33, 0x63, 0xd8, 0xb8, 0xac, 0x97, + 0xeb, 0x3f, 0x5e, 0x55, 0xe8, 0x7d, 0xf3, 0xe7, + 0x3b, 0x5c, 0x2d, 0x54, 0x67, 0x36, 0xd6, 0x1d, + 0x46, 0xf5, 0xca, 0x2d, 0x8b, 0x3a, 0x7e, 0xdc, + 0x45, 0x38, 0x79, 0x7e, 0x65, 0x71, 0x5f, 0x1c, + 0x5e, 0x79, 0xb1, 0x40, 0xcd, 0xfe, 0xc5, 0xe1, + 0xc1, 0x6b, 0x78, 0x04, 0x4e, 0x8e, 0x79, 0xf9, + 0x0a, 0xfc, 0x79, 0xb1, 0x5e, 0xb3, 0x60, 0xe3, + 0x68, 0x7b, 0xc6, 0xef, 0xcb, 0x71, 0x4c, 0xba, + 0xa7, 0x79, 0x5c, 0x7a, 0x81, 0xd1, 0x71, 0xe7, + 0x00, 0x21, 0x13, 0xe2, 0x55, 0x69, 0x0e, 0x75, + 0xbe, 0x09, 0xc3, 0x4f, 0xa9, 0xc9, 0x68, 0x22, + 0x0e, 0x97, 0x8d, 0x89, 0x6e, 0xf1, 0xe8, 0x88, + 0x7a, 0xd1, 0xd9, 0x09, 0x5d, 0xd3, 0x28, 0x78, + 0x25, 0x0b, 0x1c, 0x47, 0x73, 0x25, 0xcc, 0x21, + 0xb6, 0xda, 0xc6, 0x24, 0x5a, 0xd0, 0x37, 0x14, + 0x46, 0xc7, 0x94, 0x69, 0xe4, 0x43, 0x6f, 0x47, + 0xde, 0x00, 0x33, 0x4d, 0x8f, 0x95, 0x72, 0xfa, + 0x68, 0x71, 0x17, 0x66, 0x12, 0x1a, 0x87, 0x27, + 0xf7, 0xef, 0x7e, 0xe0, 0x35, 0x58, 0xf2, 0x4d, + 0x6f, 0x35, 0x01, 0xaa, 0x96, 0xe2, 0x3d, 0x51, + 0x13, 0x86, 0x9c, 0x79, 0xd0, 0xb7, 0xb6, 0x64, + 0xe8, 0x86, 0x65, 0x50, 0xbf, 0xcc, 0x27, 0x53, + 0x1f, 0x51, 0xd4, 0xca, 0xbe, 0xf5, 0xdd, 0x77, + 0x70, 0x98, 0x0f, 0xee, 0xa8, 0x96, 0x07, 0x5f, + 0x45, 0x6a, 0x7a, 0x0d, 0x03, 0x9c, 0x4f, 0x29, + 0xf6, 0x06, 0xf3, 0x5d, 0x58, 0x6c, 0x47, 0xd0, + 0x96, 0xa9, 0x03, 0x17, 0xbb, 0x4e, 0xc9, 0x21, + 0xe0, 0xac, 0xcd, 0x78, 0x78, 0xb2, 0xfe, 0x81, + 0xb2, 0x51, 0x53, 0xa6, 0x1f, 0x98, 0x45, 0x02, + 0x81, 0x81, 0x00, 0xcf, 0x73, 0x8c, 0xbe, 0x6d, + 0x45, 0x2d, 0x0c, 0x0b, 0x5d, 0x5c, 0x6c, 0x75, + 0x78, 0xcc, 0x35, 0x48, 0xb6, 0x98, 0xf1, 0xb9, + 0x64, 0x60, 0x8c, 0x43, 0xeb, 0x85, 0xab, 0x04, + 0xb6, 0x7d, 0x1b, 0x71, 0x75, 0x06, 0xe2, 0xda, + 0x84, 0x68, 0x2e, 0x7f, 0x4c, 0xe3, 0x73, 0xb4, + 0xde, 0x51, 0x4b, 0xb6, 0x51, 0x86, 0x7b, 0xd0, + 0xe6, 0x4d, 0xf3, 0xd1, 0xcf, 0x1a, 0xfe, 0x7f, + 0x3a, 0x83, 0xba, 0xb3, 0xe1, 0xff, 0x54, 0x13, + 0x93, 0xd7, 0x9c, 0x27, 0x80, 0xb7, 0x1e, 0x64, + 0x9e, 0xf7, 0x32, 0x2b, 0x46, 0x29, 0xf7, 0xf8, + 0x18, 0x6c, 0xf7, 0x4a, 0xbe, 0x4b, 0xee, 0x96, + 0x90, 0x8f, 0xa2, 0x16, 0x22, 0x6a, 0xcc, 0x48, + 0x06, 0x74, 0x63, 0x43, 0x7f, 0x27, 0x22, 0x44, + 0x3c, 0x2d, 0x3b, 0x62, 0xf1, 0x1c, 0xb4, 0x27, + 0x33, 0x85, 0x26, 0x60, 0x48, 0x16, 0xcb, 0xef, + 0xf8, 0xcd, 0x37, 0x02, 0x81, 0x81, 0x00, 0xce, + 0x15, 0x43, 0x6e, 0x4b, 0x0f, 0xf9, 0x3f, 0x87, + 0xc3, 0x41, 0x45, 0x97, 0xb1, 0x49, 0xc2, 0x19, + 0x23, 0x87, 0xe4, 0x24, 0x1c, 0x64, 0xe5, 0x28, + 0xcb, 0x43, 0x10, 0x14, 0x14, 0x0e, 0x19, 0xcb, + 0xbb, 0xdb, 0xfd, 0x11, 0x9d, 0x17, 0x68, 0x78, + 0x6d, 0x61, 0x70, 0x63, 0x3a, 0xa1, 0xb3, 0xf3, + 0xa7, 0x5b, 0x0e, 0xff, 0xb7, 0x61, 0x11, 0x54, + 0x91, 0x99, 0xe5, 0x91, 0x32, 0x2d, 0xeb, 0x3f, + 0xd8, 0x3e, 0xf7, 0xd4, 0xcb, 0xd2, 0xa3, 0x41, + 0xc1, 0xee, 0xc6, 0x92, 0x13, 0xeb, 0x7f, 0x42, + 0x58, 0xf4, 0xd0, 0xb2, 0x74, 0x1d, 0x8e, 0x87, + 0x46, 0xcd, 0x14, 0xb8, 0x16, 0xad, 0xb5, 0xbd, + 0x0d, 0x6c, 0x95, 0x5a, 0x16, 0xbf, 0xe9, 0x53, + 0xda, 0xfb, 0xed, 0x83, 0x51, 0x67, 0xa9, 0x55, + 0xab, 0x54, 0x02, 0x95, 0x20, 0xa6, 0x68, 0x17, + 0x53, 0xa8, 0xea, 0x43, 0xe5, 0xb0, 0xa3, 0x02, + 0x81, 0x80, 0x67, 0x9c, 0x32, 0x83, 0x39, 0x57, + 0xff, 0x73, 0xb0, 0x89, 0x64, 0x8b, 0xd6, 0xf0, + 0x0a, 0x2d, 0xe2, 0xaf, 0x30, 0x1c, 0x2a, 0x97, + 0xf3, 0x90, 0x9a, 0xab, 0x9b, 0x0b, 0x1b, 0x43, + 0x79, 0xa0, 0xa7, 0x3d, 0xe7, 0xbe, 0x8d, 0x9c, + 0xeb, 0xdb, 0xad, 0x40, 0xdd, 0xa9, 0x00, 0x80, + 0xb8, 0xe1, 0xb3, 0xa1, 0x6c, 0x25, 0x92, 0xe4, + 0x33, 0xb2, 0xbe, 0xeb, 0x4d, 0x74, 0x26, 0x5f, + 0x37, 0x43, 0x9c, 0x6c, 0x17, 0x76, 0x0a, 0x81, + 0x20, 0x82, 0xa1, 0x48, 0x2c, 0x2d, 0x45, 0xdc, + 0x0f, 0x62, 0x43, 0x32, 0xbb, 0xeb, 0x59, 0x41, + 0xf9, 0xca, 0x58, 0xce, 0x4a, 0x66, 0x53, 0x54, + 0xc8, 0x28, 0x10, 0x1e, 0x08, 0x71, 0x16, 0xd8, + 0x02, 0x71, 0x41, 0x58, 0xd4, 0x56, 0xcc, 0xf5, + 0xb1, 0x31, 0xa3, 0xed, 0x00, 0x85, 0x09, 0xbf, + 0x35, 0x95, 0x41, 0x29, 0x40, 0x19, 0x83, 0x35, + 0x24, 0x69, 0x02, 0x81, 0x80, 0x55, 0x10, 0x0b, + 0xcc, 0x3b, 0xa9, 0x75, 0x3d, 0x16, 0xe1, 0xae, + 0x50, 0x76, 0x63, 0x94, 0x49, 0x4c, 0xad, 0x10, + 0xcb, 0x47, 0x68, 0x7c, 0xf0, 0xe5, 0xdc, 0xb8, + 0x6a, 0xab, 0x8e, 0xf7, 0x9f, 0x08, 0x2c, 0x1b, + 0x8a, 0xa2, 0xb9, 0x8f, 0xce, 0xec, 0x5e, 0x61, + 0xa8, 0xcd, 0x1c, 0x87, 0x60, 0x4a, 0xc3, 0x1a, + 0x5f, 0xdf, 0x87, 0x26, 0xc6, 0xcb, 0x7c, 0x69, + 0xe4, 0x8b, 0x01, 0x06, 0x59, 0x22, 0xfa, 0x34, + 0x4b, 0x81, 0x87, 0x3c, 0x03, 0x6d, 0x02, 0x0a, + 0x77, 0xe6, 0x15, 0xd8, 0xcf, 0xa7, 0x68, 0x26, + 0x6c, 0xfa, 0x2b, 0xd9, 0x83, 0x5a, 0x2d, 0x0c, + 0x3b, 0x70, 0x1c, 0xd4, 0x48, 0xbe, 0xa7, 0x0a, + 0xd9, 0xbe, 0xdc, 0xc3, 0x0c, 0x21, 0x33, 0xb3, + 0x66, 0xff, 0x1c, 0x1b, 0xc8, 0x96, 0x76, 0xe8, + 0x6f, 0x44, 0x74, 0xbc, 0x9b, 0x1c, 0x7d, 0xc8, + 0xac, 0x21, 0xa8, 0x6e, 0x37, 0x02, 0x81, 0x80, + 0x2c, 0x7c, 0xad, 0x1e, 0x75, 0xf6, 0x69, 0x1d, + 0xe7, 0xa6, 0xca, 0x74, 0x7d, 0x67, 0xc8, 0x65, + 0x28, 0x66, 0xc4, 0x43, 0xa6, 0xbd, 0x40, 0x57, + 0xae, 0xb7, 0x65, 0x2c, 0x52, 0xf9, 0xe4, 0xc7, + 0x81, 0x7b, 0x56, 0xa3, 0xd2, 0x0d, 0xe8, 0x33, + 0x70, 0xcf, 0x06, 0x84, 0xb3, 0x4e, 0x44, 0x50, + 0x75, 0x61, 0x96, 0x86, 0x4b, 0xb6, 0x2b, 0xad, + 0xf0, 0xad, 0x57, 0xd0, 0x37, 0x0d, 0x1d, 0x35, + 0x50, 0xcb, 0x69, 0x22, 0x39, 0x29, 0xb9, 0x3a, + 0xd3, 0x29, 0x23, 0x02, 0x60, 0xf7, 0xab, 0x30, + 0x40, 0xda, 0x8e, 0x4d, 0x45, 0x70, 0x26, 0xf4, + 0xa2, 0x0d, 0xd0, 0x64, 0x5d, 0x47, 0x3c, 0x18, + 0xf4, 0xd4, 0x52, 0x95, 0x00, 0xae, 0x84, 0x6b, + 0x47, 0xb2, 0x3c, 0x82, 0xd3, 0x72, 0x53, 0xde, + 0x72, 0x2c, 0xf7, 0xc1, 0x22, 0x36, 0xd9, 0x18, + 0x56, 0xfe, 0x39, 0x28, 0x33, 0xe0, 0xdb, 0x03 +}; + +} // namespace + +namespace wvoec_ref { + +AuthenticationRoot::AuthenticationRoot(OEMCrypto_ProvisioningMethod method) : + provisioning_method_(method), + use_test_keybox_(false) { + if ((provisioning_method_ == OEMCrypto_DrmCertificate) && + !rsa_key_.LoadPkcs8RsaKey(kPrivateKey, kPrivateKeySize)) { + // This error message is OK in unit tests which use test certificate. + LOGE("FATAL ERROR: Platform uses a baked-in certificate instead of a " + "keybox, but the certificate could not be loaded."); + } +} + +KeyboxError AuthenticationRoot::ValidateKeybox() { + return keybox().Validate(); +} + +bool AuthenticationRoot::LoadTestRsaKey() { + return rsa_key_.LoadPkcs8RsaKey(kTestRSAPKCS8PrivateKeyInfo2_2048, + sizeof(kTestRSAPKCS8PrivateKeyInfo2_2048)); +} + +bool AuthenticationRoot::Validate() { + return NO_ERROR == ValidateKeybox(); +} + +} // namespace wvoec_ref diff --git a/oemcrypto/ref/src/oemcrypto_auth_ref.h b/oemcrypto/ref/src/oemcrypto_auth_ref.h new file mode 100644 index 0000000..a1c6b6d --- /dev/null +++ b/oemcrypto/ref/src/oemcrypto_auth_ref.h @@ -0,0 +1,81 @@ +// 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. +// +// Reference implementation of OEMCrypto APIs +// +#ifndef OEMCRYPTO_AUTH_REF_H_ +#define OEMCRYPTO_AUTH_REF_H_ + +#include +#include +#include + +#include + +#include "OEMCryptoCAS.h" // Needed for enums only. +#include "disallow_copy_and_assign.h" +#include "oemcrypto_key_ref.h" +#include "oemcrypto_keybox_ref.h" +#include "oemcrypto_rsa_key_shared.h" +#include "oemcrypto_types.h" + +namespace wvoec_ref { + +class AuthenticationRoot { + public: + explicit AuthenticationRoot(OEMCrypto_ProvisioningMethod method); + ~AuthenticationRoot() {} + + bool Validate(); + + KeyboxError ValidateKeybox(); + + bool InstallKeybox(const uint8_t* keybox_data, size_t keybox_length) { + return keybox().InstallKeybox(keybox_data, keybox_length); + } + + const std::vector& DeviceKey(bool use_real_keybox = false) { + return use_real_keybox ? real_keybox().device_key() : + keybox().device_key(); + } + + const std::vector& DeviceId() { + return keybox().device_id(); + } + + size_t DeviceTokenLength() { + return keybox().key_data_length(); + } + + const uint8_t* DeviceToken() { + return keybox().key_data(); + } + + WvKeybox& keybox() { return use_test_keybox_ ? test_keybox_ : keybox_; } + bool UseTestKeybox(const uint8_t* keybox_data, size_t keybox_length) { + use_test_keybox_ = true; + return test_keybox_.InstallKeybox(keybox_data, keybox_length); + } + + RSA_shared_ptr& SharedRsaKey() { return rsa_key_; } + RSA* rsa_key() { return rsa_key_.get(); } + bool LoadTestRsaKey(); + void Clear() { use_test_keybox_ = false; } + + private: + OEMCrypto_ProvisioningMethod provisioning_method_; + WvKeybox& real_keybox() { return keybox_; } + + WvKeybox keybox_; + WvKeybox test_keybox_; + bool use_test_keybox_; + + RSA_shared_ptr rsa_key_; // If no keybox, this is baked in certificate. + + CORE_DISALLOW_COPY_AND_ASSIGN(AuthenticationRoot); +}; + +} // namespace wvoec_ref + +#endif // OEMCRYPTO_AUTH_REF_H_ diff --git a/oemcrypto/ref/src/oemcrypto_engine_device_properties.cpp b/oemcrypto/ref/src/oemcrypto_engine_device_properties.cpp new file mode 100644 index 0000000..e7429cf --- /dev/null +++ b/oemcrypto/ref/src/oemcrypto_engine_device_properties.cpp @@ -0,0 +1,19 @@ +// 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. +// +// Reference implementation of OEMCrypto APIs +// + +#include "oemcrypto_engine_ref.h" + +#include + +namespace wvoec_ref { + +CryptoEngine* CryptoEngine::MakeCryptoEngine( + std::unique_ptr&& file_system) { + return new CryptoEngine(std::move(file_system)); +} + +} // namespace wvoec_ref diff --git a/oemcrypto/ref/src/oemcrypto_engine_device_properties_L1.cpp b/oemcrypto/ref/src/oemcrypto_engine_device_properties_L1.cpp new file mode 100644 index 0000000..52ba9c3 --- /dev/null +++ b/oemcrypto/ref/src/oemcrypto_engine_device_properties_L1.cpp @@ -0,0 +1,40 @@ +// 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. +// +// Reference implementation of OEMCrypto APIs +// +// This file contains oemcrypto engine properties that would be for a +// level 1 device. +#include "oemcrypto_engine_ref.h" + +#include + +namespace wvoec_ref { + +class L1CryptoEngine : public CryptoEngine { + public: + explicit L1CryptoEngine(std::unique_ptr&& file_system) + : CryptoEngine(std::move(file_system)) {} + + bool config_local_display_only() { return true; } + + OEMCrypto_HDCP_Capability config_maximum_hdcp_capability() { + return HDCP_V2; + } + + bool config_is_anti_rollback_hw_present() { return true; } + + const char* config_security_level() { return "L1"; } + + // This should start at 0, and be incremented only when a security patch has + // been applied to the device that fixes a security bug. + uint8_t config_security_patch_level() { return 3; } +}; + +CryptoEngine* CryptoEngine::MakeCryptoEngine( + std::unique_ptr&& file_system) { + return new L1CryptoEngine(std::move(file_system)); +} + +} // namespace wvoec_ref diff --git a/oemcrypto/ref/src/oemcrypto_engine_device_properties_cert.cpp b/oemcrypto/ref/src/oemcrypto_engine_device_properties_cert.cpp new file mode 100644 index 0000000..550016d --- /dev/null +++ b/oemcrypto/ref/src/oemcrypto_engine_device_properties_cert.cpp @@ -0,0 +1,38 @@ +// 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. +// +// Reference implementation of OEMCrypto APIs +// +// This file contains oemcrypto engine properties that would be for a device +// that does not have persistant storage or a keybox. +// +// Note: We also define it to be L2 for illustration only. Production devices +// are rarely level 2. +#include "oemcrypto_engine_ref.h" + +namespace wvoec_ref { + +class CertOnlyCryptoEngine : public CryptoEngine { + public: + explicit CertOnlyCryptoEngine( + std::unique_ptr&& file_system) + : CryptoEngine(std::move(file_system)) {} + + bool config_local_display_only() { return true; } + + bool config_supports_usage_table() { return false; } + + OEMCrypto_ProvisioningMethod config_provisioning_method() { + return OEMCrypto_DrmCertificate; + } + + const char* config_security_level() { return "L2"; } +}; + +CryptoEngine* CryptoEngine::MakeCryptoEngine( + std::unique_ptr&& file_system) { + return new CertOnlyCryptoEngine(std::move(file_system)); +} + +} // namespace wvoec_ref diff --git a/oemcrypto/ref/src/oemcrypto_engine_device_properties_prov30.cpp b/oemcrypto/ref/src/oemcrypto_engine_device_properties_prov30.cpp new file mode 100644 index 0000000..2c56868 --- /dev/null +++ b/oemcrypto/ref/src/oemcrypto_engine_device_properties_prov30.cpp @@ -0,0 +1,86 @@ +// 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. +// +// Reference implementation of OEMCrypto APIs +// +// This file contains oemcrypto engine properties that would be for a +// level 2 device that does not have persistant storage or a keybox. +// Note: this is for illustration only. Production devices are rarely level 2. + +#include "oemcrypto_engine_ref.h" + +#include + +#include + +#include "log.h" +#include "oem_cert.h" + +namespace wvoec_ref { + +class Prov30CryptoEngine : public CryptoEngine { + public: + explicit Prov30CryptoEngine(std::unique_ptr&& file_system) + : CryptoEngine(std::move(file_system)) {} + + bool config_local_display_only() { return true; } + + // Returns the max HDCP version supported. + OEMCrypto_HDCP_Capability config_maximum_hdcp_capability() { + return HDCP_NO_DIGITAL_OUTPUT; + } + + // Returns true if the client supports persistent storage of + // offline usage table information. + bool config_supports_usage_table() { + return false; + } + + // Returns true if the client uses a keybox as the root of trust. + bool config_supports_keybox() { + return false; + } + + // This version uses an OEM Certificate. + OEMCrypto_ProvisioningMethod config_provisioning_method() { + return OEMCrypto_OEMCertificate; + } + + OEMCryptoResult get_oem_certificate(SessionContext* session, + uint8_t* public_cert, + size_t* public_cert_length) { + if (kOEMPublicCertSize == 0) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; + } + if (public_cert_length == nullptr) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (*public_cert_length < kOEMPublicCertSize) { + *public_cert_length = kOEMPublicCertSize; + return OEMCrypto_ERROR_SHORT_BUFFER; + } + *public_cert_length = kOEMPublicCertSize; + if (public_cert == nullptr) { + return OEMCrypto_ERROR_SHORT_BUFFER; + } + memcpy(public_cert, kOEMPublicCert, kOEMPublicCertSize); + if (!session->LoadRSAKey(kOEMPrivateKey, kOEMPrivateKeySize)) { + LOGE("Private RSA Key did not load correctly."); + return OEMCrypto_ERROR_INVALID_RSA_KEY; + } + return OEMCrypto_SUCCESS; + } + + // Returns "L3" for a software only library. L1 is for hardware protected + // keys and data paths. L2 is for hardware protected keys but no data path + // protection. + const char* config_security_level() { return "L2"; } +}; + +CryptoEngine* CryptoEngine::MakeCryptoEngine( + std::unique_ptr&& file_system) { + return new Prov30CryptoEngine(std::move(file_system)); +} + +} // namespace wvoec_ref diff --git a/oemcrypto/ref/src/oemcrypto_engine_ref.cpp b/oemcrypto/ref/src/oemcrypto_engine_ref.cpp new file mode 100644 index 0000000..2f48da3 --- /dev/null +++ b/oemcrypto/ref/src/oemcrypto_engine_ref.cpp @@ -0,0 +1,337 @@ +// 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. +// +// Reference implementation of OEMCrypto APIs +// +#include "oemcrypto_engine_ref.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "clock.h" +#include "keys.h" +#include "log.h" +#include "oemcrypto_key_ref.h" +#include "oemcrypto_rsa_key_shared.h" +#include "string_conversions.h" + +namespace { + +// Lower bits in SessionId are actual session id. The rest higher bits are +// session type. +const uint32_t kSessionIdTypeShift = 28; +const uint32_t kSessionIdMask = (1u << kSessionIdTypeShift) - 1u; +} // namespace + +namespace wvoec_ref { + +// Note: The class CryptoEngine is configured at compile time by compiling in +// different device property files. The methods in this file are generic to +// all configurations. See the files oemcrypto_engine_device_properties*.cpp +// for methods that are configured for specific configurations. + +CryptoEngine::CryptoEngine(std::unique_ptr&& file_system) + : root_of_trust_(config_provisioning_method()), + file_system_(std::move(file_system)), + usage_table_(), + entitled_key_session_table_(nullptr) { + ERR_load_crypto_strings(); +} + +CryptoEngine::~CryptoEngine() { + ERR_free_strings(); +} + +bool CryptoEngine::Initialize() { + std::string file_path = GetUsageTimeFileFullPath(); + LoadOfflineTimeInfo(file_path); + usage_table_.reset(MakeUsageTable()); + return true; +} + +void CryptoEngine::Terminate() { + std::string file_path = GetUsageTimeFileFullPath(); + SaveOfflineTimeInfo(file_path); + std::unique_lock lock(session_table_lock_); + ActiveSessions::iterator it; + for (it = sessions_.begin(); it != sessions_.end(); ++it) { + delete it->second; + } + sessions_.clear(); + root_of_trust_.Clear(); +} + +SessionId CryptoEngine::OpenSession() { + std::unique_lock lock(session_table_lock_); + static OEMCrypto_SESSION unique_id = 1; + SessionId id = ++unique_id; + // Check if too many sessions have been opened. + if (SessionTypeBits(id) != 0) { + return 0; + } + // Apply session type to higher bits. + id = (kSessionTypeOEMCrypto << kSessionIdTypeShift) | (id & kSessionIdMask); + sessions_[id] = MakeSession(id); + return id; +} + +SessionContext* CryptoEngine::MakeSession(SessionId sid) { + return new SessionContext(this, sid, root_of_trust_.SharedRsaKey()); +} + +UsageTable* CryptoEngine::MakeUsageTable() { return new UsageTable(this); } + +bool CryptoEngine::DestroySession(SessionId sid) { + SessionContext* sctx = FindSession(sid); + std::unique_lock lock(session_table_lock_); + if (sctx) { + if (entitled_key_session_table_ != nullptr) { + entitled_key_session_table_->OnDestroyOEMCryptoSession(sid); + } + sessions_.erase(sid); + delete sctx; + return true; + } else { + return false; + } +} + +SessionContext* CryptoEngine::FindSession(SessionId sid) { + std::unique_lock lock(session_table_lock_); + SessionId oec_sid = sid; + // Convert entitled key session id to oemcrypto session id. + if (SessionTypeBits(sid) == kSessionTypeEntitledKey) { + if (entitled_key_session_table_ == nullptr || + entitled_key_session_table_->FindEntitledKeySession(sid) == nullptr) { + return nullptr; + } + oec_sid = entitled_key_session_table_->GetOEMCryptoSessionId(sid); + } + + ActiveSessions::iterator it = sessions_.find(oec_sid); + if (it != sessions_.end()) { + return it->second; + } + return nullptr; +} + +OEMCryptoResult CryptoEngine::CreateEntitledKeySession(SessionId oec_sid, + SessionId* key_sid) { + std::unique_lock lock(session_table_lock_); + // Create entitled key session table if it does not exist yet. + if (entitled_key_session_table_ == nullptr) { + entitled_key_session_table_ = + std::unique_ptr(new EntitledKeySessionTable()); + } + + // Generate random entitled key session id. 0 is reserved as an invalid id. + SessionId sid = 0; + do { + // Higher bits in session id is session type. + sid = (kSessionTypeEntitledKey << kSessionIdTypeShift) | + (rand() & kSessionIdMask); + } while (sid == 0 || + entitled_key_session_table_->FindEntitledKeySession(sid) != nullptr); + + EntitledKeySession* entitled_key_session = + entitled_key_session_table_->CreateEntitledKeySession(oec_sid, sid); + if (entitled_key_session == nullptr || + entitled_key_session->GetSessionId() != sid) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + *key_sid = sid; + return OEMCrypto_SUCCESS; +} + +OEMCryptoResult CryptoEngine::RemoveEntitledKeySession(SessionId key_sid) { + std::unique_lock lock(session_table_lock_); + if (entitled_key_session_table_ == nullptr) { + return OEMCrypto_ERROR_INVALID_ENTITLED_KEY_SESSION; + } + + EntitledKeySession* entitled_key_session = + entitled_key_session_table_->FindEntitledKeySession(key_sid); + if (entitled_key_session == nullptr) { + return OEMCrypto_ERROR_INVALID_ENTITLED_KEY_SESSION; + } + entitled_key_session_table_->RemoveEntitledKeySession(key_sid); + return OEMCrypto_SUCCESS; +} + +EntitledKeySession* CryptoEngine::FindEntitledKeySession(SessionId key_sid) { + std::unique_lock lock(session_table_lock_); + if (entitled_key_session_table_ == nullptr) { + return nullptr; + } + return entitled_key_session_table_->FindEntitledKeySession(key_sid); +} + +int64_t CryptoEngine::MonotonicTime() { + // Use the monotonic clock for times that don't have to be stable across + // device boots. + int64_t now = + wvutil::Clock().GetCurrentTime() + offline_time_info_.rollback_offset; + static int64_t then = now; + if (now < then) { + LOGW("Clock rollback detected: %lld seconds", then - now); + offline_time_info_.rollback_offset += then - now; + now = then; + } + then = now; + return now; +} + +int64_t CryptoEngine::SystemTime() { + const int64_t current_time = MonotonicTime(); + // Write time info to disk if kTimeInfoUpdateWindowInSeconds has elapsed since + // last write. + if (current_time - offline_time_info_.previous_time > + kTimeInfoUpdateWindowInSeconds) { + std::string file_path = GetUsageTimeFileFullPath(); + SaveOfflineTimeInfo(file_path); + } + return current_time; +} + +std::string CryptoEngine::GetUsageTimeFileFullPath() const { + std::string file_path; + // Note: file path is OK for a real implementation, but using security + // level 1 would be better. + // TODO(fredgc, jfore): Address how this property is presented to the ref. + // For now, the file path is empty. + /*if (!wvutil::Properties::GetDeviceFilesBasePath(wvutil::kSecurityLevelL3, + &file_path)) { + LOGE("Unable to get base path"); + }*/ + return file_path + kStoredUsageTimeFileName; +} + +bool CryptoEngine::LoadOfflineTimeInfo(const std::string& file_path) { + memset(&offline_time_info_, 0, sizeof(TimeInfo)); + wvutil::FileSystem* file_system = file_system_.get(); + if (file_system->Exists(file_path)) { + std::unique_ptr file = + file_system->Open(file_path, wvutil::FileSystem::kReadOnly); + if (!file) { + // This error is expected at first initialization. + LOGE("File open failed (this is expected on first initialization): %s", + file_path.c_str()); + return false; + } + // Load time info from previous call. + file->Read(reinterpret_cast(&offline_time_info_), sizeof(TimeInfo)); + + // Detect offline time rollback after loading from disk. + // Add any time offsets in the past to the current time. + int64_t current_time = MonotonicTime(); + if (offline_time_info_.previous_time > current_time) { + // Current time is earlier than the previously saved time. Time has been + // rolled back. Update the rollback offset. + offline_time_info_.rollback_offset += + offline_time_info_.previous_time - current_time; + // Keep current time at previous recorded time. + current_time = offline_time_info_.previous_time; + } + // The new previous_time will either stay the same or move forward. + offline_time_info_.previous_time = current_time; + } + return true; +} + +bool CryptoEngine::SaveOfflineTimeInfo(const std::string& file_path) { + // Add any time offsets in the past to the current time. If there was an + // earlier offline rollback, the rollback offset will be updated in + // LoadOfflineTimeInfo(). It guarantees that the current time to be saved + // will never go back. + const int64_t current_time = MonotonicTime(); + // The new previous_time will either stay the same or move forward. + if (current_time > offline_time_info_.previous_time) + offline_time_info_.previous_time = current_time; + + std::unique_ptr file; + wvutil::FileSystem* file_system = file_system_.get(); + // Write the current time and offset to disk. + file = file_system->Open( + file_path, wvutil::FileSystem::kCreate | wvutil::FileSystem::kTruncate); + if (!file) { + LOGE("File open failed: %s", file_path.c_str()); + return false; + } + file->Write(reinterpret_cast(&offline_time_info_), sizeof(TimeInfo)); + return true; +} + +bool CryptoEngine::NonceCollision(uint32_t nonce) { + for (const auto& session_pair : sessions_) { + const SessionContext* session = session_pair.second; + if (nonce == session->nonce()) return true; + } + return false; +} + +OEMCrypto_HDCP_Capability CryptoEngine::config_current_hdcp_capability() { + return config_local_display_only() ? HDCP_NO_DIGITAL_OUTPUT : HDCP_V1; +} + +OEMCrypto_HDCP_Capability CryptoEngine::config_maximum_hdcp_capability() { + return HDCP_NO_DIGITAL_OUTPUT; +} + +OEMCryptoResult CryptoEngine::SetDestination( + const OEMCrypto_DestBufferDesc& out_description, size_t data_length, + uint8_t subsample_flags) { + size_t max_length = 0; + switch (out_description.type) { + case OEMCrypto_BufferType_Clear: + destination_ = out_description.buffer.clear.address; + max_length = out_description.buffer.clear.address_length; + break; + case OEMCrypto_BufferType_Secure: + destination_ = + reinterpret_cast(out_description.buffer.secure.handle) + + out_description.buffer.secure.offset; + max_length = out_description.buffer.secure.handle_length - + out_description.buffer.secure.offset; + break; + case OEMCrypto_BufferType_Direct: + // Direct buffer type is only used on some specialized devices where + // oemcrypto has a direct connection to the screen buffer. It is not, + // for example, supported on Android. + destination_ = nullptr; + break; + default: + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + const size_t max_allowed = max_sample_size(); + if (max_allowed > 0 && + (max_allowed < max_length || max_allowed < data_length)) { + LOGE("Output too large (or buffer too small)."); + return OEMCrypto_ERROR_OUTPUT_TOO_LARGE; + } + + if (out_description.type != OEMCrypto_BufferType_Direct && + max_length < data_length) { + LOGE("[SetDestination(): OEMCrypto_ERROR_SHORT_BUFFER]"); + return OEMCrypto_ERROR_SHORT_BUFFER; + } + adjust_destination(out_description, data_length, subsample_flags); + if ((out_description.type != OEMCrypto_BufferType_Direct) && + (destination_ == nullptr)) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + return OEMCrypto_SUCCESS; +} + +uint32_t CryptoEngine::SessionTypeBits(SessionId sid) { + return sid >> kSessionIdTypeShift; +} + +} // namespace wvoec_ref diff --git a/oemcrypto/ref/src/oemcrypto_engine_ref.h b/oemcrypto/ref/src/oemcrypto_engine_ref.h new file mode 100644 index 0000000..237f508 --- /dev/null +++ b/oemcrypto/ref/src/oemcrypto_engine_ref.h @@ -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. +// +// Reference implementation of OEMCrypto APIs +// +#ifndef REF_OEMCRYPTO_ENGINE_REF_H_ +#define REF_OEMCRYPTO_ENGINE_REF_H_ + +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "OEMCryptoCAS.h" +#include "file_store.h" +#include "oemcrypto_auth_ref.h" +#include "oemcrypto_entitled_key_session.h" +#include "oemcrypto_key_ref.h" +#include "oemcrypto_rsa_key_shared.h" +#include "oemcrypto_session.h" +#include "oemcrypto_types.h" +#include "oemcrypto_usage_table_ref.h" + +namespace wvoec_ref { + +typedef std::map ActiveSessions; + +static const std::string kStoredUsageTimeFileName = "StoredUsageTime.dat"; + +typedef struct { + // The max time recorded + int64_t previous_time; + // If the wall time is rollbacked to before the previous_time, this member + // is updated to reflect the offset. + int64_t rollback_offset; + // Pad the struct so that TimeInfo is a multiple of 16. + uint8_t padding[16 - (2 * sizeof(time_t)) % 16]; +} TimeInfo; + +// Session types are higher (32 - kSessionIdTypeShift) bits in SessionId. +typedef enum SessionType { + kSessionTypeOEMCrypto = 0, + kSessionTypeEntitledKey = 1, +} SessionType; + +class CryptoEngine { + public: + static const uint32_t kApiVersion = 16; + static const uint32_t kMinorApiVersion = 0; + static const int64_t kTimeInfoUpdateWindowInSeconds = 300; + + // This is like a factory method, except we choose which version to use at + // compile time. It is defined in several source files. The build system + // should choose which one to use by only linking in the correct one. + // NOTE: The caller must instantiate a FileSystem object - ownership + // will be transferred to the new CryptoEngine object. + static CryptoEngine* MakeCryptoEngine( + std::unique_ptr&& file_system); + + virtual ~CryptoEngine(); + + virtual bool Initialize(); + + bool ValidRootOfTrust() { return root_of_trust_.Validate(); } + + bool InstallKeybox(const uint8_t* keybox, size_t keybox_length) { + return root_of_trust_.InstallKeybox(keybox, keybox_length); + } + + bool UseTestKeybox(const uint8_t* keybox_data, size_t keybox_length) { + return root_of_trust_.UseTestKeybox(keybox_data, keybox_length); + } + + bool LoadTestRsaKey() { return root_of_trust_.LoadTestRsaKey(); } + + KeyboxError ValidateKeybox() { return root_of_trust_.ValidateKeybox(); } + + const std::vector& DeviceRootKey() { + return root_of_trust_.DeviceKey(); + } + + const std::vector& DeviceRootId() { + return root_of_trust_.DeviceId(); + } + + size_t DeviceRootTokenLength() { return root_of_trust_.DeviceTokenLength(); } + + const uint8_t* DeviceRootToken() { + return root_of_trust_.DeviceToken(); + } + + virtual void Terminate(); + + virtual SessionId OpenSession(); + + virtual bool DestroySession(SessionId sid); + + // |sid| can be either oemcrypto session or entitled key session. + SessionContext* FindSession(SessionId sid); + + virtual OEMCryptoResult CreateEntitledKeySession(SessionId oec_sid, + SessionId* key_sid); + + virtual OEMCryptoResult RemoveEntitledKeySession(SessionId key_sid); + + EntitledKeySession* FindEntitledKeySession(SessionId key_sid); + + size_t GetNumberOfOpenSessions() { return sessions_.size(); } + + size_t GetMaxNumberOfSessions() { + // An arbitrary limit for ref implementation. + static const size_t kMaxSupportedOEMCryptoSessions = 64; + return kMaxSupportedOEMCryptoSessions; + } + + // The OEMCrypto system time. Prevents time rollback. + int64_t SystemTime(); + + // Verify that this nonce does not collide with another nonce in any session. + virtual bool NonceCollision(uint32_t nonce); + + // Returns the HDCP version currently in use. + virtual OEMCrypto_HDCP_Capability config_current_hdcp_capability(); + + // Returns the max HDCP version supported. + virtual OEMCrypto_HDCP_Capability config_maximum_hdcp_capability(); + + // Return true if there might be analog video output enabled. + virtual bool analog_display_active() { + return !config_local_display_only(); + } + + // Return true if there is an analog display, and CGMS A is turned on. + virtual bool cgms_a_active() { return false; } + + // Return the analog output flags. + virtual uint32_t analog_output_flags() { + return config_local_display_only() ? OEMCrypto_No_Analog_Output + : OEMCrypto_Supports_Analog_Output; + } + + UsageTable& usage_table() { return *(usage_table_.get()); } + wvutil::FileSystem* file_system() { return file_system_.get(); } + + // If config_local_display_only() returns true, we pretend we are using a + // built-in display, instead of HDMI or WiFi output. + virtual bool config_local_display_only() { return false; } + + // A closed platform is permitted to use clear buffers. + virtual bool config_closed_platform() { return false; } + + // Returns true if the client supports persistent storage of + // offline usage table information. + virtual bool config_supports_usage_table() { return true; } + + virtual OEMCrypto_ProvisioningMethod config_provisioning_method() { + return OEMCrypto_Keybox; + } + + virtual OEMCryptoResult get_oem_certificate(uint8_t* public_cert, + size_t* public_cert_length) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; + } + + virtual OEMCryptoResult load_oem_private_key(SessionContext* session) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; + } + + // Used for OEMCrypto_IsAntiRollbackHwPresent. + virtual bool config_is_anti_rollback_hw_present() { return false; } + + // Returns "L3" for a software only library. L1 is for hardware protected + // data paths. + virtual const char* config_security_level() { return "L3"; } + + // This should start at 0, and be incremented only when a security patch has + // been applied to the device that fixes a security bug. + virtual uint8_t config_security_patch_level() { return 0; } + + // If 0 no restriction, otherwise it's the max subsample size for + // DecryptCENC. This is not the same as the max sample or buffer size. + virtual size_t max_subsample_size() { return 1024 * 100; } // 100 KiB + + // If 0 no restriction, otherwise it's the max sample size for DecryptCENC. + // This is the same as the max input and output buffer size for DecryptCENC + // and CopyBuffer. It is not the same as the max subsample size. + virtual size_t max_sample_size() { return 1024 * 1024; } // 1 MiB + + virtual bool srm_update_supported() { return false; } + + virtual OEMCryptoResult current_srm_version(uint16_t* version) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; + } + + virtual OEMCryptoResult load_srm(const uint8_t* buffer, + size_t buffer_length) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; + } + + virtual OEMCryptoResult remove_srm() { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; + } + + virtual bool srm_blacklisted_device_attached() { return false; } + + // Rate limit for nonce generation. Default to 200 nonce/second. + virtual int nonce_flood_count() { return 200; } + + // Limit for size of usage table. If this is zero, then the + // size is unlimited -- or limited only by memory size. + virtual size_t max_usage_table_size() { return 0; } + + virtual uint32_t resource_rating() { return 1; } + + // Set destination pointer based on the output destination description. + OEMCryptoResult SetDestination( + const OEMCrypto_DestBufferDesc& out_description, size_t data_length, + uint8_t subsample_flags); + + // The current destination. + uint8_t* destination() { return destination_; } + + // Subclasses can adjust the destination -- for use in testing. + virtual void adjust_destination( + const OEMCrypto_DestBufferDesc& out_description, size_t data_length, + uint8_t subsample_flags) {} + + // Push destination buffer to output -- used by subclasses for testing. + virtual OEMCryptoResult PushDestination( + const OEMCrypto_DestBufferDesc& out_description, + uint8_t subsample_flags) { + return OEMCrypto_SUCCESS; + } + + // Get the session type bits from |sid|. + static uint32_t SessionTypeBits(SessionId sid); + + protected: + // System clock, measuring time in seconds, including anti-rollback offset. + int64_t MonotonicTime(); + + bool LoadOfflineTimeInfo(const std::string& file_path); + bool SaveOfflineTimeInfo(const std::string& file_path); + std::string GetUsageTimeFileFullPath() const; + + explicit CryptoEngine(std::unique_ptr&& file_system); + virtual SessionContext* MakeSession(SessionId sid); + virtual UsageTable* MakeUsageTable(); + uint8_t* destination_; + ActiveSessions sessions_; + AuthenticationRoot root_of_trust_; + std::mutex session_table_lock_; + std::unique_ptr file_system_; + std::unique_ptr usage_table_; + TimeInfo offline_time_info_; + std::unique_ptr entitled_key_session_table_; + + CORE_DISALLOW_COPY_AND_ASSIGN(CryptoEngine); +}; + +} // namespace wvoec_ref + +#endif // REF_OEMCRYPTO_ENGINE_REF_H_ diff --git a/oemcrypto/ref/src/oemcrypto_entitled_key_session.cpp b/oemcrypto/ref/src/oemcrypto_entitled_key_session.cpp new file mode 100644 index 0000000..b1d3ff1 --- /dev/null +++ b/oemcrypto/ref/src/oemcrypto_entitled_key_session.cpp @@ -0,0 +1,120 @@ +// 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 "oemcrypto_entitled_key_session.h" + +namespace wvoec_ref { + +const Key* EntitledKeySession::GetEntitlementKey( + const KeyId& content_key_id) const { + if (entitlement_key_map_.find(content_key_id) == entitlement_key_map_.end()) { + return nullptr; + } + return entitlement_key_map_.at(content_key_id); +} + +EntitledKey* EntitledKeySession::GetContentKey( + const KeyId& content_key_id) const { + if (content_key_map_.find(content_key_id) == content_key_map_.end()) { + return nullptr; + } + return content_key_map_.at(content_key_id).get(); +} + +bool EntitledKeySession::AddOrUpdateContentKey( + Key* entitlement_key, const KeyId& content_key_id, + std::unique_ptr content_key) { + if (entitlement_key == nullptr || content_key_id.empty() || + content_key == nullptr) { + return false; + } + // Remove the entry if |entitlement_key| already exists. Each entitlement key + // can only be referred by one content key within an entitled key session. + for (auto const& content_id_entitlement : entitlement_key_map_) { + if (content_id_entitlement.second == entitlement_key) { + RemoveContentKey(content_id_entitlement.first); + break; + } + } + // |content_key_id| must be unique. + if (content_key_map_.find(content_key_id) != content_key_map_.end()) { + return false; + } + content_key_map_[content_key_id] = std::move(content_key); + entitlement_key_map_[content_key_id] = entitlement_key; + return true; +} + +bool EntitledKeySession::RemoveContentKey(const KeyId& content_key_id) { + if (content_key_map_.find(content_key_id) == content_key_map_.end()) { + return false; + } + content_key_map_.erase(content_key_id); + entitlement_key_map_.erase(content_key_id); + return true; +} + +/***************************************/ + +EntitledKeySession* EntitledKeySessionTable::CreateEntitledKeySession( + SessionId oec_sid, SessionId key_sid) { + std::unique_lock lock(session_lock_); + // The given |key_sid| already exists. + if (entitled_key_sessions_.find(key_sid) != entitled_key_sessions_.end()) { + return nullptr; + } + entitled_key_sessions_[key_sid] = + std::unique_ptr(new EntitledKeySession(key_sid)); + + oec_session_to_key_sessions_[oec_sid].insert(oec_sid); + key_session_to_oec_session_[key_sid] = oec_sid; + + return entitled_key_sessions_[key_sid].get(); +} + +EntitledKeySession* EntitledKeySessionTable::FindEntitledKeySession( + SessionId key_sid) { + std::unique_lock lock(session_lock_); + if (entitled_key_sessions_.find(key_sid) == entitled_key_sessions_.end()) { + return nullptr; + } + return entitled_key_sessions_.at(key_sid).get(); +} + +void EntitledKeySessionTable::RemoveEntitledKeySession(SessionId key_sid) { + std::unique_lock lock(session_lock_); + if (entitled_key_sessions_.find(key_sid) == entitled_key_sessions_.end()) { + return; + } + + SessionId oec_sid = key_session_to_oec_session_[key_sid]; + key_session_to_oec_session_.erase(key_sid); + oec_session_to_key_sessions_[oec_sid].erase(key_sid); + + entitled_key_sessions_.erase(key_sid); +} + +SessionId EntitledKeySessionTable::GetOEMCryptoSessionId(SessionId key_sid) { + std::unique_lock lock(session_lock_); + if (key_session_to_oec_session_.find(key_sid) == + key_session_to_oec_session_.end()) { + return 0; + } + return key_session_to_oec_session_.at(key_sid); +} + +void EntitledKeySessionTable::OnDestroyOEMCryptoSession(SessionId oec_sid) { + std::unique_lock lock(session_lock_); + if (oec_session_to_key_sessions_.find(oec_sid) == + oec_session_to_key_sessions_.end()) { + return; + } + for (auto entitled_key_sid : oec_session_to_key_sessions_[oec_sid]) { + key_session_to_oec_session_.erase(entitled_key_sid); + entitled_key_sessions_.erase(entitled_key_sid); + } + oec_session_to_key_sessions_.erase(oec_sid); +} + +} // namespace wvoec_ref diff --git a/oemcrypto/ref/src/oemcrypto_entitled_key_session.h b/oemcrypto/ref/src/oemcrypto_entitled_key_session.h new file mode 100644 index 0000000..5c0a5cf --- /dev/null +++ b/oemcrypto/ref/src/oemcrypto_entitled_key_session.h @@ -0,0 +1,110 @@ +// 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 OEMCRYPTO_ENTITLED_KEY_SESSION_H_ +#define OEMCRYPTO_ENTITLED_KEY_SESSION_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include "oemcrypto_key_ref.h" + +namespace wvoec_ref { + +typedef std::vector KeyId; +typedef uint32_t SessionId; + +class EntitledKeySession { + public: + explicit EntitledKeySession(SessionId key_sid) + : key_sid_(key_sid), + current_content_key_(nullptr), + current_entitlement_key_(nullptr){}; + EntitledKeySession(const EntitledKeySession&) = delete; + EntitledKeySession(EntitledKeySession&&) = delete; + ~EntitledKeySession() = default; + + // Get id of this entitled key session . + SessionId GetSessionId() const { return key_sid_; } + // Get reference of the entitlement key that entitles |content_key_id|. + const Key* GetEntitlementKey(const KeyId& content_key_id) const; + // Get content key whose id is |content_key_id|. + EntitledKey* GetContentKey(const KeyId& content_key_id) const; + + // Select current content key to |content_key_id|, and current entitlement + // key to its corresponding entitlement key. + void SetCurrentKey(const KeyId& content_key_id) { + current_content_key_ = GetContentKey(content_key_id); + current_entitlement_key_ = GetEntitlementKey(content_key_id); + } + // Return the current content key selected, nullptr if none is selected. + const EntitledKey* CurrentContentKey() const { return current_content_key_; } + // Return the current entitlement key selected, nullptr if none is selected. + const Key* CurrentEntitlementKey() const { return current_entitlement_key_; } + + // Update the content key that refers to |entitlement_key| or insert a new + // entry that refers to |entitlement_key|. |entitlement_key| must not be null. + bool AddOrUpdateContentKey(Key* entitlement_key, const KeyId& content_key_id, + std::unique_ptr content_key); + // Remove a content key |content_key_id|. + bool RemoveContentKey(const KeyId& content_key_id); + + // Total number of content keys this entitled key session holds. + size_t size() const { return content_key_map_.size(); } + + private: + const SessionId key_sid_; + EntitledKey* current_content_key_; + const Key* current_entitlement_key_; + // Map from content key id to content key. + std::map> content_key_map_; + // Map from content key id to referenced entitlement key. + std::map entitlement_key_map_; +}; + +class EntitledKeySessionTable { + public: + EntitledKeySessionTable() = default; + EntitledKeySessionTable(const EntitledKeySessionTable&) = delete; + EntitledKeySessionTable(EntitledKeySessionTable&&) = delete; + ~EntitledKeySessionTable() = default; + + // Create an entitled key session with given id |key_sid|, which refers to + // OEMCrypto session |oec_sid|. + EntitledKeySession* CreateEntitledKeySession(SessionId oec_sid, + SessionId key_sid); + // Return the entitled key session that has |key_sid| as the id. + EntitledKeySession* FindEntitledKeySession(SessionId key_sid); + // Remove the entitled key session that has |key_sid| as the id. + void RemoveEntitledKeySession(SessionId key_sid); + + // Get the referenced OEMCrypto session id of entitled key session |key_sid|. + SessionId GetOEMCryptoSessionId(SessionId key_sid); + // Remove all entitled key sessions that refer to |oec_sid|. + void OnDestroyOEMCryptoSession(SessionId oec_sid); + + // Total number of active entitled key sessions. + size_t size() const { return entitled_key_sessions_.size(); } + + private: + std::mutex session_lock_; + // Map from entitled key session id to entitled key session. + std::unordered_map> + entitled_key_sessions_; + // Map from entitled key session id to referenced OEMCrypto session id. + std::unordered_map key_session_to_oec_session_; + // Map from OEMCrypto session id to the set of entitled key sessions that + // refers to this OEMCrypto session. + std::unordered_map> + oec_session_to_key_sessions_; +}; + +} // namespace wvoec_ref + +#endif // OEMCRYPTO_ENTITLED_KEY_SESSION_H_ diff --git a/oemcrypto/ref/src/oemcrypto_key_ref.cpp b/oemcrypto/ref/src/oemcrypto_key_ref.cpp new file mode 100644 index 0000000..feb2374 --- /dev/null +++ b/oemcrypto/ref/src/oemcrypto_key_ref.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. +// +// Reference implementation of OEMCrypto APIs +// +#include "oemcrypto_key_ref.h" +#include "oemcrypto_types.h" + +#include +#include + +#include "log.h" + +namespace wvoec_ref { + +bool KeyControlBlock::Validate() { + if (memcmp(verification_, "kctl", 4) && // original verification + memcmp(verification_, "kc09", 4) && // add in version 9 api + memcmp(verification_, "kc10", 4) && // add in version 10 api + memcmp(verification_, "kc11", 4) && // add in version 11 api + memcmp(verification_, "kc12", 4) && // add in version 12 api + memcmp(verification_, "kc13", 4) && // add in version 13 api + memcmp(verification_, "kc14", 4) && // add in version 14 api + memcmp(verification_, "kc15", 4) && // add in version 15 api + memcmp(verification_, "kc16", 4)) { // add in version 16 api + LOGE("KCB: BAD verification string: %4.4s", verification_); + valid_ = false; + } else { + valid_ = true; + } + return valid_; +} + +// This extracts 4 bytes in network byte order to a 32 bit integer in +// host byte order. +uint32_t KeyControlBlock::ExtractField(const std::vector& str, + int idx) { + int bidx = idx * 4; + uint32_t t = static_cast(str[bidx]) << 24; + t |= static_cast(str[bidx + 1]) << 16; + t |= static_cast(str[bidx + 2]) << 8; + t |= static_cast(str[bidx + 3]); + return t; +} + +KeyControlBlock::KeyControlBlock( + const std::vector& key_control_string) { + if (key_control_string.size() < wvoec::KEY_CONTROL_SIZE) { + LOGE("KCB: BAD Size: %d (not %d)", key_control_string.size(), + wvoec::KEY_CONTROL_SIZE); + return; + } + + memcpy(verification_, &key_control_string[0], 4); + duration_ = ExtractField(key_control_string, 1); + nonce_ = ExtractField(key_control_string, 2); + control_bits_ = ExtractField(key_control_string, 3); + Validate(); +} + +void Key::UpdateDuration(const KeyControlBlock& control) { + control_.set_duration(control.duration()); +} + +void KeyControlBlock::RequireLocalDisplay() { + // Set all bits to require HDCP Local Display Only. + control_bits_ |= wvoec::kControlHDCPVersionMask; +} + +} // namespace wvoec_ref diff --git a/oemcrypto/ref/src/oemcrypto_key_ref.h b/oemcrypto/ref/src/oemcrypto_key_ref.h new file mode 100644 index 0000000..c2fc17e --- /dev/null +++ b/oemcrypto/ref/src/oemcrypto_key_ref.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. +// +// Reference implementation of OEMCrypto APIs +// +#ifndef OEMCRYPTO_KEY_REF_H_ +#define OEMCRYPTO_KEY_REF_H_ + +#include + +#include +#include +#include + +namespace wvoec_ref { + +class KeyControlBlock { + public: + KeyControlBlock(const std::vector& key_control_string); + ~KeyControlBlock() {} + + bool Validate(); + void Invalidate() { valid_ = false; } + + bool valid() const { return valid_; } + uint32_t duration() const { return duration_; } + void set_duration(uint32_t duration) { duration_ = duration; } + uint32_t nonce() const { return nonce_; } + const char* verification() const { return verification_; } + uint32_t control_bits() const { return control_bits_; } + void RequireLocalDisplay(); + + private: + uint32_t ExtractField(const std::vector& str, int idx); + + bool valid_; + char verification_[4]; + uint32_t duration_; + uint32_t nonce_; + uint32_t control_bits_; +}; + +// AES-128 crypto key, or HMAC signing key. +class Key { + public: + Key(std::vector key_string, const KeyControlBlock& control) + : value_(std::move(key_string)), control_(control), ctr_mode_(true){}; + Key(const Key&) = default; + Key(Key&&) = default; + + virtual ~Key() = default; + void UpdateDuration(const KeyControlBlock& control); + virtual const std::vector& value() const { return value_; } + const KeyControlBlock& control() const { return control_; } + bool ctr_mode() const { return ctr_mode_; } + void set_ctr_mode(bool ctr_mode) { ctr_mode_ = ctr_mode; } + + private: + std::vector value_; + KeyControlBlock control_; + bool ctr_mode_; +}; + +// EntitledKey does not own KeyControlBlock. +class EntitledKey { + public: + explicit EntitledKey(std::vector key_string) + : value_(std::move(key_string)), ctr_mode_(true){}; + EntitledKey(const EntitledKey&) = default; + EntitledKey(EntitledKey&&) = default; + ~EntitledKey() = default; + + const std::vector& value() const { return value_; } + bool ctr_mode() const { return ctr_mode_; } + void set_ctr_mode(bool ctr_mode) { ctr_mode_ = ctr_mode; } + + private: + std::vector value_; + bool ctr_mode_; +}; + +} // namespace wvoec_ref + +#endif // OEMCRYPTO_KEY_REF_H_ diff --git a/oemcrypto/ref/src/oemcrypto_keybox_ref.cpp b/oemcrypto/ref/src/oemcrypto_keybox_ref.cpp new file mode 100644 index 0000000..0e4a630 --- /dev/null +++ b/oemcrypto/ref/src/oemcrypto_keybox_ref.cpp @@ -0,0 +1,74 @@ +// 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. +// +// Reference implementation of OEMCrypto APIs +// +#include "oemcrypto_keybox_ref.h" + +#include + +#include +#include + +#include "log.h" +#include "oemcrypto_types.h" +#include "platform.h" +#include "wvcrc32.h" + +namespace wvoec_ref { + +WvKeybox::WvKeybox() : loaded_(false) { + static std::string fake_device_id = "device_with_no_keybox"; + device_id_.assign(fake_device_id.begin(), fake_device_id.end()); +} + +KeyboxError WvKeybox::Validate() { + if (!loaded_) { + LOGE("[KEYBOX NOT LOADED]"); + return OTHER_ERROR; + } + if (strncmp(reinterpret_cast(magic_), "kbox", 4) != 0) { + LOGE("[KEYBOX HAS BAD MAGIC]"); + return BAD_MAGIC; + } + uint32_t crc_computed; + uint32_t crc_stored; + uint8_t* crc_stored_bytes = (uint8_t*)&crc_stored; + memcpy(crc_stored_bytes, crc_, sizeof(crc_)); + wvoec::WidevineKeybox keybox; + memset(&keybox, 0, sizeof(keybox)); + memcpy(keybox.device_id_, &device_id_[0], device_id_.size()); + memcpy(keybox.device_key_, &device_key_[0], sizeof(keybox.device_key_)); + memcpy(keybox.data_, key_data_, sizeof(keybox.data_)); + memcpy(keybox.magic_, magic_, sizeof(keybox.magic_)); + + crc_computed = ntohl(wvcrc32(reinterpret_cast(&keybox), + sizeof(keybox) - 4)); // Drop last 4 bytes. + if (crc_computed != crc_stored) { + LOGE("[KEYBOX CRC problem: computed = %08x, stored = %08x]\n", + crc_computed, crc_stored); + return BAD_CRC; + } + return NO_ERROR; +} + +bool WvKeybox::InstallKeybox(const uint8_t* buffer, size_t keyBoxLength) { + if (keyBoxLength != 128) { + return false; + } + const wvoec::WidevineKeybox* keybox = + reinterpret_cast(buffer); + size_t device_id_length = + strnlen(reinterpret_cast(keybox->device_id_), 32); + device_id_.assign(keybox->device_id_, keybox->device_id_ + device_id_length); + device_key_.assign(keybox->device_key_, + keybox->device_key_ + sizeof(keybox->device_key_)); + memcpy(key_data_, keybox->data_, sizeof(keybox->data_)); + memcpy(magic_, keybox->magic_, sizeof(keybox->magic_)); + memcpy(crc_, keybox->crc_, sizeof(keybox->crc_)); + loaded_ = true; + return true; +} + +} // namespace wvoec_ref diff --git a/oemcrypto/ref/src/oemcrypto_keybox_ref.h b/oemcrypto/ref/src/oemcrypto_keybox_ref.h new file mode 100644 index 0000000..5f65ea7 --- /dev/null +++ b/oemcrypto/ref/src/oemcrypto_keybox_ref.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. +// +// Reference implementation of OEMCrypto APIs +// +#ifndef OEMCRYPTO_KEYBOX_REF_H_ +#define OEMCRYPTO_KEYBOX_REF_H_ + +#include "oemcrypto_key_ref.h" + +namespace wvoec_ref { + +const int DEVICE_KEY_LENGTH = 16; +typedef uint8_t WvKeyboxKey[DEVICE_KEY_LENGTH]; + +const int KEY_DATA_LENGTH = 72; +typedef uint8_t WvKeyboxKeyData[KEY_DATA_LENGTH]; + +enum KeyboxError { NO_ERROR, BAD_CRC, BAD_MAGIC, OTHER_ERROR }; + +// Widevine keybox +class WvKeybox { + public: + WvKeybox(); + ~WvKeybox() {} + + KeyboxError Validate(); + const std::vector& device_id() { return device_id_; } + std::vector& device_key() { return device_key_; } + const WvKeyboxKeyData& key_data() { return key_data_; } + size_t key_data_length() { return KEY_DATA_LENGTH; } + bool InstallKeybox(const uint8_t* keybox, size_t keyBoxLength); + + private: + bool loaded_; + std::vector device_id_; + std::vector device_key_; + WvKeyboxKeyData key_data_; + uint8_t magic_[4]; + uint8_t crc_[4]; +}; + +} // namespace wvoec_ref + +#endif // OEMCRYPTO_KEYBOX_REF_H_ diff --git a/oemcrypto/ref/src/oemcrypto_nonce_table.cpp b/oemcrypto/ref/src/oemcrypto_nonce_table.cpp new file mode 100644 index 0000000..65d9269 --- /dev/null +++ b/oemcrypto/ref/src/oemcrypto_nonce_table.cpp @@ -0,0 +1,76 @@ +// 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. +// +// Reference implementation of OEMCrypto APIs +// +#include "oemcrypto_nonce_table.h" + +namespace wvoec_ref { + +void NonceTable::AddNonce(uint32_t nonce) { + int new_slot = -1; + int oldest_slot = -1; + + // Flush any nonces that have been checked but not flushed. + // After flush, nonces will be either valid or invalid. + Flush(); + + for (int i = 0; i < kTableSize; ++i) { + // Increase age of all valid nonces. + if (kNTStateValid == state_[i]) { + ++age_[i]; + if (-1 == oldest_slot) { + oldest_slot = i; + } else { + if (age_[i] > age_[oldest_slot]) { + oldest_slot = i; + } + } + } else { + if (-1 == new_slot) { + age_[i] = 0; + nonces_[i] = nonce; + state_[i] = kNTStateValid; + new_slot = i; + } + } + } + if (-1 == new_slot) { + // reuse oldest + // assert (oldest_slot != -1) + int i = oldest_slot; + age_[i] = 0; + nonces_[i] = nonce; + state_[i] = kNTStateValid; + } +} + +bool NonceTable::CheckNonce(uint32_t nonce) { + for (int i = 0; i < kTableSize; ++i) { + if (kNTStateInvalid != state_[i]) { + if (nonce == nonces_[i]) { + state_[i] = kNTStateFlushPending; + return true; + } + } + } + return false; +} + +bool NonceTable::NonceCollision(uint32_t nonce) const { + for (int i = 0; i < kTableSize; ++i) { + if (nonce == nonces_[i]) return true; + } + return false; +} + +void NonceTable::Flush() { + for (int i = 0; i < kTableSize; ++i) { + if (kNTStateFlushPending == state_[i]) { + state_[i] = kNTStateInvalid; + } + } +} + +} // namespace wvoec_ref diff --git a/oemcrypto/ref/src/oemcrypto_nonce_table.h b/oemcrypto/ref/src/oemcrypto_nonce_table.h new file mode 100644 index 0000000..409e7fc --- /dev/null +++ b/oemcrypto/ref/src/oemcrypto_nonce_table.h @@ -0,0 +1,42 @@ +// 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. +// +// Reference implementation of OEMCrypto APIs +// +#ifndef REF_OEMCRYPTO_NONCE_TABLE_H_ +#define REF_OEMCRYPTO_NONCE_TABLE_H_ + +#include + +namespace wvoec_ref { + +class NonceTable { + public: + static const int kTableSize = 4; + NonceTable() { + for (int i = 0; i < kTableSize; ++i) { + state_[i] = kNTStateInvalid; + } + } + ~NonceTable() {} + void AddNonce(uint32_t nonce); + bool CheckNonce(uint32_t nonce); + // Verify that the nonce is not the same as any in this table. + bool NonceCollision(uint32_t nonce) const; + void Flush(); + + private: + enum NonceTableState { + kNTStateInvalid, + kNTStateValid, + kNTStateFlushPending + }; + NonceTableState state_[kTableSize]; + uint32_t age_[kTableSize]; + uint32_t nonces_[kTableSize]; +}; + +} // namespace wvoec_ref + +#endif // REF_OEMCRYPTO_NONCE_TABLE_H_ diff --git a/oemcrypto/ref/src/oemcrypto_ref.cpp b/oemcrypto/ref/src/oemcrypto_ref.cpp new file mode 100644 index 0000000..4904c08 --- /dev/null +++ b/oemcrypto/ref/src/oemcrypto_ref.cpp @@ -0,0 +1,1993 @@ +// 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. +// +// Reference implementation of OEMCrypto APIs +// +#include "OEMCryptoCAS.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "file_store.h" +#include "log.h" +#include "odk.h" +#include "oemcrypto_engine_ref.h" +#include "oemcrypto_session.h" +#include "oemcrypto_usage_table_ref.h" +#include "string_conversions.h" + +#if defined(_WIN32) +# define OEMCRYPTO_API extern "C" __declspec(dllexport) +#else // defined(_WIN32) +# define OEMCRYPTO_API extern "C" __attribute__((visibility("default"))) +#endif // defined(_WIN32) + +namespace { +const uint8_t kBakedInCertificateMagicBytes[] = {0xDE, 0xAD, 0xBE, 0xEF}; + +// Return uint32 referenced through a potentially unaligned pointer. +// If the pointer is nullptr, return 0. +uint32_t unaligned_dereference_uint32(const void* unaligned_ptr) { + if (unaligned_ptr == nullptr) return 0; + uint32_t value; + const uint8_t* src = reinterpret_cast(unaligned_ptr); + uint8_t* dest = reinterpret_cast(&value); + for (unsigned long i = 0; i < sizeof(value); i++) { + dest[i] = src[i]; + } + return value; +} + +} // namespace + +namespace wvoec_ref { + +static std::unique_ptr crypto_engine; + +typedef struct { + uint8_t signature[wvoec::MAC_KEY_SIZE]; + uint8_t context[wvoec::MAC_KEY_SIZE]; + uint8_t iv[wvoec::KEY_IV_SIZE]; + uint8_t enc_rsa_key[]; +} WrappedRSAKey; + +OEMCRYPTO_API OEMCryptoResult OEMCrypto_Initialize(void) { + if (crypto_engine != nullptr) { + LOGE("------------------------- Calling Initialize without Terminate\n"); + crypto_engine.reset(); + } + // NOTE: This requires a compatible Filesystem implementation. + // NOTE: Ownership of the FileSystem object is transferred to CryptoEngine + std::unique_ptr fs(new wvutil::FileSystem()); + crypto_engine.reset(CryptoEngine::MakeCryptoEngine(std::move(fs))); + if (crypto_engine == nullptr || !crypto_engine->Initialize()) { + LOGE("[OEMCrypto_Initialize(): failed]"); + return OEMCrypto_ERROR_INIT_FAILED; + } + return OEMCrypto_SUCCESS; +} + +OEMCRYPTO_API OEMCryptoResult OEMCrypto_SetSandbox( + const uint8_t* /*sandbox_id*/, size_t /*sandbox_id_length*/) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCRYPTO_API OEMCryptoResult OEMCrypto_Terminate(void) { + if (crypto_engine == nullptr) { + LOGE("[OEMCrypto_Terminate(): not initialized]"); + return OEMCrypto_ERROR_TERMINATE_FAILED; + } + crypto_engine->Terminate(); + crypto_engine.reset(); + return OEMCrypto_SUCCESS; +} + +OEMCRYPTO_API OEMCryptoResult OEMCrypto_OpenSession( + OEMCrypto_SESSION* session) { + if (crypto_engine == nullptr) { + LOGE("OEMCrypto_OpenSession: OEMCrypto not initialized."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (crypto_engine->GetNumberOfOpenSessions() >= + crypto_engine->GetMaxNumberOfSessions()) { + LOGE("[OEMCrypto_OpenSession(): failed due to too many sessions]"); + return OEMCrypto_ERROR_TOO_MANY_SESSIONS; + } + SessionId sid = crypto_engine->OpenSession(); + if (sid == 0) { + LOGE("OEMCrypto_OpenSession: invalid session id returned."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + *session = (OEMCrypto_SESSION)sid; + return OEMCrypto_SUCCESS; +} + +OEMCRYPTO_API OEMCryptoResult OEMCrypto_CloseSession( + OEMCrypto_SESSION session) { + if (crypto_engine == nullptr) { + LOGE("OEMCrypto_CloseSession: OEMCrypto not initialized."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (crypto_engine->SessionTypeBits(session) != kSessionTypeOEMCrypto) { + LOGE("Unexpected session type."); + return OEMCrypto_ERROR_INVALID_SESSION; + } + if (!crypto_engine->DestroySession((SessionId)session)) { + return OEMCrypto_ERROR_CLOSE_SESSION_FAILED; + } else { + return OEMCrypto_SUCCESS; + } +} + +OEMCRYPTO_API OEMCryptoResult OEMCrypto_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) { + if (crypto_engine == nullptr) { + LOGE("OEMCrypto_GenerateDerivedKeys: OEMCrypto not initialized."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (crypto_engine->config_provisioning_method() != OEMCrypto_Keybox) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; + } + if (!crypto_engine->ValidRootOfTrust()) { + LOGE("[OEMCrypto_GenerateDerivedKeys(): ERROR_KEYBOX_INVALID]"); + return OEMCrypto_ERROR_KEYBOX_INVALID; + } + + SessionContext* session_ctx = crypto_engine->FindSession(session); + if (session_ctx == nullptr || !session_ctx->isValid()) { + LOGE("[OEMCrypto_GenerateDerivedKeys(): ERROR_INVALID_SESSION]"); + return OEMCrypto_ERROR_INVALID_SESSION; + } + + const std::vector mac_ctx_str( + mac_key_context, mac_key_context + mac_key_context_length); + const std::vector enc_ctx_str( + enc_key_context, enc_key_context + enc_key_context_length); + + // Generate mac and encryption keys for current session context + if (!session_ctx->DeriveKeys(crypto_engine->DeviceRootKey(), mac_ctx_str, + enc_ctx_str)) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + return OEMCrypto_SUCCESS; +} + +OEMCRYPTO_API OEMCryptoResult OEMCrypto_GenerateNonce(OEMCrypto_SESSION session, + uint32_t* nonce) { + if (crypto_engine == nullptr) { + LOGE("OEMCrypto_GenerateNonce: OEMCrypto not initialized."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + SessionContext* session_ctx = crypto_engine->FindSession(session); + if (session_ctx == nullptr || !session_ctx->isValid()) { + LOGE("[OEMCrypto_GenerateNonce(): ERROR_INVALID_SESSION]"); + return OEMCrypto_ERROR_INVALID_SESSION; + } + + // Prevent nonce flood. + static std::chrono::steady_clock clock; + const auto now = clock.now().time_since_epoch(); + static auto last_nonce_time = now; + // For testing, we set nonce_flood_count to 1. Since count is initialized to + // 1, the very first nonce after initialization is counted as a flood. + static int nonce_count = 1; + + if (now - last_nonce_time < std::chrono::seconds(1)) { + nonce_count++; + if (nonce_count > crypto_engine->nonce_flood_count()) { + LOGE("[OEMCrypto_GenerateNonce(): Nonce Flood detected]"); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + } else { + nonce_count = 1; + last_nonce_time = now; + } + + uint32_t nonce_value = 0; + uint8_t* nonce_string = reinterpret_cast(&nonce_value); + + while (nonce_value == 0 || crypto_engine->NonceCollision(nonce_value)) { + // Generate 4 bytes of random data + if (!RAND_bytes(nonce_string, 4)) { + LOGE("[OEMCrypto_GenerateNonce(): Random bytes failure]"); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + } + if (!session_ctx->set_nonce(nonce_value)) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + *nonce = nonce_value; + return OEMCrypto_SUCCESS; +} + +OEMCRYPTO_API OEMCryptoResult OEMCrypto_PrepAndSignLicenseRequest( + OEMCrypto_SESSION session, uint8_t* message, size_t message_length, + size_t* core_message_length, uint8_t* signature, size_t* signature_length) { + if (crypto_engine == nullptr) { + LOGE("OEMCrypto Not Initialized."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + SessionContext* session_ctx = crypto_engine->FindSession(session); + if (session_ctx == nullptr || !session_ctx->isValid()) { + LOGE("OEMCrypto_ERROR_INVALID_SESSION"); + return OEMCrypto_ERROR_INVALID_SESSION; + } + return session_ctx->PrepAndSignLicenseRequest(message, message_length, + core_message_length, signature, + signature_length); +} + +OEMCRYPTO_API OEMCryptoResult OEMCrypto_PrepAndSignRenewalRequest( + OEMCrypto_SESSION session, uint8_t* message, size_t message_length, + size_t* core_message_length, uint8_t* signature, size_t* signature_length) { + if (crypto_engine == nullptr) { + LOGE("OEMCrypto Not Initialized."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + SessionContext* session_ctx = crypto_engine->FindSession(session); + if (session_ctx == nullptr || !session_ctx->isValid()) { + LOGE("OEMCrypto_ERROR_INVALID_SESSION"); + return OEMCrypto_ERROR_INVALID_SESSION; + } + return session_ctx->PrepAndSignRenewalRequest(message, message_length, + core_message_length, signature, + signature_length); +} + +OEMCRYPTO_API OEMCryptoResult OEMCrypto_PrepAndSignProvisioningRequest( + OEMCrypto_SESSION session, uint8_t* message, size_t message_length, + size_t* core_message_length, uint8_t* signature, size_t* signature_length) { + if (crypto_engine == nullptr) { + LOGE("OEMCrypto Not Initialized."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + SessionContext* session_ctx = crypto_engine->FindSession(session); + if (session_ctx == nullptr || !session_ctx->isValid()) { + LOGE("OEMCrypto_ERROR_INVALID_SESSION"); + return OEMCrypto_ERROR_INVALID_SESSION; + } + return session_ctx->PrepAndSignProvisioningRequest( + message, message_length, core_message_length, signature, + signature_length); +} + +bool RangeCheck(const uint8_t* message, uint32_t message_length, + const uint8_t* field, uint32_t field_length, bool allow_null) { + if (field == nullptr) return allow_null; + if (field < message) return false; + if (field + field_length > message + message_length) return false; + return true; +} + +bool RangeCheck(uint32_t message_length, const OEMCrypto_Substring& substring, + bool allow_null) { + if (!substring.length) return allow_null; + if (substring.offset > message_length) return false; + if (substring.offset + substring.length > message_length) return false; + return true; +} + +OEMCRYPTO_API 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) { + if (crypto_engine == nullptr) { + LOGE("not initialized"); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (message == nullptr || message_length == 0 || signature == nullptr || + signature_length == 0) { + LOGE("OEMCrypto_ERROR_INVALID_CONTEXT"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if (!crypto_engine->ValidRootOfTrust()) { + LOGE("ERROR_KEYBOX_INVALID"); + return OEMCrypto_ERROR_KEYBOX_INVALID; + } + if (crypto_engine->SessionTypeBits(session) != kSessionTypeOEMCrypto) { + LOGE("Unexpected session type."); + return OEMCrypto_ERROR_INVALID_SESSION; + } + SessionContext* session_ctx = crypto_engine->FindSession(session); + if (session_ctx == nullptr || !session_ctx->isValid()) { + LOGE("ERROR_INVALID_SESSION sid=%d", session); + return OEMCrypto_ERROR_INVALID_SESSION; + } + return session_ctx->LoadLicense(message, message_length, core_message_length, + signature, signature_length); +} + +OEMCRYPTO_API 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 num_keys, const OEMCrypto_KeyObject* key_array, + OEMCrypto_Substring pst, OEMCrypto_Substring srm_restriction_data, + OEMCrypto_LicenseType license_type) { + if (crypto_engine == nullptr) { + LOGE("OEMCrypto_LoadKeys: OEMCrypto Not Initialized."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (!crypto_engine->ValidRootOfTrust()) { + LOGE("ERROR_KEYBOX_INVALID"); + return OEMCrypto_ERROR_KEYBOX_INVALID; + } + if (crypto_engine->SessionTypeBits(session) != kSessionTypeOEMCrypto) { + LOGE("Unexpected session type."); + return OEMCrypto_ERROR_INVALID_SESSION; + } + SessionContext* session_ctx = crypto_engine->FindSession(session); + if (session_ctx == nullptr || !session_ctx->isValid()) { + LOGE("ERROR_INVALID_SESSION sid=%d", session); + return OEMCrypto_ERROR_INVALID_SESSION; + } + if (message == nullptr || message_length == 0 || signature == nullptr || + signature_length == 0 || key_array == nullptr || num_keys == 0) { + LOGE("[OEMCrypto_LoadKeys(): OEMCrypto_ERROR_INVALID_CONTEXT]"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + + // Range check + if (!RangeCheck(message_length, enc_mac_keys_iv, true) || + !RangeCheck(message_length, enc_mac_keys, true) || + !RangeCheck(message_length, pst, true) || + !RangeCheck(message_length, srm_restriction_data, true)) { + LOGE("[OEMCrypto_LoadKeys(): OEMCrypto_ERROR_INVALID_CONTEXT - " + "range check.]"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + + for (unsigned int i = 0; i < num_keys; i++) { + if (!RangeCheck(message_length, key_array[i].key_id, false) || + !RangeCheck(message_length, key_array[i].key_data, false) || + !RangeCheck(message_length, key_array[i].key_data_iv, false) || + !RangeCheck(message_length, key_array[i].key_control, false) || + !RangeCheck(message_length, key_array[i].key_control_iv, false)) { + LOGE("[OEMCrypto_LoadKeys(): OEMCrypto_ERROR_INVALID_CONTEXT - " + "range check %d]", i); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + } + if (enc_mac_keys.offset >= wvoec::KEY_IV_SIZE && enc_mac_keys.length > 0) { + if (enc_mac_keys_iv.offset + wvoec::KEY_IV_SIZE == enc_mac_keys.offset) { + LOGE("[OEMCrypto_LoadKeys(): OEMCrypto_ERROR_INVALID_CONTEXT - " + "range check iv]"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } else { + if (CRYPTO_memcmp(message + enc_mac_keys.offset - wvoec::KEY_IV_SIZE, + message + enc_mac_keys_iv.offset, + wvoec::KEY_IV_SIZE) == 0) { + LOGE("[OEMCrypto_LoadKeys(): OEMCrypto_ERROR_INVALID_CONTEXT - " + "suspicious iv]"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + } + } + return session_ctx->LoadKeys(message, message_length, signature, + signature_length, enc_mac_keys_iv, enc_mac_keys, + num_keys, key_array, pst, srm_restriction_data, + license_type); +} + +OEMCRYPTO_API OEMCryptoResult OEMCrypto_LoadEntitledContentKeys( + OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, + size_t key_array_length, + const OEMCrypto_EntitledContentKeyObject* key_array) { + if (key_array_length == 0) { + LOGE("[OEMCrypto_LoadEntitledContentKeys(): key_array is empty."); + return OEMCrypto_SUCCESS; + } + if (key_array == nullptr) { + LOGE("[OEMCrypto_LoadEntitledContentKeys(): missing key_array."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (crypto_engine == nullptr) { + LOGE("OEMCrypto_LoadEntitledContentKeys(): OEMCrypto Not Initialized."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (crypto_engine->SessionTypeBits(session) != kSessionTypeEntitledKey) { + LOGE("OEMCrypto_LoadEntitledContentKeys(): wrong session type."); + return OEMCrypto_ERROR_INVALID_ENTITLED_KEY_SESSION; + } + + EntitledKeySession* entitled_key_session = + crypto_engine->FindEntitledKeySession(session); + if (entitled_key_session == nullptr) { + LOGE("OEMCrypto_LoadEntitledContentKeys(): can not find the key session."); + return OEMCrypto_ERROR_INVALID_ENTITLED_KEY_SESSION; + } + SessionContext* session_ctx = crypto_engine->FindSession(session); + if (session_ctx == nullptr || !session_ctx->isValid()) { + LOGE("[OEMCrypto_LoadEntitledContentKeys(): ERROR_INVALID_SESSION]"); + return OEMCrypto_ERROR_INVALID_SESSION; + } + for (size_t i = 0; i < key_array_length; i++) { + if (!RangeCheck(message_length, key_array[i].entitlement_key_id, false) || + !RangeCheck(message_length, key_array[i].content_key_id, false) || + !RangeCheck(message_length, key_array[i].content_key_data_iv, false) || + !RangeCheck(message_length, key_array[i].content_key_data, false)) { + LOGE( + "[OEMCrypto_LoadEntitledContentKeys(): " + "OEMCrypto_ERROR_INVALID_CONTEXT -range " + "check %zu]", + i); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + } + return session_ctx->LoadEntitledContentKeys(entitled_key_session, message, + message_length, key_array_length, + key_array); +} + +OEMCRYPTO_API OEMCryptoResult OEMCrypto_LoadCasECMKeys( + OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, + const OEMCrypto_EntitledCasKeyObject* even_key, + const OEMCrypto_EntitledCasKeyObject* odd_key) { + // Implementations that do not use a hardware demuxer and + // descrambler (including this reference implementation) can load keys using + // LoadEntitledContentKeys support. Implementations that do support hardware + // demux and descramble can set the keys in hardware. + if (!even_key && !odd_key) { + LOGE("[OEMCrypto_LoadCasECMKeys(): no keys."); + return OEMCrypto_SUCCESS; + } + if (crypto_engine == nullptr) { + LOGE("OEMCrypto_LoadCasECMKeys: OEMCrypto Not Initialized."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (crypto_engine->SessionTypeBits(session) != kSessionTypeEntitledKey) { + LOGE("OEMCrypto_LoadCasECMKeys(): wrong session type."); + return OEMCrypto_ERROR_INVALID_ENTITLED_KEY_SESSION; + } + + EntitledKeySession* entitled_key_session = + crypto_engine->FindEntitledKeySession(session); + if (entitled_key_session == nullptr) { + LOGE("OEMCrypto_LoadCasECMKeys(): can not find the key session."); + return OEMCrypto_ERROR_INVALID_ENTITLED_KEY_SESSION; + } + SessionContext* session_ctx = crypto_engine->FindSession(session); + if (session_ctx == nullptr || !session_ctx->isValid()) { + LOGE("[OEMCrypto_LoadCasECMKeys(): ERROR_INVALID_SESSION]"); + return OEMCrypto_ERROR_INVALID_SESSION; + } + std::vector key_array; + key_array.reserve(2); + if (even_key) key_array.push_back(*even_key); + if (odd_key) key_array.push_back(*odd_key); + for (unsigned int i = 0; i < key_array.size(); i++) { + if (!RangeCheck(message_length, key_array[i].entitlement_key_id, false) || + !RangeCheck(message_length, key_array[i].content_key_id, false) || + !RangeCheck(message_length, key_array[i].content_key_data_iv, false) || + !RangeCheck(message_length, key_array[i].content_key_data, false) || + !RangeCheck(message_length, key_array[i].content_iv, false)) { + LOGE( + "[OEMCrypto_LoadCasECMKeys(): " + "OEMCrypto_ERROR_INVALID_CONTEXT -range " + "check %zu]", + i); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + } + return session_ctx->LoadEntitledCasKeys(entitled_key_session, message, + message_length, key_array.size(), + &key_array[0]); +} + +OEMCRYPTO_API 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) { + if (crypto_engine == nullptr) { + LOGE("OEMCrypto Not Initialized."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + + if (!crypto_engine->ValidRootOfTrust()) { + LOGE("ERROR_KEYBOX_INVALID"); + return OEMCrypto_ERROR_KEYBOX_INVALID; + } + + SessionContext* session_ctx = crypto_engine->FindSession(session); + if (session_ctx == nullptr || !session_ctx->isValid()) { + LOGE("ERROR_INVALID_SESSION"); + return OEMCrypto_ERROR_INVALID_SESSION; + } + + if (message == nullptr || message_length == 0 || signature == nullptr || + signature_length == 0) { + LOGE("ERROR_INVALID_CONTEXT"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + return session_ctx->LoadRenewal(message, message_length, core_message_length, + signature, signature_length); +} + +OEMCRYPTO_API 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) { + if (crypto_engine == nullptr) { + LOGE("OEMCrypto Not Initialized."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + + if (!crypto_engine->ValidRootOfTrust()) { + LOGE("ERROR_KEYBOX_INVALID"); + return OEMCrypto_ERROR_KEYBOX_INVALID; + } + + SessionContext* session_ctx = crypto_engine->FindSession(session); + if (session_ctx == nullptr || !session_ctx->isValid()) { + LOGE("ERROR_INVALID_SESSION"); + return OEMCrypto_ERROR_INVALID_SESSION; + } + + if (message == nullptr || message_length == 0 || signature == nullptr || + signature_length == 0 || num_keys == 0) { + LOGE("ERROR_INVALID_CONTEXT"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + + // We only use the first key object to update the entire license. Since we + // know num_keys > 0 after the last if statement, we can assume index is not + // out of bounds. + constexpr size_t kIndex = 0; + + // Range check. + if (!RangeCheck(message_length, key_array[kIndex].key_id, true) || + !RangeCheck(message_length, key_array[kIndex].key_control, false) || + !RangeCheck(message_length, key_array[kIndex].key_control_iv, true)) { + LOGE("Range Check %zu", kIndex); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + + // Validate message signature + if (!session_ctx->ValidateMessage(message, message_length, signature, + signature_length)) { + LOGE("Signature was invalid"); + return OEMCrypto_ERROR_SIGNATURE_FAILURE; + } + + // Decrypt and refresh keys in key refresh object + OEMCryptoResult status = OEMCrypto_SUCCESS; + std::vector key_id; + std::vector key_control; + std::vector key_control_iv; + if (key_array[kIndex].key_id.length != 0) { + key_id.assign(message + key_array[kIndex].key_id.offset, + message + key_array[kIndex].key_id.offset + + key_array[kIndex].key_id.length); + key_control.assign(message + key_array[kIndex].key_control.offset, + message + key_array[kIndex].key_control.offset + + wvoec::KEY_CONTROL_SIZE); + if (key_array[kIndex].key_control_iv.length == 0) { + key_control_iv.clear(); + } else { + key_control_iv.assign(message + key_array[kIndex].key_control_iv.offset, + message + key_array[kIndex].key_control_iv.offset + + wvoec::KEY_IV_SIZE); + } + } else { + // key_id could be null if special control key type + // key_control is not encrypted in this case + key_id.clear(); + key_control_iv.clear(); + key_control.assign(message + key_array[kIndex].key_control.offset, + message + key_array[kIndex].key_control.offset + + wvoec::KEY_CONTROL_SIZE); + } + + status = session_ctx->RefreshKey(key_id, key_control, key_control_iv); + if (status != OEMCrypto_SUCCESS) { + return status; + } + return OEMCrypto_SUCCESS; +} + +OEMCRYPTO_API OEMCryptoResult OEMCrypto_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) { + if (crypto_engine == nullptr) { + LOGE("OEMCrypto_QueryKeyControl: OEMCrypto Not Initialized."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + uint32_t* block = reinterpret_cast(key_control_block); + if ((key_control_block_length == nullptr) || + (*key_control_block_length < wvoec::KEY_CONTROL_SIZE)) { + LOGE("[OEMCrypto_QueryKeyControl(): OEMCrypto_ERROR_SHORT_BUFFER]"); + return OEMCrypto_ERROR_SHORT_BUFFER; + } + *key_control_block_length = wvoec::KEY_CONTROL_SIZE; + if (key_id == nullptr) { + LOGE( + "[OEMCrypto_QueryKeyControl(): key_id null. " + "OEMCrypto_ERROR_UNKNOWN_FAILURE]"); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + + SessionContext* session_ctx = crypto_engine->FindSession(session); + if (session_ctx == nullptr || !session_ctx->isValid()) { + LOGE("[OEMCrypto_QueryKeyControl(): ERROR_INVALID_SESSION]"); + return OEMCrypto_ERROR_INVALID_SESSION; + } + EntitledKeySession* entitled_key_session = nullptr; + if (crypto_engine->SessionTypeBits(session) == kSessionTypeEntitledKey) { + entitled_key_session = crypto_engine->FindEntitledKeySession(session); + if (entitled_key_session == nullptr) { + LOGE("OEMCrypto_QueryKeyControl(): can not find the key session."); + return OEMCrypto_ERROR_INVALID_ENTITLED_KEY_SESSION; + } + } + const std::vector key_id_str = + std::vector(key_id, key_id + key_id_length); + if (!session_ctx->QueryKeyControlBlock(entitled_key_session, key_id_str, + block)) { + LOGE("[OEMCrypto_QueryKeyControl(): FAIL]"); + return OEMCrypto_ERROR_NO_CONTENT_KEY; + } + return OEMCrypto_SUCCESS; +} + +OEMCRYPTO_API OEMCryptoResult OEMCrypto_SelectKey( + const OEMCrypto_SESSION session, const uint8_t* key_id, + size_t key_id_length, OEMCryptoCipherMode cipher_mode) { +#ifndef NDEBUG + if (!crypto_engine->ValidRootOfTrust()) { + LOGE("[OEMCrypto_SelectKey(): ERROR_KEYBOX_INVALID]"); + return OEMCrypto_ERROR_KEYBOX_INVALID; + } +#endif + + SessionContext* session_ctx = crypto_engine->FindSession(session); + if (session_ctx == nullptr || !session_ctx->isValid()) { + LOGE("[OEMCrypto_SelectKey(): ERROR_INVALID_SESSION]"); + return OEMCrypto_ERROR_INVALID_SESSION; + } + const std::vector key_id_str = + std::vector(key_id, key_id + key_id_length); + + if (crypto_engine->SessionTypeBits(session) == kSessionTypeEntitledKey) { + return session_ctx->SelectEntitledContentKey( + crypto_engine->FindEntitledKeySession(session), key_id_str, + cipher_mode); + } + return session_ctx->SelectContentKey(key_id_str, cipher_mode); +} + +OEMCRYPTO_API OEMCryptoResult OEMCrypto_DecryptCENC( + OEMCrypto_SESSION session, const OEMCrypto_SampleDescription* samples, + size_t samples_length, const OEMCrypto_CENCEncryptPatternDesc* pattern) { + if (crypto_engine == nullptr) { + LOGE("OEMCrypto_DecryptCENC: OEMCrypto Not Initialized."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (samples == nullptr || samples_length == 0) { + LOGE("[OEMCrypto_DecryptCENC(): No samples]"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if (pattern == nullptr) { + LOGE("[OEMCrypto_DecryptCENC(): No pattern]"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + + SessionContext* session_ctx = crypto_engine->FindSession(session); + if (session_ctx == nullptr || !session_ctx->isValid()) { + LOGE("[OEMCrypto_DecryptCENC(): ERROR_INVALID_SESSION]"); + return OEMCrypto_ERROR_INVALID_SESSION; + } + + EntitledKeySession* entitled_key_session = nullptr; + if (crypto_engine->SessionTypeBits(session) == kSessionTypeEntitledKey) { + entitled_key_session = crypto_engine->FindEntitledKeySession(session); + if (entitled_key_session == nullptr) { + LOGE("OEMCrypto_DecryptCENC(): can not find the key session."); + return OEMCrypto_ERROR_INVALID_ENTITLED_KEY_SESSION; + } + } + +#ifndef NDEBUG + if (!crypto_engine->ValidRootOfTrust()) { + LOGE("[OEMCrypto_DecryptCENC(): ERROR_KEYBOX_INVALID]"); + return OEMCrypto_ERROR_KEYBOX_INVALID; + } +#endif + + // Iterate through all the samples and validate them before doing any decrypt + for (size_t sample_index = 0; sample_index < samples_length; ++sample_index) { + const OEMCrypto_SampleDescription& sample = samples[sample_index]; + + if (sample.buffers.input_data == nullptr || + sample.buffers.input_data_length == 0) { + LOGE("[OEMCrypto_DecryptCENC(): OEMCrypto_ERROR_INVALID_CONTEXT]"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if (crypto_engine->max_sample_size() > 0 && + sample.buffers.input_data_length > crypto_engine->max_sample_size()) { + // For testing reasons only, pretend that this integration only supports + // the given buffer size. + LOGE("[OEMCrypto_DecryptCENC(): Sample too large]"); + return OEMCrypto_ERROR_BUFFER_TOO_LARGE; + } + + // Iterate through all the subsamples and sum their lengths + size_t subsample_length_tally = 0; + for (size_t subsample_index = 0; subsample_index < sample.subsamples_length; + ++subsample_index) { + const OEMCrypto_SubSampleDescription& subsample = + sample.subsamples[subsample_index]; + const size_t length = + subsample.num_bytes_clear + subsample.num_bytes_encrypted; + if (crypto_engine->max_subsample_size() > 0 && + length > crypto_engine->max_subsample_size()) { + // For testing reasons only, pretend that this integration only supports + // the given buffer size. + LOGE("[OEMCrypto_DecryptCENC(): Subsample too large]"); + return OEMCrypto_ERROR_BUFFER_TOO_LARGE; + } + subsample_length_tally += length; + } + if (subsample_length_tally != sample.buffers.input_data_length) { + LOGE( + "[OEMCrypto_DecryptCENC(): Sample and subsample lengths do not " + "match.]"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + } + + return session_ctx->DecryptSamples(entitled_key_session, samples, + samples_length, pattern); +} + +OEMCRYPTO_API OEMCryptoResult OEMCrypto_CopyBuffer( + OEMCrypto_SESSION session, const uint8_t* data_addr, size_t data_length, + const OEMCrypto_DestBufferDesc* out_buffer_descriptor, + uint8_t subsample_flags) { + if (crypto_engine == nullptr) { + LOGE("OEMCrypto_CopyBuffer: OEMCrypto Not Initialized."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (data_addr == nullptr || out_buffer_descriptor == nullptr) { + LOGE("[OEMCrypto_CopyBuffer(): OEMCrypto_ERROR_INVALID_CONTEXT]"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if (crypto_engine->max_subsample_size() > 0 && + data_length > crypto_engine->max_subsample_size()) { + // For testing reasons only, pretend that this integration only supports + // the minimum possible buffer size. + LOGE("[OEMCrypto_CopyBuffer(): OEMCrypto_ERROR_BUFFER_TOO_LARGE]"); + return OEMCrypto_ERROR_BUFFER_TOO_LARGE; + } + OEMCryptoResult status = crypto_engine->SetDestination( + *out_buffer_descriptor, data_length, subsample_flags); + if (status != OEMCrypto_SUCCESS) return status; + if (crypto_engine->destination() != nullptr) { + memmove(crypto_engine->destination(), data_addr, data_length); + } + return crypto_engine->PushDestination(*out_buffer_descriptor, + subsample_flags); +} + +OEMCRYPTO_API OEMCryptoResult OEMCrypto_WrapKeyboxOrOEMCert( + const uint8_t* keybox, size_t keyBoxLength, uint8_t* wrappedKeybox, + size_t* wrappedKeyBoxLength, const uint8_t* transportKey, + size_t transportKeyLength) { + if (crypto_engine->config_provisioning_method() != OEMCrypto_Keybox) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; + } + if (!keybox || !wrappedKeybox || !wrappedKeyBoxLength || + (keyBoxLength != *wrappedKeyBoxLength)) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + // This implementation ignores the transport key. For test keys, we + // don't need to encrypt the keybox. + memcpy(wrappedKeybox, keybox, keyBoxLength); + return OEMCrypto_SUCCESS; +} + +OEMCRYPTO_API OEMCryptoResult OEMCrypto_InstallKeyboxOrOEMCert( + const uint8_t* keybox, size_t keyBoxLength) { + if (crypto_engine == nullptr) { + LOGE("OEMCrypto_InstallKeyboxOrOEMCert: OEMCrypto Not Initialized."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (crypto_engine->config_provisioning_method() != OEMCrypto_Keybox) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; + } + if (crypto_engine->InstallKeybox(keybox, keyBoxLength)) { + return OEMCrypto_SUCCESS; + } + return OEMCrypto_ERROR_WRITE_KEYBOX; +} + +OEMCRYPTO_API OEMCryptoResult OEMCrypto_LoadTestKeybox(const uint8_t* buffer, + size_t length) { + if (crypto_engine == nullptr) { + LOGE("OEMCrypto_LoadTestKeybox: OEMCrypto Not Initialized."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (crypto_engine->config_provisioning_method() != OEMCrypto_Keybox) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; + } + crypto_engine->UseTestKeybox(buffer, length); + return OEMCrypto_SUCCESS; +} + +OEMCRYPTO_API OEMCryptoResult OEMCrypto_IsKeyboxOrOEMCertValid(void) { + if (crypto_engine == nullptr) { + LOGE("OEMCrypto_IsKeyboxOrOEMCertValid: OEMCrypto Not Initialized."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + switch (crypto_engine->config_provisioning_method()) { + case OEMCrypto_DrmCertificate: + return OEMCrypto_SUCCESS; + case OEMCrypto_Keybox: + switch (crypto_engine->ValidateKeybox()) { + case NO_ERROR: + return OEMCrypto_SUCCESS; + case BAD_CRC: + return OEMCrypto_ERROR_BAD_CRC; + case BAD_MAGIC: + return OEMCrypto_ERROR_BAD_MAGIC; + default: + case OTHER_ERROR: + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + break; + case OEMCrypto_OEMCertificate: + // TODO(fredgc): verify that the certificate exists and is valid. + return OEMCrypto_SUCCESS; + break; + default: + LOGE("Invalid provisioning method: %d.", + crypto_engine->config_provisioning_method()); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } +} + +OEMCRYPTO_API OEMCrypto_ProvisioningMethod OEMCrypto_GetProvisioningMethod() { + if (crypto_engine == nullptr) { + LOGE("OEMCrypto_GetProvisioningMethod: OEMCrypto Not Initialized."); + return OEMCrypto_ProvisioningError; + } + return crypto_engine->config_provisioning_method(); +} + +OEMCRYPTO_API OEMCryptoResult +OEMCrypto_LoadOEMPrivateKey(OEMCrypto_SESSION session) { + if (crypto_engine == nullptr) { + LOGE("OEMCrypto Not Initialized."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (crypto_engine->config_provisioning_method() != OEMCrypto_OEMCertificate) { + LOGE("Unexpected provisioning method = %d.", + crypto_engine->config_provisioning_method()); + return OEMCrypto_ERROR_NOT_IMPLEMENTED; + } + SessionContext* session_ctx = crypto_engine->FindSession(session); + if (session_ctx == nullptr || !session_ctx->isValid()) { + LOGE("OEMCrypto_ERROR_INVALID_SESSION"); + return OEMCrypto_ERROR_INVALID_SESSION; + } + return crypto_engine->load_oem_private_key(session_ctx); +} + +OEMCRYPTO_API OEMCryptoResult OEMCrypto_GetOEMPublicCertificate( + uint8_t* public_cert, size_t* public_cert_length) { + if (crypto_engine == nullptr) { + LOGE("OEMCrypto Not Initialized."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (crypto_engine->config_provisioning_method() != OEMCrypto_OEMCertificate) { + LOGE("Unexpected provisioning method = %d.", + crypto_engine->config_provisioning_method()); + return OEMCrypto_ERROR_NOT_IMPLEMENTED; + } + return crypto_engine->get_oem_certificate(public_cert, public_cert_length); +} + +OEMCRYPTO_API OEMCryptoResult OEMCrypto_GetDeviceID(uint8_t* deviceID, + size_t* idLength) { + if (crypto_engine == nullptr) { + LOGE("OEMCrypto_GetDeviceID: OEMCrypto Not Initialized."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + const std::vector& dev_id_string = crypto_engine->DeviceRootId(); + if (dev_id_string.empty()) { + LOGE("[OEMCrypto_GetDeviceId(): Keybox Invalid]"); + return OEMCrypto_ERROR_KEYBOX_INVALID; + } + + size_t dev_id_len = dev_id_string.size(); + if (*idLength < dev_id_len) { + *idLength = dev_id_len; + LOGE("[OEMCrypto_GetDeviceId(): ERROR_SHORT_BUFFER]"); + return OEMCrypto_ERROR_SHORT_BUFFER; + } + memset(deviceID, 0, *idLength); + memcpy(deviceID, &dev_id_string[0], dev_id_len); + *idLength = dev_id_len; + return OEMCrypto_SUCCESS; +} + +OEMCRYPTO_API OEMCryptoResult OEMCrypto_GetKeyData(uint8_t* keyData, + size_t* keyDataLength) { + if (crypto_engine == nullptr) { + LOGE("OEMCrypto_GetKeyData: OEMCrypto Not Initialized."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (crypto_engine->config_provisioning_method() != OEMCrypto_Keybox) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; + } + size_t length = crypto_engine->DeviceRootTokenLength(); + if (keyDataLength == nullptr) { + LOGE("[OEMCrypto_GetKeyData(): null pointer. ERROR_UNKNOWN_FAILURE]"); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (*keyDataLength < length) { + *keyDataLength = length; + LOGE("[OEMCrypto_GetKeyData(): ERROR_SHORT_BUFFER]"); + return OEMCrypto_ERROR_SHORT_BUFFER; + } + if (keyData == nullptr) { + LOGE("[OEMCrypto_GetKeyData(): null pointer. ERROR_UNKNOWN_FAILURE]"); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + memset(keyData, 0, *keyDataLength); + memcpy(keyData, crypto_engine->DeviceRootToken(), length); + *keyDataLength = length; + return OEMCrypto_SUCCESS; +} + +OEMCRYPTO_API OEMCryptoResult OEMCrypto_GetRandom(uint8_t* randomData, + size_t dataLength) { + if (crypto_engine == nullptr) { + LOGE("OEMCrypto_GetRandom: OEMCrypto Not Initialized."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (!randomData) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (RAND_bytes(randomData, dataLength)) { + return OEMCrypto_SUCCESS; + } + return OEMCrypto_ERROR_UNKNOWN_FAILURE; +} + +// This function is no longer exported -- it is only used by LoadProvisioning. +static 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) { + uint32_t nonce = unaligned_dereference_uint32(unaligned_nonce); + if (unaligned_nonce == nullptr) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if (crypto_engine == nullptr) { + LOGE("OEMCrypto_RewrapDeviceRSAKey30: OEMCrypto Not Initialized."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (wrapped_rsa_key_length == nullptr) { + LOGE("[OEMCrypto_RewrapDeviceRSAKey30(): OEMCrypto_ERROR_INVALID_CONTEXT]"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + // For the reference implementation, the wrapped key and the encrypted + // key are the same size -- just encrypted with different keys. + // We add 32 bytes for a context, 32 for iv, and 32 bytes for a signature. + // Important: This layout must match OEMCrypto_LoadDRMPrivateKey below. + const size_t buffer_size = enc_rsa_key_length + sizeof(WrappedRSAKey); + + if (wrapped_rsa_key == nullptr || *wrapped_rsa_key_length < buffer_size) { + *wrapped_rsa_key_length = buffer_size; + return OEMCrypto_ERROR_SHORT_BUFFER; + } + *wrapped_rsa_key_length = buffer_size; // Tell caller how much space we used. + if (!crypto_engine->ValidRootOfTrust()) { + LOGE("[OEMCrypto_RewrapDeviceRSAKey30(): ERROR_KEYBOX_INVALID]"); + return OEMCrypto_ERROR_KEYBOX_INVALID; + } + SessionContext* session_ctx = crypto_engine->FindSession(session); + if (session_ctx == nullptr || !session_ctx->isValid()) { + LOGE("[OEMCrypto_RewrapDeviceRSAKey30(): ERROR_INVALID_SESSION]"); + return OEMCrypto_ERROR_INVALID_SESSION; + } + if (encrypted_message_key == nullptr || encrypted_message_key_length == 0 || + enc_rsa_key == nullptr || enc_rsa_key_iv == nullptr || + unaligned_nonce == nullptr) { + LOGE("[OEMCrypto_RewrapDeviceRSAKey30(): OEMCrypto_ERROR_INVALID_CONTEXT]"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + + // Validate nonce + if (!session_ctx->CheckNonce(nonce)) { + return OEMCrypto_ERROR_INVALID_NONCE; + } + + if (!session_ctx->InstallRSAEncryptedKey(encrypted_message_key, + encrypted_message_key_length)) { + LOGE( + "OEMCrypto_RewrapDeviceRSAKey30: " + "Error loading encrypted_message_key."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + + // Decrypt RSA key. + std::vector pkcs8_rsa_key(enc_rsa_key_length); + if (!session_ctx->DecryptRSAKey(enc_rsa_key, enc_rsa_key_length, + enc_rsa_key_iv, &pkcs8_rsa_key[0])) { + return OEMCrypto_ERROR_INVALID_RSA_KEY; + } + size_t padding = pkcs8_rsa_key[enc_rsa_key_length - 1]; + if (padding > 16) { + // Do not return an error at this point, to avoid a padding oracle attack. + padding = 0; + } + size_t rsa_key_length = enc_rsa_key_length - padding; + if (!session_ctx->LoadRSAKey(&pkcs8_rsa_key[0], rsa_key_length)) { + LOGE("[OEMCrypto_RewrapDeviceRSAKey30(): Failed to LoadRSAKey."); + return OEMCrypto_ERROR_INVALID_RSA_KEY; + } + + // Now we generate a wrapped keybox. + WrappedRSAKey* wrapped = reinterpret_cast(wrapped_rsa_key); + // Pick a random context and IV for generating keys. + if (!RAND_bytes(wrapped->context, sizeof(wrapped->context))) { + LOGE("[_RewrapDeviceRSAKey30(): RAND_bytes failed."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (!RAND_bytes(wrapped->iv, sizeof(wrapped->iv))) { + LOGE("[_RewrapDeviceRSAKey30(): RAND_bytes failed."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + const std::vector context( + wrapped->context, wrapped->context + sizeof(wrapped->context)); + // Generate mac and encryption keys for encrypting the signature. + if (!session_ctx->DeriveKeys(crypto_engine->DeviceRootKey(), context, + context)) { + LOGE("[_RewrapDeviceRSAKey30(): DeriveKeys failed."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + + // Encrypt rsa key with keybox. + if (!session_ctx->EncryptRSAKey(&pkcs8_rsa_key[0], enc_rsa_key_length, + wrapped->iv, wrapped->enc_rsa_key)) { + LOGE("[_RewrapDeviceRSAKey30(): EncrypteRSAKey failed."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + // The wrapped keybox must be signed with the same key we verify with. I'll + // pick the server key, so I don't have to modify LoadRSAKey. + unsigned int sig_length = sizeof(wrapped->signature); + if (!HMAC(EVP_sha256(), &session_ctx->mac_key_server()[0], + session_ctx->mac_key_server().size(), wrapped->context, + buffer_size - sizeof(wrapped->signature), wrapped->signature, + &sig_length)) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + return OEMCrypto_SUCCESS; +} + +// This function is no longer exported -- it is only used by LoadProvisioning. +static 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) { + uint32_t nonce = unaligned_dereference_uint32(unaligned_nonce); + if (unaligned_nonce == nullptr) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if (crypto_engine == nullptr) { + LOGE("OEMCrypto_RewrapDeviceRSAKey: OEMCrypto Not Initialized."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (crypto_engine->config_provisioning_method() != OEMCrypto_Keybox) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; + } + if (wrapped_rsa_key_length == nullptr) { + LOGE("[OEMCrypto_RewrapDeviceRSAKey(): OEMCrypto_ERROR_INVALID_CONTEXT]"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + // For the reference implementation, the wrapped key and the encrypted + // key are the same size -- just encrypted with different keys. + // We add 32 bytes for a context, 32 for iv, and 32 bytes for a signature. + // Important: This layout must match OEMCrypto_LoadDRMPrivateKey below. + const size_t buffer_size = enc_rsa_key_length + sizeof(WrappedRSAKey); + + if (wrapped_rsa_key == nullptr || *wrapped_rsa_key_length < buffer_size) { + *wrapped_rsa_key_length = buffer_size; + return OEMCrypto_ERROR_SHORT_BUFFER; + } + *wrapped_rsa_key_length = buffer_size; // Tell caller how much space we used. + if (!crypto_engine->ValidRootOfTrust()) { + LOGE("[OEMCrypto_RewrapDeviceRSAKey(): ERROR_KEYBOX_INVALID]"); + return OEMCrypto_ERROR_KEYBOX_INVALID; + } + SessionContext* session_ctx = crypto_engine->FindSession(session); + if (session_ctx == nullptr || !session_ctx->isValid()) { + LOGE("[OEMCrypto_RewrapDeviceRSAKey(): ERROR_INVALID_SESSION]"); + return OEMCrypto_ERROR_INVALID_SESSION; + } + if (message == nullptr || message_length == 0 || signature == nullptr || + signature_length == 0 || unaligned_nonce == nullptr || + enc_rsa_key == nullptr) { + LOGE("[OEMCrypto_RewrapDeviceRSAKey(): OEMCrypto_ERROR_INVALID_CONTEXT]"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + + // verify signature. + if (!session_ctx->ValidateMessage(message, message_length, signature, + signature_length)) { + LOGE("[RewrapDeviceRSAKey(): Could not verify signature]"); + return OEMCrypto_ERROR_SIGNATURE_FAILURE; + } + + // Range check performed by ODK library. + + // Validate nonce + if (!session_ctx->CheckNonce(nonce)) { + return OEMCrypto_ERROR_INVALID_NONCE; + } + + // Decrypt RSA key and verify it. + std::vector pkcs8_rsa_key(enc_rsa_key_length); + if (!session_ctx->DecryptRSAKey(enc_rsa_key, enc_rsa_key_length, + enc_rsa_key_iv, &pkcs8_rsa_key[0])) { + return OEMCrypto_ERROR_INVALID_RSA_KEY; + } + + size_t padding = pkcs8_rsa_key[enc_rsa_key_length - 1]; + if (padding > 16) { + // Do not return an error at this point, to avoid a padding oracle attack. + padding = 0; + } + size_t rsa_key_length = enc_rsa_key_length - padding; + if (!session_ctx->LoadRSAKey(&pkcs8_rsa_key[0], rsa_key_length)) { + return OEMCrypto_ERROR_INVALID_RSA_KEY; + } + + // Now we generate a wrapped keybox. + WrappedRSAKey* wrapped = reinterpret_cast(wrapped_rsa_key); + // Pick a random context and IV for generating keys. + if (!RAND_bytes(wrapped->context, sizeof(wrapped->context))) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (!RAND_bytes(wrapped->iv, sizeof(wrapped->iv))) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + const std::vector context( + wrapped->context, wrapped->context + sizeof(wrapped->context)); + // Generate mac and encryption keys for encrypting the signature. + if (!session_ctx->DeriveKeys(crypto_engine->DeviceRootKey(), context, + context)) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + + // Encrypt rsa key with keybox. + if (!session_ctx->EncryptRSAKey(&pkcs8_rsa_key[0], enc_rsa_key_length, + wrapped->iv, wrapped->enc_rsa_key)) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + // The wrapped keybox must be signed with the same key we verify with. I'll + // pick the server key, so I don't have to modify LoadRSAKey. + unsigned int sig_length = sizeof(wrapped->signature); + if (!HMAC(EVP_sha256(), &session_ctx->mac_key_server()[0], + session_ctx->mac_key_server().size(), wrapped->context, + buffer_size - sizeof(wrapped->signature), wrapped->signature, + &sig_length)) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + return OEMCrypto_SUCCESS; +} + +OEMCRYPTO_API 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) { + if (crypto_engine == nullptr) { + LOGE("OEMCrypto Not Initialized"); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (wrapped_private_key_length == nullptr || message == nullptr || + message_length == 0 || signature == nullptr || signature_length == 0) { + LOGE("OEMCrypto_ERROR_INVALID_CONTEXT"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if (!crypto_engine->ValidRootOfTrust()) { + LOGE("OEMCrypto_ERROR_KEYBOX_INVALID"); + return OEMCrypto_ERROR_KEYBOX_INVALID; + } + SessionContext* session_ctx = crypto_engine->FindSession(session); + if (session_ctx == nullptr || !session_ctx->isValid()) { + LOGE("OEMCrypto_ERROR_INVALID_SESSION"); + return OEMCrypto_ERROR_INVALID_SESSION; + } + std::vector device_id = crypto_engine->DeviceRootId(); + ODK_ParsedProvisioning parsed_response; + const uint32_t nonce = session_ctx->nonce(); + const OEMCryptoResult result = + ODK_ParseProvisioning(message, message_length, core_message_length, + &(session_ctx->nonce_values()), device_id.data(), + device_id.size(), &parsed_response); + if (result != OEMCrypto_SUCCESS) { + LOGE("ODK Error %d", result); + return result; + } + + // For the reference implementation, the wrapped key and the encrypted + // key are the same size -- just encrypted with different keys. + // We add 32 bytes for a context, 32 for iv, and 32 bytes for a signature. + // Important: This layout must match OEMCrypto_LoadDRMPrivateKey below. + const size_t buffer_size = + parsed_response.enc_private_key.length + sizeof(WrappedRSAKey); + + if (wrapped_private_key == nullptr || + *wrapped_private_key_length < buffer_size) { + *wrapped_private_key_length = buffer_size; + return OEMCrypto_ERROR_SHORT_BUFFER; + } + *wrapped_private_key_length = + buffer_size; // Tell caller how much space we used. + const uint8_t* message_body = message + core_message_length; + if (crypto_engine->config_provisioning_method() == OEMCrypto_Keybox) { + return OEMCrypto_RewrapDeviceRSAKey( + session, message, message_length, signature, signature_length, &nonce, + message_body + parsed_response.enc_private_key.offset, + parsed_response.enc_private_key.length, + message_body + parsed_response.enc_private_key_iv.offset, + wrapped_private_key, wrapped_private_key_length); + } else if (crypto_engine->config_provisioning_method() == + OEMCrypto_OEMCertificate) { + return OEMCrypto_RewrapDeviceRSAKey30( + session, &nonce, + message_body + parsed_response.encrypted_message_key.offset, + parsed_response.encrypted_message_key.length, + message_body + parsed_response.enc_private_key.offset, + parsed_response.enc_private_key.length, + message_body + parsed_response.enc_private_key_iv.offset, + wrapped_private_key, wrapped_private_key_length); + } else { + LOGE("Invalid provisioning method: %d.", + crypto_engine->config_provisioning_method()); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } +} + +OEMCRYPTO_API OEMCryptoResult OEMCrypto_LoadDRMPrivateKey( + OEMCrypto_SESSION session, OEMCrypto_PrivateKeyType key_type, + const uint8_t* wrapped_rsa_key, size_t wrapped_rsa_key_length) { + if (wrapped_rsa_key == nullptr) { + LOGE("OEMCrypto_ERROR_INVALID_CONTEXT nullptr"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if (crypto_engine == nullptr) { + LOGE("OEMCrypto Not Initialized."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (key_type != OEMCrypto_RSA_Private_Key) { + LOGE("ECC keys not yet supported in reference code."); + return OEMCrypto_ERROR_NOT_IMPLEMENTED; + } + if (crypto_engine->config_provisioning_method() == OEMCrypto_DrmCertificate) { + // If we are using a baked in cert, the "wrapped RSA key" should actually be + // the magic value for baked-in certificates. + if (wrapped_rsa_key_length != sizeof(kBakedInCertificateMagicBytes) || + memcmp(kBakedInCertificateMagicBytes, wrapped_rsa_key, + wrapped_rsa_key_length) != 0) { + LOGE("Baked in Cert has wrong size."); + return OEMCrypto_ERROR_INVALID_RSA_KEY; + } else { + return OEMCrypto_SUCCESS; + } + } + const WrappedRSAKey* wrapped = + reinterpret_cast(wrapped_rsa_key); + if (!crypto_engine->ValidRootOfTrust()) { + LOGE("ERROR_KEYBOX_INVALID"); + return OEMCrypto_ERROR_KEYBOX_INVALID; + } + + SessionContext* session_ctx = crypto_engine->FindSession(session); + if (session_ctx == nullptr || !session_ctx->isValid()) { + LOGE("ERROR_INVALID_SESSION"); + return OEMCrypto_ERROR_INVALID_SESSION; + } + const std::vector context( + wrapped->context, wrapped->context + sizeof(wrapped->context)); + // Generate mac and encryption keys for encrypting the signature. + if (!session_ctx->DeriveKeys(crypto_engine->DeviceRootKey(), context, + context)) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + // verify signature. + if (!session_ctx->ValidateMessage( + wrapped->context, wrapped_rsa_key_length - sizeof(wrapped->signature), + wrapped->signature, sizeof(wrapped->signature))) { + LOGE("Could not verify signature"); + return OEMCrypto_ERROR_SIGNATURE_FAILURE; + } + // Decrypt RSA key. + std::vector pkcs8_rsa_key(wrapped_rsa_key_length - + sizeof(wrapped->signature)); + size_t enc_rsa_key_length = wrapped_rsa_key_length - sizeof(WrappedRSAKey); + if (!session_ctx->DecryptRSAKey(wrapped->enc_rsa_key, enc_rsa_key_length, + wrapped->iv, &pkcs8_rsa_key[0])) { + return OEMCrypto_ERROR_INVALID_RSA_KEY; + } + size_t padding = pkcs8_rsa_key[enc_rsa_key_length - 1]; + if (padding > 16) { + // Do not return an error at this point, to avoid a padding oracle attack. + padding = 0; + } + size_t rsa_key_length = enc_rsa_key_length - padding; + if (!session_ctx->LoadRSAKey(&pkcs8_rsa_key[0], rsa_key_length)) { + return OEMCrypto_ERROR_INVALID_RSA_KEY; + } + return OEMCrypto_SUCCESS; +} + +OEMCRYPTO_API OEMCryptoResult OEMCrypto_LoadTestRSAKey() { + if (crypto_engine == nullptr) { + LOGE("OEMCrypto_LoadTestRSAKey: OEMCrypto Not Initialized."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (crypto_engine->LoadTestRsaKey()) return OEMCrypto_SUCCESS; + return OEMCrypto_ERROR_UNKNOWN_FAILURE; +} + +OEMCRYPTO_API 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) { + if (crypto_engine == nullptr) { + LOGE("OEMCrypto_GenerateRSASignature: OEMCrypto Not Initialized."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + + if (signature_length == 0) { + LOGE("[OEMCrypto_GenerateRSASignature(): OEMCrypto_ERROR_INVALID_CONTEXT]"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + + SessionContext* session_ctx = crypto_engine->FindSession(session); + if (session_ctx == nullptr || !session_ctx->isValid()) { + LOGE("[OEMCrypto_GenerateRSASignature(): ERROR_INVALID_SESSION]"); + return OEMCrypto_ERROR_INVALID_SESSION; + } + + size_t required_size = session_ctx->RSASignatureSize(); + if (*signature_length < required_size) { + *signature_length = required_size; + return OEMCrypto_ERROR_SHORT_BUFFER; + } + + if (message == nullptr || message_length == 0 || signature == nullptr || + signature_length == 0) { + LOGE("[OEMCrypto_GenerateRSASignature(): OEMCrypto_ERROR_INVALID_CONTEXT]"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + + OEMCryptoResult sts = session_ctx->GenerateRSASignature( + message, message_length, signature, signature_length, padding_scheme); + return sts; +} + +OEMCRYPTO_API 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) { + if (crypto_engine == nullptr) { + LOGE("OEMCrypto_DeriveKeysFromSessionKey: OEMCrypto Not Initialized."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (!crypto_engine->ValidRootOfTrust()) { + LOGE("[OEMCrypto_GenerateDerivedKeys(): ERROR_KEYBOX_INVALID]"); + return OEMCrypto_ERROR_KEYBOX_INVALID; + } + + SessionContext* session_ctx = crypto_engine->FindSession(session); + if (session_ctx == nullptr || !session_ctx->isValid()) { + LOGE("[OEMCrypto_GenerateDerivedKeys(): ERROR_INVALID_SESSION]"); + return OEMCrypto_ERROR_INVALID_SESSION; + } + + if (session_ctx->allowed_schemes() != kSign_RSASSA_PSS) { + LOGE("[OEMCrypto_GenerateDerivedKeys(): x509 key used to derive keys]"); + return OEMCrypto_ERROR_INVALID_RSA_KEY; + } + + const std::vector ssn_key_str( + enc_session_key, enc_session_key + enc_session_key_length); + const std::vector mac_ctx_str( + mac_key_context, mac_key_context + mac_key_context_length); + const std::vector enc_ctx_str( + enc_key_context, enc_key_context + enc_key_context_length); + + // Generate mac and encryption keys for current session context + if (!session_ctx->RSADeriveKeys(ssn_key_str, mac_ctx_str, enc_ctx_str)) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + return OEMCrypto_SUCCESS; +} + +OEMCRYPTO_API uint32_t OEMCrypto_APIVersion() { + return CryptoEngine::kApiVersion; +} + +OEMCRYPTO_API uint32_t OEMCrypto_MinorAPIVersion() { + return CryptoEngine::kMinorApiVersion; +} + +OEMCRYPTO_API uint8_t OEMCrypto_Security_Patch_Level() { + uint8_t security_patch_level = crypto_engine->config_security_patch_level(); + return security_patch_level; +} + +OEMCRYPTO_API const char* OEMCrypto_SecurityLevel() { + const char* security_level = crypto_engine->config_security_level(); + return security_level; +} + +OEMCRYPTO_API OEMCryptoResult OEMCrypto_GetHDCPCapability( + OEMCrypto_HDCP_Capability* current, OEMCrypto_HDCP_Capability* maximum) { + if (crypto_engine == nullptr) { + LOGE("OEMCrypto_GetHDCPCapability: OEMCrypto Not Initialized."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (current == nullptr) return OEMCrypto_ERROR_UNKNOWN_FAILURE; + if (maximum == nullptr) return OEMCrypto_ERROR_UNKNOWN_FAILURE; + *current = crypto_engine->config_current_hdcp_capability(); + *maximum = crypto_engine->config_maximum_hdcp_capability(); + return OEMCrypto_SUCCESS; +} + +OEMCRYPTO_API uint32_t OEMCrypto_GetAnalogOutputFlags() { + if (crypto_engine == nullptr) { + LOGE("OEMCrypto_GetAnalogOutputFlags: OEMCrypto Not Initialized."); + return 0; + } + return crypto_engine->analog_output_flags(); +} + +OEMCRYPTO_API const char* OEMCrypto_BuildInformation() { + return "OEMCrypto Ref Code __DATE__ __TIME__"; +} + +OEMCRYPTO_API uint32_t OEMCrypto_ResourceRatingTier() { + if (crypto_engine == nullptr) { + LOGE("OEMCrypto_ResourceRatingTier: OEMCrypto Not Initialized."); + return 0; + } + return crypto_engine->resource_rating(); +} + +OEMCRYPTO_API bool OEMCrypto_SupportsUsageTable() { + if (crypto_engine == nullptr) { + LOGE("OEMCrypto_SupportsUsageTable: OEMCrypto Not Initialized."); + return 0; + } + bool supports_usage = crypto_engine->config_supports_usage_table(); + return supports_usage; +} + +OEMCRYPTO_API size_t OEMCrypto_MaximumUsageTableHeaderSize() { + if (crypto_engine == nullptr) { + LOGE("OEMCrypto_MaximumUsageTableHeaderSize: OEMCrypto Not Initialized."); + return 0; + } + return crypto_engine->max_usage_table_size(); +} + +OEMCRYPTO_API OEMCryptoResult OEMCrypto_GetNumberOfOpenSessions(size_t* count) { + if (crypto_engine == nullptr) { + LOGE("OEMCrypto_GetNumberOfOpenSessions: OEMCrypto Not Initialized."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (count == nullptr) return OEMCrypto_ERROR_UNKNOWN_FAILURE; + *count = crypto_engine->GetNumberOfOpenSessions(); + return OEMCrypto_SUCCESS; +} + +OEMCRYPTO_API OEMCryptoResult OEMCrypto_GetMaxNumberOfSessions( + size_t* maximum) { + if (crypto_engine == nullptr) { + LOGE("OEMCrypto_GetMaxNumberOfSessions: OEMCrypto Not Initialized."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (maximum == nullptr) return OEMCrypto_ERROR_UNKNOWN_FAILURE; + *maximum = crypto_engine->GetMaxNumberOfSessions(); + return OEMCrypto_SUCCESS; +} + +OEMCRYPTO_API bool OEMCrypto_IsAntiRollbackHwPresent() { + bool anti_rollback_hw_present = + crypto_engine->config_is_anti_rollback_hw_present(); + + return anti_rollback_hw_present; +} + +OEMCRYPTO_API uint32_t OEMCrypto_SupportedCertificates() { + if (crypto_engine == nullptr) { + LOGE("OEMCrypto_GetProvisioningMethod: OEMCrypto Not Initialized."); + return 0; + } + if (crypto_engine->config_provisioning_method() == OEMCrypto_DrmCertificate) { + return 0; + } + return OEMCrypto_Supports_RSA_2048bit | OEMCrypto_Supports_RSA_3072bit | + OEMCrypto_Supports_RSA_CAST; +} + +OEMCRYPTO_API OEMCryptoResult OEMCrypto_Generic_Encrypt( + OEMCrypto_SESSION session, const uint8_t* in_buffer, size_t buffer_length, + const uint8_t* iv, OEMCrypto_Algorithm algorithm, uint8_t* out_buffer) { + if (crypto_engine == nullptr) { + LOGE("OEMCrypto_Generic_Encrypt: OEMCrypto Not Initialized."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (!crypto_engine->ValidRootOfTrust()) { + LOGE("[OEMCrypto_Generic_Encrypt(): ERROR_KEYBOX_INVALID]"); + return OEMCrypto_ERROR_KEYBOX_INVALID; + } + SessionContext* session_ctx = crypto_engine->FindSession(session); + if (session_ctx == nullptr || !session_ctx->isValid()) { + LOGE("[OEMCrypto_Generic_Encrypt(): ERROR_INVALID_SESSION]"); + return OEMCrypto_ERROR_INVALID_SESSION; + } + EntitledKeySession* entitled_key_session = nullptr; + if (crypto_engine->SessionTypeBits(session) == kSessionTypeEntitledKey) { + entitled_key_session = crypto_engine->FindEntitledKeySession(session); + if (entitled_key_session == nullptr) { + LOGE("OEMCrypto_Generic_Encrypt(): can not find the key session."); + return OEMCrypto_ERROR_INVALID_ENTITLED_KEY_SESSION; + } + } + if (in_buffer == nullptr || buffer_length == 0 || iv == nullptr || + out_buffer == nullptr) { + LOGE("[OEMCrypto_Generic_Encrypt(): OEMCrypto_ERROR_INVALID_CONTEXT]"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + OEMCryptoResult sts = + session_ctx->Generic_Encrypt(entitled_key_session, in_buffer, + buffer_length, iv, algorithm, out_buffer); + return sts; +} + +OEMCRYPTO_API OEMCryptoResult OEMCrypto_Generic_Decrypt( + OEMCrypto_SESSION session, const uint8_t* in_buffer, size_t buffer_length, + const uint8_t* iv, OEMCrypto_Algorithm algorithm, uint8_t* out_buffer) { + if (crypto_engine == nullptr) { + LOGE("OEMCrypto_Generic_Decrypt: OEMCrypto Not Initialized."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (!crypto_engine->ValidRootOfTrust()) { + LOGE("[OEMCrypto_Generic_Decrypt(): ERROR_KEYBOX_INVALID]"); + return OEMCrypto_ERROR_KEYBOX_INVALID; + } + SessionContext* session_ctx = crypto_engine->FindSession(session); + if (session_ctx == nullptr || !session_ctx->isValid()) { + LOGE("[OEMCrypto_Generic_Decrypt(): ERROR_INVALID_SESSION]"); + return OEMCrypto_ERROR_INVALID_SESSION; + } + EntitledKeySession* entitled_key_session = nullptr; + if (crypto_engine->SessionTypeBits(session) == kSessionTypeEntitledKey) { + entitled_key_session = crypto_engine->FindEntitledKeySession(session); + if (entitled_key_session == nullptr) { + LOGE("OEMCrypto_Generic_Encrypt(): can not find the key session."); + return OEMCrypto_ERROR_INVALID_ENTITLED_KEY_SESSION; + } + } + if (in_buffer == nullptr || buffer_length == 0 || iv == nullptr || + out_buffer == nullptr) { + LOGE("[OEMCrypto_Generic_Decrypt(): OEMCrypto_ERROR_INVALID_CONTEXT]"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + OEMCryptoResult sts = + session_ctx->Generic_Decrypt(entitled_key_session, in_buffer, + buffer_length, iv, algorithm, out_buffer); + return sts; +} + +OEMCRYPTO_API OEMCryptoResult OEMCrypto_Generic_Sign( + OEMCrypto_SESSION session, const uint8_t* in_buffer, size_t buffer_length, + OEMCrypto_Algorithm algorithm, uint8_t* signature, + size_t* signature_length) { + if (crypto_engine == nullptr) { + LOGE("OEMCrypto_Generic_Sign: OEMCrypto Not Initialized."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (!crypto_engine->ValidRootOfTrust()) { + LOGE("[OEMCrypto_Generic_Sign(): ERROR_KEYBOX_INVALID]"); + return OEMCrypto_ERROR_KEYBOX_INVALID; + } + SessionContext* session_ctx = crypto_engine->FindSession(session); + if (session_ctx == nullptr || !session_ctx->isValid()) { + LOGE("[OEMCrypto_Generic_Sign(): ERROR_INVALID_SESSION]"); + return OEMCrypto_ERROR_INVALID_SESSION; + } + EntitledKeySession* entitled_key_session = nullptr; + if (crypto_engine->SessionTypeBits(session) == kSessionTypeEntitledKey) { + entitled_key_session = crypto_engine->FindEntitledKeySession(session); + if (entitled_key_session == nullptr) { + LOGE("OEMCrypto_Generic_Encrypt(): can not find the key session."); + return OEMCrypto_ERROR_INVALID_ENTITLED_KEY_SESSION; + } + } + if (*signature_length < SHA256_DIGEST_LENGTH) { + *signature_length = SHA256_DIGEST_LENGTH; + return OEMCrypto_ERROR_SHORT_BUFFER; + } + if (in_buffer == nullptr || buffer_length == 0 || signature == nullptr) { + LOGE("[OEMCrypto_Generic_Sign(): OEMCrypto_ERROR_INVALID_CONTEXT]"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + OEMCryptoResult sts = + session_ctx->Generic_Sign(entitled_key_session, in_buffer, buffer_length, + algorithm, signature, signature_length); + return sts; +} + +OEMCRYPTO_API OEMCryptoResult OEMCrypto_Generic_Verify( + OEMCrypto_SESSION session, const uint8_t* in_buffer, size_t buffer_length, + OEMCrypto_Algorithm algorithm, const uint8_t* signature, + size_t signature_length) { + if (crypto_engine == nullptr) { + LOGE("OEMCrypto_Generic_Verify: OEMCrypto Not Initialized."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (!crypto_engine->ValidRootOfTrust()) { + LOGE("[OEMCrypto_Generic_Verify(): ERROR_KEYBOX_INVALID]"); + return OEMCrypto_ERROR_KEYBOX_INVALID; + } + SessionContext* session_ctx = crypto_engine->FindSession(session); + if (session_ctx == nullptr || !session_ctx->isValid()) { + LOGE("[OEMCrypto_Generic_Verify(): ERROR_INVALID_SESSION]"); + return OEMCrypto_ERROR_INVALID_SESSION; + } + EntitledKeySession* entitled_key_session = nullptr; + if (crypto_engine->SessionTypeBits(session) == kSessionTypeEntitledKey) { + entitled_key_session = crypto_engine->FindEntitledKeySession(session); + if (entitled_key_session == nullptr) { + LOGE("OEMCrypto_Generic_Encrypt(): can not find the key session."); + return OEMCrypto_ERROR_INVALID_ENTITLED_KEY_SESSION; + } + } + if (signature_length != SHA256_DIGEST_LENGTH) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (in_buffer == nullptr || buffer_length == 0 || signature == nullptr) { + LOGE("[OEMCrypto_Generic_Verify(): OEMCrypto_ERROR_INVALID_CONTEXT]"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + return session_ctx->Generic_Verify(entitled_key_session, in_buffer, + buffer_length, algorithm, signature, + signature_length); +} + +OEMCRYPTO_API OEMCryptoResult OEMCrypto_DeactivateUsageEntry( + OEMCrypto_SESSION session, const uint8_t* pst, size_t pst_length) { + if (crypto_engine == nullptr) { + LOGE("OEMCrypto_DeactivateUsageEntry: OEMCrypto Not Initialized."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (!crypto_engine->config_supports_usage_table()) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; + } + SessionContext* session_ctx = crypto_engine->FindSession(session); + if (session_ctx == nullptr || !session_ctx->isValid()) { + LOGE("[OEMCrypto_DeactivateUsageEntry(): ERROR_INVALID_SESSION]"); + return OEMCrypto_ERROR_INVALID_SESSION; + } + std::vector pstv(pst, pst + pst_length); + return session_ctx->DeactivateUsageEntry(pstv); +} + +OEMCRYPTO_API OEMCryptoResult OEMCrypto_ReportUsage(OEMCrypto_SESSION session, + const uint8_t* pst, + size_t pst_length, + uint8_t* buffer, + size_t* buffer_length) { + if (crypto_engine == nullptr) { + LOGE("OEMCrypto_ReportUsage: OEMCrypto Not Initialized."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (!crypto_engine->config_supports_usage_table()) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; + } + if (!buffer_length) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + SessionContext* session_ctx = crypto_engine->FindSession(session); + if (session_ctx == nullptr || !session_ctx->isValid()) { + LOGE("[OEMCrypto_ReportUsage(): ERROR_INVALID_SESSION]"); + return OEMCrypto_ERROR_INVALID_SESSION; + } + std::vector pstv(pst, pst + pst_length); + OEMCryptoResult sts = session_ctx->ReportUsage(pstv, buffer, buffer_length); + return sts; +} + +OEMCRYPTO_API bool OEMCrypto_IsSRMUpdateSupported() { + if (crypto_engine == nullptr) { + LOGE("OEMCrypto_IsSRMUpdateSupported: OEMCrypto Not Initialized."); + return false; + } + bool result = crypto_engine->srm_update_supported(); + return result; +} + +OEMCRYPTO_API OEMCryptoResult OEMCrypto_GetCurrentSRMVersion( + uint16_t* version) { + if (crypto_engine == nullptr) { + LOGE("OEMCrypto_GetCurrentSRMVersion: OEMCrypto Not Initialized."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (crypto_engine->config_local_display_only()) { + return OEMCrypto_LOCAL_DISPLAY_ONLY; + } + OEMCryptoResult result = crypto_engine->current_srm_version(version); + return result; +} + +OEMCRYPTO_API OEMCryptoResult OEMCrypto_LoadSRM(const uint8_t* buffer, + size_t buffer_length) { + if (crypto_engine == nullptr) { + LOGE("OEMCrypto_LoadSRM: OEMCrypto Not Initialized."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + return crypto_engine->load_srm(buffer, buffer_length); +} + +OEMCRYPTO_API OEMCryptoResult OEMCrypto_RemoveSRM() { + if (crypto_engine == nullptr) { + LOGE("OEMCrypto_RemoveSRM: OEMCrypto Not Initialized."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + return crypto_engine->remove_srm(); +} + +OEMCRYPTO_API OEMCryptoResult OEMCrypto_CreateUsageTableHeader( + uint8_t* header_buffer, size_t* header_buffer_length) { + if (crypto_engine == nullptr) { + LOGE("OEMCrypto_CreateUsageTableHeader: OEMCrypto Not Initialized."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (!crypto_engine->config_supports_usage_table()) { + LOGE("OEMCrypto_CreateUsageTableHeader: Configured without Usage Tables."); + return OEMCrypto_ERROR_NOT_IMPLEMENTED; + } + return crypto_engine->usage_table().CreateUsageTableHeader( + header_buffer, header_buffer_length); +} + +OEMCRYPTO_API OEMCryptoResult OEMCrypto_LoadUsageTableHeader( + const uint8_t* buffer, size_t buffer_length) { + if (crypto_engine == nullptr) { + LOGE("OEMCrypto_LoadUsageTableHeader: OEMCrypto Not Initialized."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (!crypto_engine->config_supports_usage_table()) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; + } + if (!buffer) { + LOGE("OEMCrypto_LoadUsageTableHeader: buffer null."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + std::vector bufferv(buffer, buffer + buffer_length); + return crypto_engine->usage_table().LoadUsageTableHeader(bufferv); +} + +OEMCRYPTO_API OEMCryptoResult OEMCrypto_CreateNewUsageEntry( + OEMCrypto_SESSION session, uint32_t* usage_entry_number) { + if (crypto_engine == nullptr) { + LOGE("OEMCrypto_CreateNewUsageEntry: OEMCrypto Not Initialized."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (!crypto_engine->config_supports_usage_table()) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; + } + SessionContext* session_ctx = crypto_engine->FindSession(session); + if (session_ctx == nullptr || !session_ctx->isValid()) { + LOGE("[OEMCrypto_CreateNewUsageEntry(): ERROR_INVALID_SESSION]"); + return OEMCrypto_ERROR_INVALID_SESSION; + } + if (!usage_entry_number) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + OEMCryptoResult sts = session_ctx->CreateNewUsageEntry(usage_entry_number); + return sts; +} + +OEMCRYPTO_API OEMCryptoResult OEMCrypto_LoadUsageEntry( + OEMCrypto_SESSION session, uint32_t index, const uint8_t* buffer, + size_t buffer_size) { + if (crypto_engine == nullptr) { + LOGE("OEMCrypto_LoadUsageEntry: OEMCrypto Not Initialized."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (!crypto_engine->config_supports_usage_table()) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; + } + SessionContext* session_ctx = crypto_engine->FindSession(session); + if (session_ctx == nullptr || !session_ctx->isValid()) { + LOGE("[OEMCrypto_LoadUsageEntry(): ERROR_INVALID_SESSION]"); + return OEMCrypto_ERROR_INVALID_SESSION; + } + if (!buffer) { + LOGE("[OEMCrypto_LoadUsageEntry(): buffer null]"); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + std::vector bufferv(buffer, buffer + buffer_size); + return session_ctx->LoadUsageEntry(index, bufferv); +} + +OEMCRYPTO_API OEMCryptoResult OEMCrypto_UpdateUsageEntry( + OEMCrypto_SESSION session, uint8_t* header_buffer, + size_t* header_buffer_length, uint8_t* entry_buffer, + size_t* entry_buffer_length) { + if (crypto_engine == nullptr) { + LOGE("OEMCrypto_UpdateUsageEntry: OEMCrypto Not Initialized."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (!crypto_engine->config_supports_usage_table()) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; + } + if (!header_buffer_length || !entry_buffer_length) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + SessionContext* session_ctx = crypto_engine->FindSession(session); + if (session_ctx == nullptr || !session_ctx->isValid()) { + LOGE("[OEMCrypto_UpdateUsageEntry(): ERROR_INVALID_SESSION]"); + return OEMCrypto_ERROR_INVALID_SESSION; + } + return session_ctx->UpdateUsageEntry(header_buffer, header_buffer_length, + entry_buffer, entry_buffer_length); +} + +OEMCRYPTO_API OEMCryptoResult OEMCrypto_ShrinkUsageTableHeader( + uint32_t new_table_size, uint8_t* header_buffer, + size_t* header_buffer_length) { + if (crypto_engine == nullptr) { + LOGE("OEMCrypto_ShrinkUsageTableHeader: OEMCrypto Not Initialized."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (!crypto_engine->config_supports_usage_table()) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; + } + return crypto_engine->usage_table().ShrinkUsageTableHeader( + new_table_size, header_buffer, header_buffer_length); +} + +OEMCRYPTO_API OEMCryptoResult OEMCrypto_MoveEntry(OEMCrypto_SESSION session, + uint32_t new_index) { + if (crypto_engine == nullptr) { + LOGE("OEMCrypto_MoveEntry: OEMCrypto Not Initialized."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (!crypto_engine->config_supports_usage_table()) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; + } + SessionContext* session_ctx = crypto_engine->FindSession(session); + if (session_ctx == nullptr || !session_ctx->isValid()) { + LOGE("[OEMCrypto_MoveEntry(): ERROR_INVALID_SESSION]"); + return OEMCrypto_ERROR_INVALID_SESSION; + } + return session_ctx->MoveEntry(new_index); +} + +OEMCRYPTO_API uint32_t OEMCrypto_SupportsDecryptHash() { + return OEMCrypto_CRC_Clear_Buffer; +} + +OEMCRYPTO_API OEMCryptoResult OEMCrypto_SetDecryptHash( + OEMCrypto_SESSION session, uint32_t frame_number, const uint8_t* hash, + size_t hash_length) { + // TODO(chelu): This API should support both oec session and key session. + if (crypto_engine == nullptr) { + LOGE("OEMCrypto_SetDecryptHash: OEMCrypto Not Initialized."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + SessionContext* session_ctx = crypto_engine->FindSession(session); + if (session_ctx == nullptr || !session_ctx->isValid()) { + LOGE("[OEMCrypto_SetDecryptHash(): ERROR_INVALID_SESSION]"); + return OEMCrypto_ERROR_INVALID_SESSION; + } + return session_ctx->SetDecryptHash(frame_number, hash, hash_length); +} + +OEMCRYPTO_API OEMCryptoResult OEMCrypto_GetHashErrorCode( + OEMCrypto_SESSION session, uint32_t* failed_frame_number) { + // TODO(chelu): This API should support both oec session and key session. + if (crypto_engine == nullptr) { + LOGE("OEMCrypto_GetHashErrorCode: OEMCrypto Not Initialized."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + SessionContext* session_ctx = crypto_engine->FindSession(session); + if (session_ctx == nullptr || !session_ctx->isValid()) { + LOGE("[OEMCrypto_GetHashErrorCode(): ERROR_INVALID_SESSION]"); + return OEMCrypto_ERROR_INVALID_SESSION; + } + return session_ctx->GetHashErrorCode(failed_frame_number); +} + +OEMCRYPTO_API OEMCryptoResult OEMCrypto_AllocateSecureBuffer( + OEMCrypto_SESSION session, size_t buffer_size, + OEMCrypto_DestBufferDesc* output_descriptor, int* secure_fd) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCRYPTO_API OEMCryptoResult OEMCrypto_FreeSecureBuffer( + OEMCrypto_SESSION session, OEMCrypto_DestBufferDesc* output_descriptor, + int secure_fd) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCRYPTO_API OEMCryptoResult OEMCrypto_CreateEntitledKeySession( + OEMCrypto_SESSION oec_session, OEMCrypto_SESSION* key_session) { + if (crypto_engine == nullptr) { + LOGE("OEMCrypto_CreateEntitledKeySession: OEMCrypto Not Initialized."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } +#ifndef NDEBUG + if (!crypto_engine->ValidRootOfTrust()) { + LOGE("[OEMCrypto_CreateEntitledKeySession(): ERROR_KEYBOX_INVALID]"); + return OEMCrypto_ERROR_KEYBOX_INVALID; + } +#endif + if (crypto_engine->SessionTypeBits(oec_session) != kSessionTypeOEMCrypto) { + LOGE("Unexpected session type."); + return OEMCrypto_ERROR_INVALID_SESSION; + } + SessionContext* session_ctx = crypto_engine->FindSession(oec_session); + if (!session_ctx || !session_ctx->isValid()) { + LOGE("[OEMCrypto_CreateEntitledKeySession(): ERROR_INVALID_SESSION]"); + return OEMCrypto_ERROR_INVALID_SESSION; + } + return crypto_engine->CreateEntitledKeySession(oec_session, key_session); +} + +OEMCRYPTO_API OEMCryptoResult +OEMCrypto_RemoveEntitledKeySession(OEMCrypto_SESSION key_session) { + if (crypto_engine == nullptr) { + LOGE("OEMCrypto_RemoveEntitledKeySession: OEMCrypto Not Initialized."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } +#ifndef NDEBUG + if (!crypto_engine->ValidRootOfTrust()) { + LOGE("[OEMCrypto_RemoveEntitledKeySession(): ERROR_KEYBOX_INVALID]"); + return OEMCrypto_ERROR_KEYBOX_INVALID; + } +#endif + if (crypto_engine->SessionTypeBits(key_session) != kSessionTypeEntitledKey) { + LOGE("Unexpected session type."); + return OEMCrypto_ERROR_INVALID_SESSION; + } + return crypto_engine->RemoveEntitledKeySession(key_session); +} + +} // namespace wvoec_ref diff --git a/oemcrypto/ref/src/oemcrypto_rsa_key_shared.cpp b/oemcrypto/ref/src/oemcrypto_rsa_key_shared.cpp new file mode 100644 index 0000000..6c73ad8 --- /dev/null +++ b/oemcrypto/ref/src/oemcrypto_rsa_key_shared.cpp @@ -0,0 +1,101 @@ +// 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. +// +// Reference implementation of OEMCrypto APIs +// +#include "oemcrypto_rsa_key_shared.h" + +#include + +#include +#include +#include +#include +#include + +#include "log.h" + +namespace wvoec_ref { + +void dump_boringssl_error() { + int count = 0; + while (unsigned long err = ERR_get_error()) { + count++; + char buffer[120]; + ERR_error_string_n(err, buffer, sizeof(buffer)); + LOGE("BoringSSL Error %d -- %lu -- %s", count, err, buffer); + } + LOGE("Reported %d BoringSSL Errors", count); +} + +void RSA_shared_ptr::reset() { + if (rsa_key_ && key_owned_) { + RSA_free(rsa_key_); + } + key_owned_ = false; + rsa_key_ = nullptr; +} + +bool RSA_shared_ptr::LoadPkcs8RsaKey(const uint8_t* buffer, size_t length) { + assert(buffer != nullptr); + reset(); + uint8_t* pkcs8_rsa_key = const_cast(buffer); + BIO* bio = BIO_new_mem_buf(pkcs8_rsa_key, length); + if (bio == nullptr) { + LOGE("[LoadPkcs8RsaKey(): Could not allocate bio buffer]"); + return false; + } + bool success = true; + PKCS8_PRIV_KEY_INFO* pkcs8_pki = d2i_PKCS8_PRIV_KEY_INFO_bio(bio, nullptr); + if (pkcs8_pki == nullptr) { + BIO_reset(bio); + pkcs8_pki = d2i_PKCS8_PRIV_KEY_INFO_bio(bio, nullptr); + if (pkcs8_pki == nullptr) { + LOGE("[LoadPkcs8RsaKey(): d2i_PKCS8_PRIV_KEY_INFO_bio returned nullptr]"); + dump_boringssl_error(); + success = false; + } + } + EVP_PKEY* evp = nullptr; + if (success) { + evp = EVP_PKCS82PKEY(pkcs8_pki); + if (evp == nullptr) { + LOGE("[LoadPkcs8RsaKey(): EVP_PKCS82PKEY returned nullptr]"); + dump_boringssl_error(); + success = false; + } + } + if (success) { + rsa_key_ = EVP_PKEY_get1_RSA(evp); + if (rsa_key_ == nullptr) { + LOGE("[LoadPkcs8RsaKey(): PrivateKeyInfo did not contain an RSA key]"); + success = false; + } + key_owned_ = true; + } + if (evp != nullptr) { + EVP_PKEY_free(evp); + } + if (pkcs8_pki != nullptr) { + PKCS8_PRIV_KEY_INFO_free(pkcs8_pki); + } + BIO_free(bio); + if (!success) { + return false; + } + switch (RSA_check_key(rsa_key_)) { + case 1: // valid. + return true; + case 0: // not valid. + LOGE("[LoadPkcs8RsaKey(): rsa key not valid]"); + dump_boringssl_error(); + return false; + default: // -1 == check failed. + LOGE("[LoadPkcs8RsaKey(): error checking rsa key]"); + dump_boringssl_error(); + return false; + } +} + +} // namespace wvoec_ref diff --git a/oemcrypto/ref/src/oemcrypto_rsa_key_shared.h b/oemcrypto/ref/src/oemcrypto_rsa_key_shared.h new file mode 100644 index 0000000..98ea154 --- /dev/null +++ b/oemcrypto/ref/src/oemcrypto_rsa_key_shared.h @@ -0,0 +1,42 @@ +// 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. +// +// Reference implementation of OEMCrypto APIs +// +#ifndef OEMCRYPTO_RSA_KEY_SHARED_H_ +#define OEMCRYPTO_RSA_KEY_SHARED_H_ + +#include + +#include + +namespace wvoec_ref { + +// Shared pointer with specialized destructor. This pointer is only shared +// from a CryptoEngine to a Session -- so we don't have to use full reference +// counting. +class RSA_shared_ptr { + public: + RSA_shared_ptr() : rsa_key_(nullptr), key_owned_(false) {} + ~RSA_shared_ptr() { reset(); }; + // Explicitly allow copy as share. + explicit RSA_shared_ptr(const RSA_shared_ptr& other) : + rsa_key_(other.rsa_key_), key_owned_(false) {} + RSA* get() { return rsa_key_; } + void reset(); + bool LoadPkcs8RsaKey(const uint8_t* buffer, size_t length); + + private: + void operator=(const RSA_shared_ptr); // disallow assign. + + RSA* rsa_key_; + bool key_owned_; +}; + +// Log errors from BoringSSL. +void dump_boringssl_error(); + +} // namespace wvoec_ref + +#endif // OEMCRYPTO_RSA_KEY_SHARED_H_ diff --git a/oemcrypto/ref/src/oemcrypto_session.cpp b/oemcrypto/ref/src/oemcrypto_session.cpp new file mode 100644 index 0000000..a46d56f --- /dev/null +++ b/oemcrypto/ref/src/oemcrypto_session.cpp @@ -0,0 +1,1889 @@ +// 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. +// +// Reference implementation of OEMCrypto APIs +// +#include "oemcrypto_session.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "advance_iv_ctr.h" +#include "keys.h" +#include "log.h" +#include "odk.h" +#include "oemcrypto_engine_ref.h" +#include "oemcrypto_key_ref.h" +#include "oemcrypto_rsa_key_shared.h" +#include "oemcrypto_types.h" +#include "platform.h" +#include "string_conversions.h" +#include "wvcrc32.h" + +static const int kPssSaltLength = 20; + +namespace { + +// Increment counter for AES-CTR. The CENC spec specifies we increment only +// the low 64 bits of the IV counter, and leave the high 64 bits alone. +void ctr128_inc64(uint8_t* counter) { + uint32_t n = 16; + do { + if (++counter[--n] != 0) return; + } while (n > 8); +} + +void advance_dest_buffer(OEMCrypto_DestBufferDesc* dest_buffer, size_t bytes) { + switch (dest_buffer->type) { + case OEMCrypto_BufferType_Clear: + dest_buffer->buffer.clear.address += bytes; + dest_buffer->buffer.clear.address_length -= bytes; + break; + + case OEMCrypto_BufferType_Secure: + dest_buffer->buffer.secure.offset += bytes; + break; + + case OEMCrypto_BufferType_Direct: + // Nothing to do for this buffer type. + break; + } +} + +} // namespace + +namespace wvoec_ref { + +SessionContext::SessionContext(CryptoEngine* ce, SessionId sid, + const RSA_shared_ptr& rsa_key) + : valid_(true), + ce_(ce), + id_(sid), + current_content_key_(nullptr), + session_keys_(nullptr), + license_request_hash_(), + rsa_key_(rsa_key), + allowed_schemes_(kSign_RSASSA_PSS), + decrypt_started_(false), + timer_limits_(), + clock_values_(), + usage_entry_(nullptr), + srm_requirements_status_(NoSRMVersion), + usage_entry_status_(kNoUsageEntry), + compute_hash_(false), + current_hash_(0), + bad_frame_number_(0), + hash_error_(OEMCrypto_SUCCESS), + state_nonce_created_(false), + state_request_signed_(false), + state_response_loaded_(false) { + ODK_InitializeSessionValues(&timer_limits_, &clock_values_, &nonce_values_, + CryptoEngine::kApiVersion, sid); +} + +SessionContext::~SessionContext() { +} + +// Internal utility function to derive key using CMAC-128 +bool SessionContext::DeriveKey(const std::vector& key, + const std::vector& context, int counter, + std::vector* out) { + if (key.empty() || counter > 4 || context.empty() || out == nullptr) { + LOGE("[DeriveKey(): OEMCrypto_ERROR_INVALID_CONTEXT]"); + return false; + } + + const EVP_CIPHER* cipher = EVP_aes_128_cbc(); + CMAC_CTX* cmac_ctx = CMAC_CTX_new(); + + if (!cmac_ctx) { + LOGE("[DeriveKey(): OEMCrypto_ERROR_CMAC_FAILURE]"); + return false; + } + + if (!CMAC_Init(cmac_ctx, &key[0], key.size(), cipher, 0)) { + LOGE("[DeriveKey(): OEMCrypto_ERROR_CMAC_FAILURE]"); + CMAC_CTX_free(cmac_ctx); + return false; + } + + std::vector message; + message.push_back(counter); + message.insert(message.end(), context.begin(), context.end()); + + if (!CMAC_Update(cmac_ctx, &message[0], message.size())) { + LOGE("[DeriveKey(): OEMCrypto_ERROR_CMAC_FAILURE]"); + CMAC_CTX_free(cmac_ctx); + return false; + } + + size_t reslen; + uint8_t res[128]; + if (!CMAC_Final(cmac_ctx, res, &reslen)) { + LOGE("[DeriveKey(): OEMCrypto_ERROR_CMAC_FAILURE]"); + CMAC_CTX_free(cmac_ctx); + return false; + } + + out->assign(res, res + reslen); + + CMAC_CTX_free(cmac_ctx); + + return true; +} + +bool SessionContext::DeriveKeys(const std::vector& master_key, + const std::vector& mac_key_context, + const std::vector& enc_key_context) { + // Generate derived key for mac key + std::vector mac_key_server; + std::vector mac_key_client; + std::vector mac_key_part2; + if (!DeriveKey(master_key, mac_key_context, 1, &mac_key_server)) { + return false; + } + if (!DeriveKey(master_key, mac_key_context, 2, &mac_key_part2)) { + return false; + } + mac_key_server.insert(mac_key_server.end(), mac_key_part2.begin(), + mac_key_part2.end()); + + if (!DeriveKey(master_key, mac_key_context, 3, &mac_key_client)) { + return false; + } + if (!DeriveKey(master_key, mac_key_context, 4, &mac_key_part2)) { + return false; + } + mac_key_client.insert(mac_key_client.end(), mac_key_part2.begin(), + mac_key_part2.end()); + + // Generate derived key for encryption key + std::vector enc_key; + if (!DeriveKey(master_key, enc_key_context, 1, &enc_key)) { + return false; + } + + set_mac_key_server(mac_key_server); + set_mac_key_client(mac_key_client); + set_encryption_key(enc_key); + return true; +} + +bool SessionContext::RSADeriveKeys( + const std::vector& enc_session_key, + const std::vector& mac_key_context, + const std::vector& enc_key_context) { + if (!rsa_key()) { + LOGE("[RSADeriveKeys(): no RSA key set]"); + return false; + } + if (enc_session_key.size() != static_cast(RSA_size(rsa_key()))) { + LOGE("[RSADeriveKeys(): encrypted session key wrong size:%zu, expected %d]", + enc_session_key.size(), RSA_size(rsa_key())); + dump_boringssl_error(); + return false; + } + session_key_.resize(RSA_size(rsa_key())); + int decrypted_size = + RSA_private_decrypt(enc_session_key.size(), &enc_session_key[0], + &session_key_[0], rsa_key(), RSA_PKCS1_OAEP_PADDING); + if (-1 == decrypted_size) { + LOGE("[RSADeriveKeys(): error decrypting session key.]"); + dump_boringssl_error(); + return false; + } + session_key_.resize(decrypted_size); + if (decrypted_size != static_cast(wvoec::KEY_SIZE)) { + LOGE("[RSADeriveKeys(): error. Session key is wrong size: %d.]", + decrypted_size); + dump_boringssl_error(); + session_key_.clear(); + return false; + } + return DeriveKeys(session_key_, mac_key_context, enc_key_context); +} + +OEMCryptoResult SessionContext::PrepAndSignLicenseRequest( + uint8_t* message, size_t message_length, size_t* core_message_length, + uint8_t* signature, size_t* signature_length) { + if (signature_length == nullptr || core_message_length == nullptr) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + const size_t required_signature_size = CertSignatureSize(); + OEMCryptoResult result = ODK_PrepareCoreLicenseRequest( + message, message_length, core_message_length, &nonce_values_); + if (*signature_length < required_signature_size || + result == OEMCrypto_ERROR_SHORT_BUFFER) { + *signature_length = required_signature_size; + return OEMCrypto_ERROR_SHORT_BUFFER; + } + if (result != OEMCrypto_SUCCESS) { + LOGE("ODK error: %d", result); + return result; + } + if (message == nullptr || message_length < *core_message_length || + signature == nullptr) { + LOGE("OEMCrypto_ERROR_INVALID_CONTEXT"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if (state_request_signed_) { + LOGE("Attempt to sign two license requests"); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + // For backwards compatibility, we only sign the message body, and we compute + // a SHA256 of the core message. + SHA256(message, *core_message_length, license_request_hash_); + const uint8_t* message_body = message + *core_message_length; + const size_t message_body_length = message_length - *core_message_length; + result = GenerateCertSignature(message_body, message_body_length, signature, + signature_length); + if (result == OEMCrypto_SUCCESS) state_request_signed_ = true; + ODK_InitializeClockValues(&clock_values_, ce_->SystemTime()); + return result; +} + +OEMCryptoResult SessionContext::PrepAndSignRenewalRequest( + uint8_t* message, size_t message_length, size_t* core_message_length, + uint8_t* signature, size_t* signature_length) { + if (signature_length == nullptr || core_message_length == nullptr) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + // If we have signed a request, but have not loaded it, something is wrong. + // On the other hand, we can sign a license release using the mac keys from + // the usage table. So it is OK if we have never signed a license request. + if (state_request_signed_ && !state_response_loaded_) { + LOGE("Attempt to sign renewal before load"); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + const size_t required_signature_size = SHA256_DIGEST_LENGTH; + const uint64_t now = ce_->SystemTime(); + const OEMCryptoResult result = ODK_PrepareCoreRenewalRequest( + message, message_length, core_message_length, &nonce_values_, + &clock_values_, now); + if (*signature_length < required_signature_size || + result == OEMCrypto_ERROR_SHORT_BUFFER) { + *signature_length = required_signature_size; + return OEMCrypto_ERROR_SHORT_BUFFER; + } + if (result != OEMCrypto_SUCCESS) { + LOGE("ODK error: %d", result); + return result; + } + if (message == nullptr || message_length < *core_message_length || + signature == nullptr) { + LOGE("OEMCrypto_ERROR_INVALID_CONTEXT"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + // If we are talking to an old license server, then we only sign the message + // body. + if (nonce_values_.api_major_version < 16) { + const uint8_t* message_body = message + *core_message_length; + const size_t message_body_length = message_length - *core_message_length; + return GenerateSignature(message_body, message_body_length, signature, + signature_length); + } else { + return GenerateSignature(message, message_length, signature, + signature_length); + } +} + +OEMCryptoResult SessionContext::PrepAndSignProvisioningRequest( + uint8_t* message, size_t message_length, size_t* core_message_length, + uint8_t* signature, size_t* signature_length) { + if (signature_length == nullptr || core_message_length == nullptr) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if (state_request_signed_) { + LOGE("Attempt to sign prov request after license request"); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + const size_t required_signature_size = ROTSignatureSize(); + if (required_signature_size == 0) return OEMCrypto_ERROR_UNKNOWN_FAILURE; + const std::vector& device_id = ce_->DeviceRootId(); + OEMCryptoResult result = ODK_PrepareCoreProvisioningRequest( + message, message_length, core_message_length, &nonce_values_, + device_id.data(), device_id.size()); + if (*signature_length < required_signature_size || + result == OEMCrypto_ERROR_SHORT_BUFFER) { + *signature_length = required_signature_size; + return OEMCrypto_ERROR_SHORT_BUFFER; + } + if (result != OEMCrypto_SUCCESS) { + LOGE("ODK error: %d", result); + return result; + } + if (message == nullptr || message_length == 0 || signature == nullptr) { + LOGE("OEMCrypto_ERROR_INVALID_CONTEXT"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if (ce_->config_provisioning_method() == OEMCrypto_Keybox) { + result = + GenerateSignature(message, message_length, signature, signature_length); + } else if (ce_->config_provisioning_method() == OEMCrypto_OEMCertificate) { + result = GenerateCertSignature(message, message_length, signature, + signature_length); + } else { + LOGE("Bad prov method = %d", ce_->config_provisioning_method()); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (result == OEMCrypto_SUCCESS) state_request_signed_ = true; + return result; +} + +// Utility function to generate a message signature +OEMCryptoResult SessionContext::GenerateSignature(const uint8_t* message, + size_t message_length, + uint8_t* signature, + size_t* signature_length) { + if (message == nullptr || message_length == 0 || signature == nullptr || + signature_length == nullptr) { + LOGE("OEMCrypto_ERROR_INVALID_CONTEXT"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if (mac_key_client_.size() != wvoec::MAC_KEY_SIZE) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if (*signature_length != SHA256_DIGEST_LENGTH) { + *signature_length = SHA256_DIGEST_LENGTH; + return OEMCrypto_ERROR_SHORT_BUFFER; + } + unsigned int md_len = *signature_length; + if (HMAC(EVP_sha256(), &mac_key_client_[0], wvoec::MAC_KEY_SIZE, message, + message_length, signature, &md_len)) { + *signature_length = md_len; + return OEMCrypto_SUCCESS; + } + return OEMCrypto_ERROR_UNKNOWN_FAILURE; +} + +// This is ussd when the device is a cast receiver. +size_t SessionContext::RSASignatureSize() { + if (!rsa_key()) { + LOGE("no RSA key set"); + return 0; + } + return static_cast(RSA_size(rsa_key())); +} + +size_t SessionContext::CertSignatureSize() { + // TODO(b/67735947): Add ECC cert support. + if (!rsa_key()) { + LOGE("No private key set"); + return 0; + } + return static_cast(RSA_size(rsa_key())); +} + +size_t SessionContext::ROTSignatureSize() { + if (ce_->config_provisioning_method() == OEMCrypto_Keybox) + return SHA256_DIGEST_LENGTH; + if (ce_->config_provisioning_method() == OEMCrypto_OEMCertificate) + return CertSignatureSize(); + LOGE("Bad prov method = %d", ce_->config_provisioning_method()); + return 0; +} + +OEMCryptoResult SessionContext::GenerateCertSignature( + const uint8_t* message, size_t message_length, uint8_t* signature, + size_t* signature_length) { + // TODO(b/67735947): Add ECC cert support. + if (message == nullptr || message_length == 0 || signature == nullptr || + signature_length == 0) { + LOGE("OEMCrypto_ERROR_INVALID_CONTEXT"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if (!rsa_key()) { + LOGE("no RSA key set"); + return OEMCrypto_ERROR_INVALID_RSA_KEY; + } + if (*signature_length < static_cast(RSA_size(rsa_key()))) { + *signature_length = CertSignatureSize(); + return OEMCrypto_ERROR_SHORT_BUFFER; + } + if (allowed_schemes_ != kSign_RSASSA_PSS) { + LOGE("message signing not allowed"); + return OEMCrypto_ERROR_INVALID_RSA_KEY; + } + + // Hash the message using SHA1. + uint8_t hash[SHA_DIGEST_LENGTH]; + if (!SHA1(message, message_length, hash)) { + LOGE("Error creating signature hash"); + dump_boringssl_error(); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + + // Add PSS padding. + std::vector padded_digest(*signature_length); + int status = RSA_padding_add_PKCS1_PSS_mgf1( + rsa_key(), &padded_digest[0], hash, EVP_sha1(), nullptr, kPssSaltLength); + if (status == -1) { + LOGE("Error padding hash"); + dump_boringssl_error(); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + + // Encrypt PSS padded digest. + status = RSA_private_encrypt(*signature_length, &padded_digest[0], signature, + rsa_key(), RSA_NO_PADDING); + if (status == -1) { + LOGE("Error in private encrypt"); + dump_boringssl_error(); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + return OEMCrypto_SUCCESS; +} + +OEMCryptoResult SessionContext::GenerateRSASignature( + const uint8_t* message, size_t message_length, uint8_t* signature, + size_t* signature_length, RSA_Padding_Scheme padding_scheme) { + if (message == nullptr || message_length == 0 || signature == nullptr || + signature_length == 0) { + LOGE("OEMCrypto_ERROR_INVALID_CONTEXT"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if (!rsa_key()) { + LOGE("no RSA key set"); + return OEMCrypto_ERROR_INVALID_RSA_KEY; + } + if (*signature_length < static_cast(RSA_size(rsa_key()))) { + *signature_length = RSA_size(rsa_key()); + return OEMCrypto_ERROR_SHORT_BUFFER; + } + if (((padding_scheme & allowed_schemes_) != padding_scheme) || + (padding_scheme != kSign_PKCS1_Block1)) { + LOGE("padding_scheme not allowed"); + return OEMCrypto_ERROR_INVALID_RSA_KEY; + } + // This is the maximum digest size possible for PKCS1 block type 1, + // as used for a CAST receiver. + const size_t max_digest_size = 83u; + if (message_length > max_digest_size) { + LOGE("RSA digest too large"); + return OEMCrypto_ERROR_SIGNATURE_FAILURE; + } + // Pad the message with PKCS1 padding, and then encrypt. + int status = RSA_private_encrypt(message_length, message, signature, + rsa_key(), RSA_PKCS1_PADDING); + if (status < 0) { + LOGE("error in RSA private encrypt. status=%d", status); + dump_boringssl_error(); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + *signature_length = static_cast(RSA_size(rsa_key())); + return OEMCrypto_SUCCESS; +} + +// Validate message signature +bool SessionContext::ValidateMessage(const uint8_t* given_message, + size_t message_length, + const uint8_t* given_signature, + size_t signature_length) { + if (signature_length != SHA256_DIGEST_LENGTH) { + return false; + } + uint8_t computed_signature[SHA256_DIGEST_LENGTH]; + memset(computed_signature, 0, SHA256_DIGEST_LENGTH); + unsigned int md_len = SHA256_DIGEST_LENGTH; + if (!HMAC(EVP_sha256(), mac_key_server_.data(), mac_key_server_.size(), + given_message, message_length, computed_signature, &md_len)) { + LOGE("ValidateMessage: Could not compute signature"); + return false; + } + if (CRYPTO_memcmp(given_signature, computed_signature, signature_length)) { + LOGE("Invalid signature given: %s", + wvutil::HexEncode(given_signature, signature_length).c_str()); + LOGE("Invalid signature computed: %s", + wvutil::HexEncode(computed_signature, signature_length).c_str()); + return false; + } + return true; +} + +OEMCryptoResult SessionContext::CheckStatusOnline(uint32_t nonce, + uint32_t control) { + if (!(control & wvoec::kControlNonceEnabled)) { + LOGE("LoadKeys: Server provided Nonce_Required but Nonce_Enabled = 0"); + // Server error. Continue, and assume nonce required. + } + if (!CheckNonce(nonce)) return OEMCrypto_ERROR_INVALID_NONCE; + switch (usage_entry_status_) { + case kNoUsageEntry: + LOGE("LoadKeys: Session did not create usage entry"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + case kUsageEntryLoaded: + LOGE("LoadKeys: Session reloaded existing entry"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + case kUsageEntryNew: + return OEMCrypto_SUCCESS; + default: // invalid status. + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } +} + +OEMCryptoResult SessionContext::CheckStatusOffline(uint32_t nonce, + uint32_t control) { + if (control & wvoec::kControlNonceEnabled) { + LOGE("KCB: Server provided NonceOrEntry but Nonce_Enabled = 1"); + // Server error. Continue, and assume nonce required. + } + switch (usage_entry_status_) { + case kNoUsageEntry: + LOGE("LoadKeys: Session did not create or load usage entry"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + case kUsageEntryLoaded: + // Repeat load. Calling function will verify pst and keys. + return OEMCrypto_SUCCESS; + case kUsageEntryNew: + // First load. Verify nonce. + if (!CheckNonce(nonce)) return OEMCrypto_ERROR_INVALID_NONCE; + return OEMCrypto_SUCCESS; + default: // invalid status. + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } +} + +OEMCryptoResult SessionContext::CheckNonceOrEntry( + const KeyControlBlock& key_control_block) { + switch (key_control_block.control_bits() & wvoec::kControlReplayMask) { + case wvoec::kControlNonceRequired: // Online license. Nonce always required. + return CheckStatusOnline(key_control_block.nonce(), + key_control_block.control_bits()); + break; + case wvoec::kControlNonceOrEntry: // Offline license. Nonce required on first use. + return CheckStatusOffline(key_control_block.nonce(), + key_control_block.control_bits()); + break; + default: + if ((key_control_block.control_bits() & wvoec::kControlNonceEnabled) && + (!CheckNonce(key_control_block.nonce()))) { + LOGE("LoadKeys: BAD Nonce"); + return OEMCrypto_ERROR_INVALID_NONCE; + } + } + return OEMCrypto_SUCCESS; +} + +OEMCryptoResult SessionContext::LoadLicense(const uint8_t* message, + size_t message_length, + size_t core_message_length, + const uint8_t* signature, + size_t signature_length) { + // Check state before we check signature. State is change in + // LoadKeysNoSignature. + if (state_response_loaded_) { + return OEMCrypto_ERROR_LICENSE_RELOAD; + } + ODK_ParsedLicense parsed_response; + const bool initial_license_load = (usage_entry_status_ != kUsageEntryLoaded); + const OEMCryptoResult result = ODK_ParseLicense( + message, message_length, core_message_length, initial_license_load, + usage_entry_present(), license_request_hash_, &timer_limits_, + &clock_values_, &nonce_values_, &parsed_response); + if (result != OEMCrypto_SUCCESS) { + LOGE("ODK Error %d", result); + return result; + } + // Validate message signature + if (!ValidateMessage(message, message_length, signature, signature_length)) { + return OEMCrypto_ERROR_SIGNATURE_FAILURE; + } + const uint8_t* message_body = message + core_message_length; + const size_t message_body_length = message_length - core_message_length; + return LoadKeysNoSignature( + message_body, message_body_length, parsed_response.enc_mac_keys_iv, + parsed_response.enc_mac_keys, parsed_response.key_array_length, + parsed_response.key_array, parsed_response.pst, + parsed_response.srm_restriction_data, + static_cast(parsed_response.license_type)); +} + +OEMCryptoResult SessionContext::LoadKeys( + 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) { + // Check state before we check signature. State is change in + // LoadKeysNoSignature. + if (state_response_loaded_) { + return OEMCrypto_ERROR_LICENSE_RELOAD; + } + // Validate message signature + if (!ValidateMessage(message, message_length, signature, signature_length)) { + return OEMCrypto_ERROR_SIGNATURE_FAILURE; + } + OEMCryptoResult result = LoadKeysNoSignature( + message, message_length, enc_mac_keys_iv, enc_mac_keys, num_keys, + key_array, pst, srm_restriction_data, license_type); + if (result != OEMCrypto_SUCCESS) return result; + Key* key = session_keys_->FirstKey(); + uint32_t duration = key ? key->control().duration() : 0; + result = ODK_InitializeV15Values(&timer_limits_, &clock_values_, + &nonce_values_, duration, ce_->SystemTime()); + // TODO(b/140765227): clear session on errors + return result; +} + +OEMCryptoResult SessionContext::LoadKeysNoSignature( + const uint8_t* message, size_t message_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) { + if (state_response_loaded_) { + return OEMCrypto_ERROR_LICENSE_RELOAD; + } + state_response_loaded_ = true; + if (num_keys < 1) return OEMCrypto_ERROR_INVALID_CONTEXT; + + if (session_keys_ == nullptr) { + switch (license_type) { + case OEMCrypto_ContentLicense: + case OEMCrypto_EntitlementLicense: + session_keys_.reset(new SessionKeyTable(license_type)); + break; + default: + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + } else { + if (session_keys_->type() != license_type) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + } + if (srm_restriction_data.length != 0) { + const std::string kSRMVerificationString = "HDCPDATA"; + if (memcmp(message + srm_restriction_data.offset, + kSRMVerificationString.c_str(), kSRMVerificationString.size())) { + LOGE("SRM Requirement Data has bad verification string: %8s", + message + srm_restriction_data.offset); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + uint32_t minimum_version = htonl(*reinterpret_cast( + message + srm_restriction_data.offset + 8)); + uint16_t current_version = 0; + if (OEMCrypto_SUCCESS != ce_->current_srm_version(¤t_version)) { + LOGW("[LoadKeys: SRM Version not available"); + srm_requirements_status_ = InvalidSRMVersion; + } else if (current_version < minimum_version) { + LOGW("[LoadKeys: SRM Version is too small %d, required: %d", + current_version, minimum_version); + srm_requirements_status_ = InvalidSRMVersion; + } else if (ce_->srm_blacklisted_device_attached()) { + LOGW("[LoadKeys: SRM blacklisted device attached]"); + srm_requirements_status_ = InvalidSRMVersion; + } else { + LOGI("[LoadKeys: SRM Versions is %d, required: %d]", current_version, + minimum_version); + srm_requirements_status_ = ValidSRMVersion; + } + } + + // Decrypt and install keys in key object + // Each key will have a key control block. They will all have the same nonce. + OEMCryptoResult status = OEMCrypto_SUCCESS; + std::vector key_id; + std::vector enc_key_data; + std::vector key_data_iv; + std::vector key_control; + std::vector key_control_iv; + for (unsigned int i = 0; i < num_keys; i++) { + key_id.assign( + message + key_array[i].key_id.offset, + message + key_array[i].key_id.offset + key_array[i].key_id.length); + enc_key_data.assign( + message + key_array[i].key_data.offset, + message + key_array[i].key_data.offset + key_array[i].key_data.length); + key_data_iv.assign( + message + key_array[i].key_data_iv.offset, + message + key_array[i].key_data_iv.offset + wvoec::KEY_IV_SIZE); + if (key_array[i].key_control.length == 0) { + status = OEMCrypto_ERROR_UNKNOWN_FAILURE; + break; + } + key_control.assign( + message + key_array[i].key_control.offset, + message + key_array[i].key_control.offset + wvoec::KEY_CONTROL_SIZE); + key_control_iv.assign( + message + key_array[i].key_control_iv.offset, + message + key_array[i].key_control_iv.offset + wvoec::KEY_IV_SIZE); + + OEMCryptoResult result = + InstallKey(key_id, enc_key_data, key_data_iv, key_control, + key_control_iv); + if (result != OEMCrypto_SUCCESS) { + status = result; + break; + } + } + if (status != OEMCrypto_SUCCESS) return status; + + // enc_mac_key can be nullptr if license renewal is not supported + if (enc_mac_keys.length != 0) { + // V2.1 license protocol: update mac keys after processing license response + const std::vector enc_mac_keys_str = std::vector( + message + enc_mac_keys.offset, + message + enc_mac_keys.offset + 2 * wvoec::MAC_KEY_SIZE); + const std::vector enc_mac_key_iv_str = std::vector( + message + enc_mac_keys_iv.offset, + message + enc_mac_keys_iv.offset + wvoec::KEY_IV_SIZE); + + if (!UpdateMacKeys(enc_mac_keys_str, enc_mac_key_iv_str)) { + LOGE("Failed to update mac keys.\n"); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + } + if (usage_entry_) { + OEMCryptoResult result = OEMCrypto_SUCCESS; + switch (usage_entry_status_) { + case kNoUsageEntry: + if (pst.length > 0) { + LOGE("LoadKeys: PST specified but no usage entry loaded"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + break; // no extra check. + case kUsageEntryNew: + result = usage_entry_->SetPST(message + pst.offset, pst.length); + if (result != OEMCrypto_SUCCESS) { + return result; + } + if (!usage_entry_->SetMacKeys(mac_key_server_, mac_key_client_)) { + LOGE("LoadKeys: Usage table can't set keys.\n"); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + break; + case kUsageEntryLoaded: + if (!usage_entry_->VerifyPST(message + pst.offset, pst.length)) { + return OEMCrypto_ERROR_WRONG_PST; + } + if (!usage_entry_->VerifyMacKeys(mac_key_server_, mac_key_client_)) { + LOGE("LoadKeys: Usage table entry mac keys do not match.\n"); + return OEMCrypto_ERROR_WRONG_KEYS; + } + if (usage_entry_->Inactive()) return OEMCrypto_ERROR_LICENSE_INACTIVE; + break; + } + } + encryption_key_.clear(); + return OEMCrypto_SUCCESS; +} + +OEMCryptoResult SessionContext::LoadEntitledContentKeys( + EntitledKeySession* key_session, const uint8_t* message, + size_t message_length, size_t key_array_length, + const OEMCrypto_EntitledContentKeyObject* key_array) { + if (!key_array) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (session_keys_ == nullptr || + session_keys_->type() != OEMCrypto_EntitlementLicense) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if (key_session == nullptr) { + return OEMCrypto_ERROR_INVALID_ENTITLED_KEY_SESSION; + } + + for (size_t i = 0; i < key_array_length; ++i) { + const OEMCrypto_EntitledContentKeyObject* key_data = &key_array[i]; + std::vector entitlement_key_id; + entitlement_key_id.assign(message + key_data->entitlement_key_id.offset, + message + key_data->entitlement_key_id.offset + + key_data->entitlement_key_id.length); + + Key* entitlement_key = session_keys_->Find(entitlement_key_id); + if (entitlement_key == nullptr) { + return OEMCrypto_KEY_NOT_ENTITLED; + } + std::vector content_key; + std::vector iv; + std::vector encrypted_content_key; + std::vector content_key_id; + + iv.assign(message + key_data->content_key_data_iv.offset, + message + key_data->content_key_data_iv.offset + 16); + encrypted_content_key.assign(message + key_data->content_key_data.offset, + message + key_data->content_key_data.offset + + key_data->content_key_data.length); + content_key_id.assign(message + key_data->content_key_id.offset, + message + key_data->content_key_id.offset + + key_data->content_key_id.length); + if (!DecryptMessage(entitlement_key->value(), iv, encrypted_content_key, + &content_key, 256 /* key size */)) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + + if (!key_session->AddOrUpdateContentKey( + entitlement_key, content_key_id, + std::unique_ptr(new EntitledKey(content_key)))) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + } + return OEMCrypto_SUCCESS; +} + +OEMCryptoResult SessionContext::LoadEntitledCasKeys( + EntitledKeySession* key_session, const uint8_t* message, + size_t message_length, size_t key_array_length, + const OEMCrypto_EntitledCasKeyObject* key_array) { + if (!key_array) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (session_keys_ == nullptr || + session_keys_->type() != OEMCrypto_EntitlementLicense) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if (key_session == nullptr) { + return OEMCrypto_ERROR_INVALID_ENTITLED_KEY_SESSION; + } + + for (size_t i = 0; i < key_array_length; ++i) { + const OEMCrypto_EntitledCasKeyObject* key_data = &key_array[i]; + std::vector entitlement_key_id; + entitlement_key_id.assign(message + key_data->entitlement_key_id.offset, + message + key_data->entitlement_key_id.offset + + key_data->entitlement_key_id.length); + + Key* entitlement_key = session_keys_->Find(entitlement_key_id); + if (entitlement_key == nullptr) { + return OEMCrypto_KEY_NOT_ENTITLED; + } + std::vector content_key; + std::vector iv; + std::vector encrypted_content_key; + std::vector content_key_id; + + iv.assign(message + key_data->content_key_data_iv.offset, + message + key_data->content_key_data_iv.offset + 16); + encrypted_content_key.assign(message + key_data->content_key_data.offset, + message + key_data->content_key_data.offset + + key_data->content_key_data.length); + content_key_id.assign(message + key_data->content_key_id.offset, + message + key_data->content_key_id.offset + + key_data->content_key_id.length); + if (!DecryptMessage(entitlement_key->value(), iv, encrypted_content_key, + &content_key, 256 /* key size */)) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + + if (!key_session->AddOrUpdateContentKey( + entitlement_key, content_key_id, + std::unique_ptr(new EntitledKey(content_key)))) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + } + return OEMCrypto_SUCCESS; +} + +OEMCryptoResult SessionContext::InstallKey( + const KeyId& key_id, const std::vector& key_data, + const std::vector& key_data_iv, + const std::vector& key_control, + const std::vector& key_control_iv) { + // Decrypt encrypted key_data using derived encryption key and offered iv + std::vector content_key; + std::vector key_control_str; + + if (!DecryptMessage(encryption_key_, key_data_iv, key_data, &content_key, + 128 /* key size */)) { + LOGE("[Installkey(): Could not decrypt key data]"); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + + // Key control must be supplied by license server + if (key_control.empty()) { + LOGE("[Installkey(): WARNING: No Key Control]"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if (key_control_iv.empty()) { + LOGE("[Installkey(): ERROR: No Key Control IV]"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if (!DecryptMessage(content_key, key_control_iv, key_control, + &key_control_str, 128 /* key size */)) { + LOGE("[Installkey(): ERROR: Could not decrypt content key]"); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + + KeyControlBlock key_control_block(key_control_str); + if (!key_control_block.valid()) { + LOGE("Error parsing key control"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if ((key_control_block.control_bits() & + wvoec::kControlRequireAntiRollbackHardware) && + !ce_->config_is_anti_rollback_hw_present()) { + LOGE("Anti-rollback hardware is required but hardware not present"); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + uint8_t minimum_patch_level = (key_control_block.control_bits() & + wvoec::kControlSecurityPatchLevelMask) >> + wvoec::kControlSecurityPatchLevelShift; + if (minimum_patch_level > OEMCrypto_Security_Patch_Level()) { + LOGE("[InstallKey(): security patch level: %d. Minimum:%d]", + OEMCrypto_Security_Patch_Level(), minimum_patch_level); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + OEMCryptoResult result = CheckNonceOrEntry(key_control_block); + if (result != OEMCrypto_SUCCESS) { + LOGE("LoadKeys: Failed Nonce/PST check"); + return result; + } + if (key_control_block.control_bits() & wvoec::kControlSRMVersionRequired) { + if (srm_requirements_status_ == NoSRMVersion) { + LOGE("[LoadKeys: control bit says SRM version required]"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if (srm_requirements_status_ == InvalidSRMVersion) { + // If the SRM version is too small, treat this key as local display only. + key_control_block.RequireLocalDisplay(); + } + } + + if (session_keys_ == nullptr) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + session_keys_->Insert( + key_id, std::unique_ptr(new Key(content_key, key_control_block))); + return OEMCrypto_SUCCESS; +} + +bool SessionContext::InstallRSAEncryptedKey( + const uint8_t* encrypted_message_key, size_t encrypted_message_key_length) { + encryption_key_.resize(RSA_size(rsa_key())); + int decrypted_size = RSA_private_decrypt( + encrypted_message_key_length, encrypted_message_key, &encryption_key_[0], + rsa_key(), RSA_PKCS1_OAEP_PADDING); + if (-1 == decrypted_size) { + LOGE("[RSADeriveKeys(): error decrypting session key.]"); + dump_boringssl_error(); + return false; + } + encryption_key_.resize(decrypted_size); + if (decrypted_size != static_cast(wvoec::KEY_SIZE)) { + LOGE("[RSADeriveKeys(): error. Session key is wrong size: %d.]", + decrypted_size); + dump_boringssl_error(); + encryption_key_.clear(); + return false; + } + return true; +} + +OEMCryptoResult SessionContext::LoadRenewal(const uint8_t* message, + size_t message_length, + size_t core_message_length, + const uint8_t* signature, + size_t signature_length) { + if (session_keys_ == nullptr) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if (!ValidateMessage(message, message_length, signature, signature_length)) { + LOGE("signature was invalid"); + return OEMCrypto_ERROR_SIGNATURE_FAILURE; + } + + // The reference implementation does not use a hardware timer. + uint64_t* timer_value = nullptr; + const OEMCryptoResult result = ODK_ParseRenewal( + message, message_length, core_message_length, &nonce_values_, + ce_->SystemTime(), &timer_limits_, &clock_values_, timer_value); + if (result == ODK_SET_TIMER || result == ODK_DISABLE_TIMER) + return OEMCrypto_SUCCESS; + if (result == ODK_TIMER_EXPIRED) return OEMCrypto_ERROR_KEY_EXPIRED; + // All other errors are returned to the caller. + return result; +} + +OEMCryptoResult SessionContext::RefreshKey( + const KeyId& key_id, const std::vector& key_control, + const std::vector& key_control_iv) { + if (session_keys_ == nullptr) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if (key_control.empty()) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + std::vector decrypted_key_control; + if (key_id.empty()) { + // Key control is not encrypted if key id is NULL + decrypted_key_control = key_control; + } else { + Key* content_key = session_keys_->Find(key_id); + if (nullptr == content_key) { + LOGE("Key ID not found."); + return OEMCrypto_ERROR_NO_CONTENT_KEY; + } + const std::vector content_key_value = content_key->value(); + // Decrypt encrypted key control block + if (key_control_iv.empty()) { + decrypted_key_control = key_control; + } else { + if (!DecryptMessage(content_key_value, key_control_iv, key_control, + &decrypted_key_control, 128 /* key size */)) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + } + } + KeyControlBlock key_control_block(decrypted_key_control); + if (!key_control_block.valid()) { + LOGE("Parse key control error."); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + uint32_t new_key_duration = key_control_block.duration(); + uint64_t* timer_value = nullptr; + const OEMCryptoResult result = + ODK_RefreshV15Values(&timer_limits_, &clock_values_, &nonce_values_, + ce_->SystemTime(), new_key_duration, timer_value); + if (result == ODK_SET_TIMER || result == ODK_DISABLE_TIMER) + return OEMCrypto_SUCCESS; + if (result == ODK_TIMER_EXPIRED) return OEMCrypto_ERROR_KEY_EXPIRED; + return result; +} + +bool SessionContext::DecryptRSAKey(const uint8_t* enc_rsa_key, + size_t enc_rsa_key_length, + const uint8_t* enc_rsa_key_iv, + uint8_t* pkcs8_rsa_key) { + // Decrypt rsa key with keybox. + uint8_t iv_buffer[wvoec::KEY_IV_SIZE]; + memcpy(iv_buffer, enc_rsa_key_iv, wvoec::KEY_IV_SIZE); + AES_KEY aes_key; + AES_set_decrypt_key(&encryption_key_[0], 128, &aes_key); + AES_cbc_encrypt(enc_rsa_key, pkcs8_rsa_key, enc_rsa_key_length, &aes_key, + iv_buffer, AES_DECRYPT); + return true; +} + +bool SessionContext::EncryptRSAKey(const uint8_t* pkcs8_rsa_key, + size_t enc_rsa_key_length, + const uint8_t* enc_rsa_key_iv, + uint8_t* enc_rsa_key) { + // Encrypt rsa key with keybox. + uint8_t iv_buffer[wvoec::KEY_IV_SIZE]; + memcpy(iv_buffer, enc_rsa_key_iv, wvoec::KEY_IV_SIZE); + AES_KEY aes_key; + AES_set_encrypt_key(&encryption_key_[0], 128, &aes_key); + AES_cbc_encrypt(pkcs8_rsa_key, enc_rsa_key, enc_rsa_key_length, &aes_key, + iv_buffer, AES_ENCRYPT); + return true; +} + +bool SessionContext::LoadRSAKey(const uint8_t* pkcs8_rsa_key, + size_t rsa_key_length) { + rsa_key_.reset(); + if (rsa_key_length < 8) { + LOGE("[LoadRSAKey(): Very Short Buffer]"); + return false; + } + if ((memcmp(pkcs8_rsa_key, "SIGN", 4) == 0)) { + uint32_t schemes_n; + memcpy((uint8_t*)&schemes_n, pkcs8_rsa_key + 4, sizeof(uint32_t)); + allowed_schemes_ = htonl(schemes_n); + pkcs8_rsa_key += 8; + rsa_key_length -= 8; + } else { + allowed_schemes_ = kSign_RSASSA_PSS; + } + return rsa_key_.LoadPkcs8RsaKey(pkcs8_rsa_key, rsa_key_length); +} + +OEMCryptoResult SessionContext::CheckKeyUse(const std::string& log_string, + uint32_t use_type, + OEMCryptoBufferType buffer_type) { + return CheckKeyControlBlockUse(current_content_key()->control(), log_string, + use_type, buffer_type); +} + +OEMCryptoResult SessionContext::CheckKeyControlBlockUse( + const KeyControlBlock& control, const std::string& log_string, + uint32_t use_type, OEMCryptoBufferType buffer_type) { + if (use_type && (!(control.control_bits() & use_type))) { + LOGE("[%s(): control bit says not allowed", log_string.c_str()); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (control.control_bits() & wvoec::kControlDataPathSecure) { + if (!ce_->config_closed_platform() && + buffer_type == OEMCrypto_BufferType_Clear) { + LOGE("[%s(): Secure key with insecure buffer]", log_string.c_str()); + return OEMCrypto_ERROR_DECRYPT_FAILED; + } + } + if (!decrypt_started_) { + // The reference implementation does not have a hardware timer. + uint64_t* timer_expiration = nullptr; + const OEMCryptoResult result = ODK_AttemptFirstPlayback( + ce_->SystemTime(), &timer_limits_, &clock_values_, timer_expiration); + if (result == ODK_TIMER_EXPIRED) return OEMCrypto_ERROR_KEY_EXPIRED; + if (usage_entry_ != nullptr) usage_entry_->ForbidReport(); + } else { + // Continued playback. + const OEMCryptoResult result = ODK_UpdateLastPlaybackTime( + ce_->SystemTime(), &timer_limits_, &clock_values_); + if (result == ODK_TIMER_EXPIRED) return OEMCrypto_ERROR_KEY_EXPIRED; + if (usage_entry_ != nullptr) usage_entry_->set_recent_decrypt(true); + } + if (!ce_->config_local_display_only()) { + // Only look at HDCP restrictions if the display can be non-local. + if (control.control_bits() & wvoec::kControlHDCPRequired) { + uint8_t required_hdcp = + (control.control_bits() & wvoec::kControlHDCPVersionMask) >> + wvoec::kControlHDCPVersionShift; + if (ce_->srm_blacklisted_device_attached()) { + required_hdcp = HDCP_NO_DIGITAL_OUTPUT; + } + // For reference implementation, we pretend we can handle the current + // HDCP version. + if (required_hdcp > ce_->config_current_hdcp_capability() || + ce_->config_current_hdcp_capability() == 0) { + return OEMCrypto_ERROR_INSUFFICIENT_HDCP; + } + } + } + // If the output buffer is clear, then we cannot control whether the output is + // an active analog display. In that case, return an error if analog displays + // should be disabled. + if ((control.control_bits() & wvoec::kControlDisableAnalogOutput) && + (ce_->analog_display_active() || + (buffer_type == OEMCrypto_BufferType_Clear))) { + LOGE("[%s(): control bit says disable analog", log_string.c_str()); + return OEMCrypto_ERROR_ANALOG_OUTPUT; + } + // Check if CGMS is required. + if (control.control_bits() & wvoec::kControlCGMSMask) { + // We can't control CGMS for a clear buffer. + if (buffer_type == OEMCrypto_BufferType_Clear) { + LOGE("[%s(): CGMS required, but buffer is clear", log_string.c_str()); + return OEMCrypto_ERROR_ANALOG_OUTPUT; + } + if ( ce_->analog_display_active() && !ce_->cgms_a_active()) { + LOGE("[%s(): control bit says CGMS required", log_string.c_str()); + return OEMCrypto_ERROR_ANALOG_OUTPUT; + } + } + decrypt_started_ = true; // First playback for session. + return OEMCrypto_SUCCESS; +} + +OEMCryptoResult SessionContext::Generic_Encrypt( + const EntitledKeySession* key_session, const uint8_t* in_buffer, + size_t buffer_length, const uint8_t* iv, OEMCrypto_Algorithm algorithm, + uint8_t* out_buffer) { + // Check there is a content key + if ((key_session != nullptr && key_session->CurrentContentKey() == nullptr) || + (key_session == nullptr && current_content_key() == nullptr)) { + LOGE("[Generic_Encrypt(): OEMCrypto_ERROR_NO_CONTENT_KEY]"); + return OEMCrypto_ERROR_NO_CONTENT_KEY; + } + const std::vector& key = + key_session == nullptr ? current_content_key()->value() + : key_session->CurrentContentKey()->value(); + + // Set the AES key. + if (static_cast(key.size()) != AES_BLOCK_SIZE) { + LOGE("[Generic_Encrypt(): CONTENT_KEY has wrong size: %d", key.size()); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + OEMCryptoResult result = CheckKeyUse("Generic_Encrypt", wvoec::kControlAllowEncrypt, + OEMCrypto_BufferType_Clear); + if (result != OEMCrypto_SUCCESS) return result; + if (algorithm != OEMCrypto_AES_CBC_128_NO_PADDING) { + LOGE("[Generic_Encrypt(): algorithm bad"); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (buffer_length % AES_BLOCK_SIZE != 0) { + LOGE("[Generic_Encrypt(): buffers size bad"); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + const uint8_t* key_u8 = &key[0]; + AES_KEY aes_key; + if (AES_set_encrypt_key(key_u8, AES_BLOCK_SIZE * 8, &aes_key) != 0) { + LOGE("[Generic_Encrypt(): FAILURE]"); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + uint8_t iv_buffer[wvoec::KEY_IV_SIZE]; + memcpy(iv_buffer, iv, wvoec::KEY_IV_SIZE); + AES_cbc_encrypt(in_buffer, out_buffer, buffer_length, &aes_key, iv_buffer, + AES_ENCRYPT); + return OEMCrypto_SUCCESS; +} + +OEMCryptoResult SessionContext::Generic_Decrypt( + const EntitledKeySession* key_session, const uint8_t* in_buffer, + size_t buffer_length, const uint8_t* iv, OEMCrypto_Algorithm algorithm, + uint8_t* out_buffer) { + // Check there is a content key + if ((key_session != nullptr && key_session->CurrentContentKey() == nullptr) || + (key_session == nullptr && current_content_key() == nullptr)) { + LOGE("[Generic_Decrypt(): OEMCrypto_ERROR_NO_CONTENT_KEY]"); + return OEMCrypto_ERROR_NO_CONTENT_KEY; + } + const std::vector& key = + key_session == nullptr ? current_content_key()->value() + : key_session->CurrentContentKey()->value(); + // Set the AES key. + if (static_cast(key.size()) != AES_BLOCK_SIZE) { + LOGE("[Generic_Decrypt(): CONTENT_KEY has wrong size"); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + OEMCryptoResult result = CheckKeyUse("Generic_Decrypt", wvoec::kControlAllowDecrypt, + OEMCrypto_BufferType_Clear); + if (result != OEMCrypto_SUCCESS) return result; + + if (algorithm != OEMCrypto_AES_CBC_128_NO_PADDING) { + LOGE("[Generic_Decrypt(): bad algorithm"); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (buffer_length % AES_BLOCK_SIZE != 0) { + LOGE("[Generic_Decrypt(): bad buffer size"); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + const uint8_t* key_u8 = &key[0]; + AES_KEY aes_key; + if (AES_set_decrypt_key(key_u8, AES_BLOCK_SIZE * 8, &aes_key) != 0) { + LOGE("[Generic_Decrypt(): FAILURE]"); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + uint8_t iv_buffer[wvoec::KEY_IV_SIZE]; + memcpy(iv_buffer, iv, wvoec::KEY_IV_SIZE); + AES_cbc_encrypt(in_buffer, out_buffer, buffer_length, &aes_key, iv_buffer, + AES_DECRYPT); + return OEMCrypto_SUCCESS; +} + +OEMCryptoResult SessionContext::Generic_Sign( + const EntitledKeySession* key_session, const uint8_t* in_buffer, + size_t buffer_length, OEMCrypto_Algorithm algorithm, uint8_t* signature, + size_t* signature_length) { + // Check there is a content key + if ((key_session != nullptr && key_session->CurrentContentKey() == nullptr) || + (key_session == nullptr && current_content_key() == nullptr)) { + LOGE("[Generic_Sign(): OEMCrypto_ERROR_NO_CONTENT_KEY]"); + return OEMCrypto_ERROR_NO_CONTENT_KEY; + } + if (*signature_length < SHA256_DIGEST_LENGTH) { + *signature_length = SHA256_DIGEST_LENGTH; + LOGE("[Generic_Sign(): bad signature length"); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + const std::vector& key = + key_session == nullptr ? current_content_key()->value() + : key_session->CurrentContentKey()->value(); + if (static_cast(key.size()) != SHA256_DIGEST_LENGTH) { + LOGE("[Generic_Sign(): CONTENT_KEY has wrong size; %d", key.size()); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + OEMCryptoResult result = CheckKeyUse("Generic_Sign", wvoec::kControlAllowSign, + OEMCrypto_BufferType_Clear); + if (result != OEMCrypto_SUCCESS) return result; + if (algorithm != OEMCrypto_HMAC_SHA256) { + LOGE("[Generic_Sign(): bad algorithm"); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + unsigned int md_len = *signature_length; + if (HMAC(EVP_sha256(), &key[0], key.size(), in_buffer, buffer_length, + signature, &md_len)) { + *signature_length = md_len; + return OEMCrypto_SUCCESS; + } + LOGE("[Generic_Sign(): hmac failed"); + dump_boringssl_error(); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; +} + +OEMCryptoResult SessionContext::Generic_Verify( + const EntitledKeySession* key_session, const uint8_t* in_buffer, + size_t buffer_length, OEMCrypto_Algorithm algorithm, + const uint8_t* signature, size_t signature_length) { + // Check there is a content key + if ((key_session != nullptr && key_session->CurrentContentKey() == nullptr) || + (key_session == nullptr && current_content_key() == nullptr)) { + LOGE("[Decrypt_Verify(): OEMCrypto_ERROR_NO_CONTENT_KEY]"); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (signature_length < SHA256_DIGEST_LENGTH) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + const std::vector& key = + key_session == nullptr ? current_content_key()->value() + : key_session->CurrentContentKey()->value(); + if (static_cast(key.size()) != SHA256_DIGEST_LENGTH) { + LOGE("[Generic_Verify(): CONTENT_KEY has wrong size: %d", key.size()); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + OEMCryptoResult result = CheckKeyUse("Generic_Verify", wvoec::kControlAllowVerify, + OEMCrypto_BufferType_Clear); + if (result != OEMCrypto_SUCCESS) return result; + if (algorithm != OEMCrypto_HMAC_SHA256) { + LOGE("[Generic_Verify(): bad algorithm"); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + unsigned int md_len = signature_length; + uint8_t computed_signature[SHA256_DIGEST_LENGTH]; + if (HMAC(EVP_sha256(), &key[0], key.size(), in_buffer, buffer_length, + computed_signature, &md_len)) { + if (0 == + CRYPTO_memcmp(signature, computed_signature, SHA256_DIGEST_LENGTH)) { + return OEMCrypto_SUCCESS; + } else { + return OEMCrypto_ERROR_SIGNATURE_FAILURE; + } + } + LOGE("[Generic_Verify(): HMAC failed"); + dump_boringssl_error(); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; +} + +bool SessionContext::UpdateMacKeys(const std::vector& enc_mac_keys, + const std::vector& iv) { + // Decrypt mac key from enc_mac_key using device_keya + std::vector mac_keys; + if (!DecryptMessage(encryption_key_, iv, enc_mac_keys, &mac_keys, + 128 /* key size */)) { + return false; + } + mac_key_server_ = std::vector( + mac_keys.begin(), mac_keys.begin() + wvoec::MAC_KEY_SIZE); + mac_key_client_ = std::vector(mac_keys.begin() + wvoec::MAC_KEY_SIZE, + mac_keys.end()); + return true; +} + +bool SessionContext::QueryKeyControlBlock(EntitledKeySession* key_session, + const KeyId& key_id, uint32_t* data) { + if (session_keys_ == nullptr) { + return false; + } + if ((session_keys_->type() == OEMCrypto_EntitlementLicense && + key_session == nullptr) || + (session_keys_->type() == OEMCrypto_ContentLicense && + key_session != nullptr)) { + return false; + } + + // For entitlement license, |key_id| here refers to the content key id. + const Key* control_key = key_session != nullptr + ? key_session->GetEntitlementKey(key_id) + : session_keys_->Find(key_id); + if (control_key == nullptr) { + LOGE("[QueryKeyControlBlock(): No key matches key id]"); + return false; + } + data[0] = 0; // verification optional. + data[1] = htonl(control_key->control().duration()); + data[2] = 0; // nonce optional. + data[3] = htonl(control_key->control().control_bits()); + return true; +} + +OEMCryptoResult SessionContext::SelectEntitledContentKey( + EntitledKeySession* key_session, const KeyId& key_id, + OEMCryptoCipherMode cipher_mode) { + if (key_session == nullptr) { + LOGE("Select Key: entitled key session is null."); + return OEMCrypto_ERROR_INVALID_ENTITLED_KEY_SESSION; + } + if (session_keys_ == nullptr || + session_keys_->type() != OEMCrypto_EntitlementLicense) { + LOGE("Select Key: no session keys or unexpected session keys type."); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + + // Update cipher_mode of the content key. + EntitledKey* content_key = key_session->GetContentKey(key_id); + if (content_key == nullptr) { + LOGE("No key matches key id"); + return OEMCrypto_ERROR_NO_CONTENT_KEY; + } + content_key->set_ctr_mode(cipher_mode == OEMCrypto_CipherMode_CTR); + + // Update current content key selection. + key_session->SetCurrentKey(key_id); + return OEMCrypto_SUCCESS; +} + +OEMCryptoResult SessionContext::SelectContentKey( + const KeyId& key_id, OEMCryptoCipherMode cipher_mode) { + if (session_keys_ == nullptr) { + LOGE("Select Key: no session keys"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if (session_keys_->type() != OEMCrypto_ContentLicense) { + LOGE("Select Key: Unexpected session keys type"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + + Key* content_key = session_keys_->Find(key_id); + if (content_key == nullptr) { + LOGE("No key matches key id"); + return OEMCrypto_ERROR_NO_CONTENT_KEY; + } + content_key->set_ctr_mode(cipher_mode == OEMCrypto_CipherMode_CTR); + current_content_key_ = content_key; + return OEMCrypto_SUCCESS; +} + +OEMCryptoResult SessionContext::CreateNewUsageEntry( + uint32_t* usage_entry_number) { + if (usage_entry_) { + // Can only load one entry per session. + return OEMCrypto_ERROR_MULTIPLE_USAGE_ENTRIES; + } + OEMCryptoResult result = ce_->usage_table().CreateNewUsageEntry( + this, &usage_entry_, usage_entry_number); + if (usage_entry_) { + usage_entry_status_ = kUsageEntryNew; + } + return result; +} + +OEMCryptoResult SessionContext::LoadUsageEntry( + uint32_t index, const std::vector& buffer) { + if (usage_entry_) { + // Can only load one entry per session. + return OEMCrypto_ERROR_MULTIPLE_USAGE_ENTRIES; + } + const OEMCryptoResult result = ce_->usage_table().LoadUsageEntry( + this, &usage_entry_, index, buffer, &clock_values_); + if ((result != OEMCrypto_SUCCESS) && + (result != OEMCrypto_WARNING_GENERATION_SKEW)) + return result; + if (!usage_entry_) return OEMCrypto_ERROR_UNKNOWN_FAILURE; + + usage_entry_status_ = kUsageEntryLoaded; + // Copy the mac keys to the current session. + mac_key_server_ = std::vector( + usage_entry_->mac_key_server(), + usage_entry_->mac_key_server() + wvoec::MAC_KEY_SIZE); + mac_key_client_ = std::vector( + usage_entry_->mac_key_client(), + usage_entry_->mac_key_client() + wvoec::MAC_KEY_SIZE); + return result; +} + +OEMCryptoResult SessionContext::UpdateUsageEntry(uint8_t* header_buffer, + size_t* header_buffer_length, + uint8_t* entry_buffer, + size_t* entry_buffer_length) { + if (!usage_entry_) { + LOGE("UpdateUsageEntry: Session has no entry"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + return ce_->usage_table().UpdateUsageEntry( + this, usage_entry_.get(), header_buffer, header_buffer_length, + entry_buffer, entry_buffer_length, &clock_values_); +} + +OEMCryptoResult SessionContext::DeactivateUsageEntry( + const std::vector& pst) { + if (!usage_entry_) return OEMCrypto_ERROR_INVALID_CONTEXT; + usage_entry_->ForbidReport(); + return ODK_DeactivateUsageEntry(&clock_values_); +} + +OEMCryptoResult SessionContext::ReportUsage(const std::vector& pst, + uint8_t* buffer, + size_t* buffer_length) { + if (!usage_entry_) return OEMCrypto_ERROR_INVALID_CONTEXT; + return usage_entry_->ReportUsage(pst, buffer, buffer_length); +} + +OEMCryptoResult SessionContext::MoveEntry(uint32_t new_index) { + if (!usage_entry_) return OEMCrypto_ERROR_INVALID_CONTEXT; + return ce_->usage_table().MoveEntry(usage_entry_.get(), new_index); +} + +// Internal utility function to decrypt the message +bool SessionContext::DecryptMessage(const std::vector& key, + const std::vector& iv, + const std::vector& message, + std::vector* decrypted, + uint32_t key_size) { + if (key.empty() || iv.empty() || message.empty() || !decrypted) { + LOGE("[DecryptMessage(): OEMCrypto_ERROR_INVALID_CONTEXT]"); + return false; + } + decrypted->resize(message.size()); + uint8_t iv_buffer[16]; + memcpy(iv_buffer, &iv[0], 16); + AES_KEY aes_key; + AES_set_decrypt_key(&key[0], key_size, &aes_key); + AES_cbc_encrypt(&message[0], &(decrypted->front()), message.size(), &aes_key, + iv_buffer, AES_DECRYPT); + return true; +} + +OEMCryptoResult SessionContext::DecryptSamples( + const EntitledKeySession* key_session, + const OEMCrypto_SampleDescription* samples, size_t samples_length, + const OEMCrypto_CENCEncryptPatternDesc* pattern) { + // Iterate through all the samples and decrypt each one + for (size_t sample_index = 0; sample_index < samples_length; ++sample_index) { + const OEMCrypto_SampleDescription& sample = samples[sample_index]; + + // Iterate through all the subsamples and decrypt each one. A production + // implementation may be able to do something more efficient, like + // decrypting all the encrypted portions in one pass. + const uint8_t* subsample_source = sample.buffers.input_data; + OEMCrypto_DestBufferDesc subsample_dest = sample.buffers.output_descriptor; + uint8_t subsample_iv[wvoec::KEY_IV_SIZE]; + static_assert(sizeof(sample.iv) == wvoec::KEY_IV_SIZE, + "The IV in OEMCrypto_SampleDescription is the wrong length."); + // Per its type, sizeof(subsample_iv) == wvoec::KEY_IV_SIZE + memcpy(subsample_iv, sample.iv, wvoec::KEY_IV_SIZE); + for (size_t subsample_index = 0; subsample_index < sample.subsamples_length; + ++subsample_index) { + const OEMCrypto_SubSampleDescription& subsample = + sample.subsamples[subsample_index]; + const size_t subsample_length = + subsample.num_bytes_clear + subsample.num_bytes_encrypted; + + OEMCryptoResult result = ce_->SetDestination( + subsample_dest, subsample_length, subsample.subsample_flags); + if (result != OEMCrypto_SUCCESS) { + LOGE("SetDestination status: %d", result); + return result; + } + + result = DecryptSubsample(key_session, subsample, subsample_source, + ce_->destination(), subsample_dest.type, + subsample_iv, pattern); + if (result != OEMCrypto_SUCCESS) { + LOGE("DecryptSubsample status: %d", result); + return result; + } + + result = ce_->PushDestination(subsample_dest, subsample.subsample_flags); + if (result != OEMCrypto_SUCCESS) { + LOGE("PushDestination status: %d", result); + return result; + } + + // Advance the source buffer, the dest buffer, and (if necessary) the IV + subsample_source += subsample_length; + advance_dest_buffer(&subsample_dest, subsample_length); + if (subsample.num_bytes_encrypted > 0) { + bool is_ctr_mode = key_session == nullptr + ? current_content_key()->ctr_mode() + : key_session->CurrentContentKey()->ctr_mode(); + if (is_ctr_mode) { + wvutil::AdvanceIvCtr( + &subsample_iv, + subsample.block_offset + subsample.num_bytes_encrypted); + } + } + } // Subsample loop + } // Sample loop + + return OEMCrypto_SUCCESS; +} + +OEMCryptoResult SessionContext::DecryptSubsample( + const EntitledKeySession* key_session, + const OEMCrypto_SubSampleDescription& subsample, const uint8_t* cipher_data, + uint8_t* clear_data, OEMCryptoBufferType buffer_type, + const uint8_t (&iv)[wvoec::KEY_IV_SIZE], + const OEMCrypto_CENCEncryptPatternDesc* pattern) { + // Handle the clear portion of the subsample. + if (subsample.num_bytes_clear > 0) { + if (buffer_type != OEMCrypto_BufferType_Direct) { + memmove(clear_data, cipher_data, subsample.num_bytes_clear); + } + // For the reference implementation, we quietly drop the clear direct video. + } + + // Handle the encrypted portion of the subsample. + OEMCryptoResult result = OEMCrypto_SUCCESS; + if (subsample.num_bytes_encrypted > 0) { + const uint8_t* source = cipher_data + subsample.num_bytes_clear; + uint8_t* dest = clear_data + subsample.num_bytes_clear; + result = + ChooseDecrypt(key_session, iv, subsample.block_offset, pattern, source, + subsample.num_bytes_encrypted, dest, buffer_type); + } + + // Compute hash for FDPT. + if (compute_hash_) { + bool has_key_control = false; + uint32_t key_control_bits = 0; + if (key_session != nullptr && + key_session->CurrentEntitlementKey() != nullptr) { + has_key_control = true; + key_control_bits = + key_session->CurrentEntitlementKey()->control().control_bits(); + } else if (key_session == nullptr && current_content_key() != nullptr) { + has_key_control = true; + key_control_bits = current_content_key()->control().control_bits(); + } + + if (!has_key_control || + (key_control_bits & wvoec::kControlAllowHashVerification) == 0) { + LOGE("[DecryptCENC(): OEMCrypto_ERROR_UNKNOWN_FAILURE]"); + hash_error_ = OEMCrypto_ERROR_UNKNOWN_FAILURE; + compute_hash_ = false; + current_hash_ = 0; + current_frame_number_ = 0; + } else { + if (OEMCrypto_FirstSubsample & subsample.subsample_flags) { + current_hash_ = wvcrc32Init(); + } + current_hash_ = wvcrc32Cont( + clear_data, subsample.num_bytes_clear + subsample.num_bytes_encrypted, + current_hash_); + if (OEMCrypto_LastSubsample & subsample.subsample_flags) { + if (current_hash_ != given_hash_) { + LOGE("CRC for frame %d is %08x, should be %08x\n", + current_frame_number_, current_hash_, given_hash_); + // Update bad_frame_number_ only if this is the first bad frame. + if (hash_error_ == OEMCrypto_SUCCESS) { + bad_frame_number_ = current_frame_number_; + hash_error_ = OEMCrypto_ERROR_BAD_HASH; + } + } + compute_hash_ = false; + } + } + } + + // Return the result of the previous ChooseDecrypt() call after computing the + // hash. + return result; +} + +OEMCryptoResult SessionContext::ChooseDecrypt( + const EntitledKeySession* key_session, const uint8_t* iv, + size_t block_offset, const OEMCrypto_CENCEncryptPatternDesc* pattern, + const uint8_t* cipher_data, size_t cipher_data_length, uint8_t* clear_data, + OEMCryptoBufferType buffer_type) { + // Check there is a content key + if ((key_session != nullptr && key_session->CurrentContentKey() == nullptr) || + (key_session == nullptr && current_content_key() == nullptr)) { + LOGE("[ChooseDecrypt(): OEMCrypto_ERROR_NO_CONTENT_KEY]"); + return OEMCrypto_ERROR_DECRYPT_FAILED; + } + + const KeyControlBlock& key_control_block = + key_session == nullptr ? current_content_key()->control() + : key_session->CurrentEntitlementKey()->control(); + OEMCryptoResult result = + CheckKeyControlBlockUse(key_control_block, "DecryptCENC", 0, buffer_type); + if (result != OEMCrypto_SUCCESS) return result; + + const std::vector& content_key = + key_session == nullptr ? current_content_key()->value() + : key_session->CurrentContentKey()->value(); + + // Set the AES key. + if (static_cast(content_key.size()) != AES_BLOCK_SIZE) { + LOGE("[DecryptCTR(): CONTENT_KEY has wrong size: %d", content_key.size()); + return OEMCrypto_ERROR_DECRYPT_FAILED; + } + const uint8_t* key_u8 = &content_key[0]; + + if (buffer_type == OEMCrypto_BufferType_Direct) { + // For reference implementation, we quietly drop the decrypted direct video. + return OEMCrypto_SUCCESS; + } + bool is_ctr_mode = key_session == nullptr + ? current_content_key()->ctr_mode() + : key_session->CurrentContentKey()->ctr_mode(); + if (!is_ctr_mode) { + if (block_offset > 0 || pattern->encrypt == 0) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + return PatternDecryptCBC(key_u8, iv, pattern, cipher_data, + cipher_data_length, clear_data); + } else { + if (pattern->skip != 0 || pattern->encrypt != 0) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + return DecryptCTR(key_u8, iv, block_offset, cipher_data, cipher_data_length, + clear_data); + } +} + +OEMCryptoResult SessionContext::PatternDecryptCBC( + const uint8_t* key, const uint8_t* initial_iv, + const OEMCrypto_CENCEncryptPatternDesc* pattern, const uint8_t* cipher_data, + size_t cipher_data_length, uint8_t* clear_data) { + AES_KEY aes_key; + AES_set_decrypt_key(&key[0], AES_BLOCK_SIZE * 8, &aes_key); + uint8_t iv[AES_BLOCK_SIZE]; + uint8_t next_iv[AES_BLOCK_SIZE]; + memcpy(iv, &initial_iv[0], AES_BLOCK_SIZE); + + const size_t pattern_length = pattern->encrypt + pattern->skip; + if (pattern_length <= 0) return OEMCrypto_ERROR_INVALID_CONTEXT; + + size_t l = 0; + size_t pattern_offset = 0; + while (l < cipher_data_length) { + const size_t size = + std::min(cipher_data_length - l, static_cast(AES_BLOCK_SIZE)); + const bool skip_block = (pattern_offset >= pattern->encrypt); + pattern_offset = (pattern_offset + 1) % pattern_length; + if (skip_block || (size < AES_BLOCK_SIZE)) { + // If we are decrypting in-place, then this byte is already correct and + // can be skipped. + if (clear_data != cipher_data) { + memcpy(&clear_data[l], &cipher_data[l], size); + } + } else { + uint8_t aes_output[AES_BLOCK_SIZE]; + // Save the iv for the next block, in case cipher_data is in the same + // buffer as clear_data. + memcpy(next_iv, &cipher_data[l], AES_BLOCK_SIZE); + AES_decrypt(&cipher_data[l], aes_output, &aes_key); + for (size_t n = 0; n < AES_BLOCK_SIZE; n++) { + clear_data[l + n] = aes_output[n] ^ iv[n]; + } + memcpy(iv, next_iv, AES_BLOCK_SIZE); + } + l += size; + } + return OEMCrypto_SUCCESS; +} + +OEMCryptoResult SessionContext::DecryptCTR(const uint8_t* key_u8, + const uint8_t* iv, + size_t block_offset, + const uint8_t* cipher_data, + size_t cipher_data_length, + uint8_t* clear_data) { + // Local copy (will be modified). + // Allocated as 64-bit ints to enforce 64-bit alignment for later access as a + // 64-bit value. + uint64_t aes_iv[2]; + assert(sizeof(aes_iv) == AES_BLOCK_SIZE); + // The double-cast is needed to comply with strict aliasing rules. + uint8_t* aes_iv_u8 = + reinterpret_cast(reinterpret_cast(aes_iv)); + memcpy(aes_iv_u8, &iv[0], AES_BLOCK_SIZE); + + // The CENC spec specifies we increment only the low 64 bits of the IV + // counter, and leave the high 64 bits alone. This is different from the + // OpenSSL implementation, which increments the entire 128 bit iv. That is + // why we implement the CTR loop ourselves. + size_t l = 0; + if (block_offset > 0 && l < cipher_data_length) { + // Encrypt the IV. + uint8_t ecount_buf[AES_BLOCK_SIZE]; + + AES_KEY aes_key; + if (AES_set_encrypt_key(key_u8, AES_BLOCK_SIZE * 8, &aes_key) != 0) { + LOGE("[DecryptCTR(): FAILURE]"); + return OEMCrypto_ERROR_DECRYPT_FAILED; + } + AES_encrypt(aes_iv_u8, ecount_buf, &aes_key); + for (int n = block_offset; n < AES_BLOCK_SIZE && l < cipher_data_length; + ++n, ++l) { + clear_data[l] = cipher_data[l] ^ ecount_buf[n]; + } + ctr128_inc64(aes_iv_u8); + block_offset = 0; + } + + uint64_t remaining = cipher_data_length - l; + int out_len = 0; + + while (remaining) { + EVP_CIPHER_CTX* evp_cipher_ctx = EVP_CIPHER_CTX_new(); + EVP_CIPHER_CTX_set_padding(evp_cipher_ctx, 0); + if (!EVP_DecryptInit_ex(evp_cipher_ctx, EVP_aes_128_ctr(), nullptr, key_u8, + aes_iv_u8)) { + LOGE("[DecryptCTR(): EVP_INIT ERROR]"); + EVP_CIPHER_CTX_free(evp_cipher_ctx); + return OEMCrypto_ERROR_DECRYPT_FAILED; + } + + // Test the MSB of the counter portion of the initialization vector. If the + // value is 0xFF the counter is near wrapping. In this case we calculate + // the number of bytes we can safely decrypt before the counter wraps. + uint64_t decrypt_length = 0; + if (aes_iv_u8[8] == 0xFF) { + uint64_t bottom_64_bits = wvutil::ntohll64(aes_iv[1]); + uint64_t bytes_before_iv_wrap = (~bottom_64_bits + 1) * AES_BLOCK_SIZE; + decrypt_length = + bytes_before_iv_wrap < remaining ? bytes_before_iv_wrap : remaining; + } else { + decrypt_length = remaining; + } + + if (!EVP_DecryptUpdate(evp_cipher_ctx, &clear_data[l], &out_len, + &cipher_data[l], decrypt_length)) { + LOGE("[DecryptCTR(): EVP_UPDATE_ERROR]"); + EVP_CIPHER_CTX_free(evp_cipher_ctx); + return OEMCrypto_ERROR_DECRYPT_FAILED; + } + l += decrypt_length; + remaining = cipher_data_length - l; + + int final; + if (!EVP_DecryptFinal_ex(evp_cipher_ctx, + &clear_data[cipher_data_length - remaining], + &final)) { + LOGE("[DecryptCTR(): EVP_FINAL_ERROR]"); + EVP_CIPHER_CTX_free(evp_cipher_ctx); + return OEMCrypto_ERROR_DECRYPT_FAILED; + } + EVP_CIPHER_CTX_free(evp_cipher_ctx); + + // If remaining is not zero, reset the iv before the second pass. + if (remaining) { + memcpy(aes_iv_u8, &iv[0], AES_BLOCK_SIZE); + memset(&aes_iv_u8[8], 0, AES_BLOCK_SIZE / 2); + } + } + return OEMCrypto_SUCCESS; +} + +OEMCryptoResult SessionContext::SetDecryptHash(uint32_t frame_number, + const uint8_t* hash, + size_t hash_length) { + if (hash_length < sizeof(uint32_t)) { + LOGE("[SetDecryptHash(): short buffer]"); + return OEMCrypto_ERROR_SHORT_BUFFER; + } + if (hash_length > sizeof(uint32_t)) { + LOGE("[SetDecryptHash(): long buffer]"); + return OEMCrypto_ERROR_BUFFER_TOO_LARGE; + } + compute_hash_ = true; + current_frame_number_ = frame_number; + given_hash_ = *reinterpret_cast(hash); + return OEMCrypto_SUCCESS; +} + +OEMCryptoResult SessionContext::GetHashErrorCode( + uint32_t* failed_frame_number) { + if (failed_frame_number == nullptr) return OEMCrypto_ERROR_UNKNOWN_FAILURE; + if (hash_error_ != OEMCrypto_SUCCESS) + *failed_frame_number = bad_frame_number_; + return hash_error_; +} + +bool SessionContext::set_nonce(uint32_t nonce) { + if (state_nonce_created_) return false; + if (nonce == 0) return false; + state_nonce_created_ = true; + ODK_SetNonceValues(&nonce_values_, nonce); + return true; +} + +} // namespace wvoec_ref diff --git a/oemcrypto/ref/src/oemcrypto_session.h b/oemcrypto/ref/src/oemcrypto_session.h new file mode 100644 index 0000000..35ab5ea --- /dev/null +++ b/oemcrypto/ref/src/oemcrypto_session.h @@ -0,0 +1,300 @@ +// 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. +// +// Reference implementation of OEMCrypto APIs +// +#ifndef REF_OEMCRYPTO_SESSION_H_ +#define REF_OEMCRYPTO_SESSION_H_ + +#include +#include +#include +#include + +#include + +#include "OEMCryptoCAS.h" +#include "odk_structs.h" +#include "oemcrypto_auth_ref.h" +#include "oemcrypto_entitled_key_session.h" +#include "oemcrypto_key_ref.h" +#include "oemcrypto_rsa_key_shared.h" +#include "oemcrypto_session_key_table.h" +#include "oemcrypto_types.h" +#include "oemcrypto_usage_table_ref.h" + +namespace wvoec_ref { + +class CryptoEngine; +typedef uint32_t SessionId; + +enum SRMVersionStatus { NoSRMVersion, ValidSRMVersion, InvalidSRMVersion }; + +class SessionContext { + + public: + SessionContext(CryptoEngine* ce, SessionId sid, + const RSA_shared_ptr& rsa_key); + SessionContext() = delete; + virtual ~SessionContext(); + + bool isValid() { return valid_; } + + virtual bool DeriveKeys(const std::vector& master_key, + const std::vector& mac_context, + const std::vector& enc_context); + virtual bool RSADeriveKeys(const std::vector& enc_session_key, + const std::vector& mac_context, + const std::vector& enc_context); + virtual OEMCryptoResult PrepAndSignLicenseRequest(uint8_t* message, + size_t message_length, + size_t* core_message_length, + uint8_t* signature, + size_t* signature_length); + virtual OEMCryptoResult PrepAndSignRenewalRequest(uint8_t* message, + size_t message_length, + size_t* core_message_length, + uint8_t* signature, + size_t* signature_length); + virtual OEMCryptoResult PrepAndSignProvisioningRequest( + uint8_t* message, size_t message_length, size_t* core_message_length, + uint8_t* signature, size_t* signature_length); + // The size of an RSA signature. This is used when signing as a CAST + // receiver. + size_t RSASignatureSize(); + virtual OEMCryptoResult GenerateRSASignature( + const uint8_t* message, size_t message_length, uint8_t* signature, + size_t* signature_length, RSA_Padding_Scheme padding_scheme); + virtual bool ValidateMessage(const uint8_t* message, size_t message_length, + const uint8_t* signature, + size_t signature_length); + // |key_session| may be null if the session is content license. + OEMCryptoResult DecryptSamples( + const EntitledKeySession* key_session, + const OEMCrypto_SampleDescription* samples, size_t samples_length, + const OEMCrypto_CENCEncryptPatternDesc* pattern); + + OEMCryptoResult Generic_Encrypt(const EntitledKeySession* key_session, + const uint8_t* in_buffer, + size_t buffer_length, const uint8_t* iv, + OEMCrypto_Algorithm algorithm, + uint8_t* out_buffer); + OEMCryptoResult Generic_Decrypt(const EntitledKeySession* key_session, + const uint8_t* in_buffer, + size_t buffer_length, const uint8_t* iv, + OEMCrypto_Algorithm algorithm, + uint8_t* out_buffer); + OEMCryptoResult Generic_Sign(const EntitledKeySession* key_session, + const uint8_t* in_buffer, size_t buffer_length, + OEMCrypto_Algorithm algorithm, + uint8_t* signature, size_t* signature_length); + OEMCryptoResult Generic_Verify(const EntitledKeySession* key_session, + const uint8_t* in_buffer, size_t buffer_length, + OEMCrypto_Algorithm algorithm, + const uint8_t* signature, + size_t signature_length); + virtual OEMCryptoResult LoadLicense(const uint8_t* message, + size_t message_length, + size_t core_message_length, + const uint8_t* signature, + size_t signature_length); + virtual OEMCryptoResult LoadKeys( + 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); + virtual OEMCryptoResult LoadKeysNoSignature( + const uint8_t* message, size_t message_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); + virtual OEMCryptoResult LoadEntitledContentKeys( + EntitledKeySession* key_session, const uint8_t* message, + size_t message_length, size_t key_array_length, + const OEMCrypto_EntitledContentKeyObject* key_array); + virtual OEMCryptoResult LoadEntitledCasKeys( + EntitledKeySession* key_session, const uint8_t* message, + size_t message_length, size_t key_array_length, + const OEMCrypto_EntitledCasKeyObject* key_array); + virtual OEMCryptoResult InstallKey( + const KeyId& key_id, const std::vector& key_data, + const std::vector& key_data_iv, + const std::vector& key_control, + const std::vector& key_control_iv); + bool InstallRSAEncryptedKey(const uint8_t* encrypted_message_key, + size_t encrypted_message_key_length); + bool DecryptRSAKey(const uint8_t* enc_rsa_key, size_t enc_rsa_key_length, + const uint8_t* wrapped_rsa_key_iv, uint8_t* pkcs8_rsa_key); + bool EncryptRSAKey(const uint8_t* pkcs8_rsa_key, size_t enc_rsa_key_length, + const uint8_t* enc_rsa_key_iv, uint8_t* enc_rsa_key); + bool LoadRSAKey(const uint8_t* pkcs8_rsa_key, size_t rsa_key_length); + virtual OEMCryptoResult LoadRenewal(const uint8_t* message, + size_t message_length, + size_t core_message_length, + const uint8_t* signature, + size_t signature_length); + virtual OEMCryptoResult RefreshKey( + const KeyId& key_id, const std::vector& key_control, + const std::vector& key_control_iv); + virtual bool UpdateMacKeys(const std::vector& mac_keys, + const std::vector& iv); + virtual bool QueryKeyControlBlock(EntitledKeySession* key_session, + const KeyId& key_id, uint32_t* data); + virtual OEMCryptoResult SelectEntitledContentKey( + EntitledKeySession* key_session, const KeyId& key_id, + OEMCryptoCipherMode cipher_mode); + virtual OEMCryptoResult SelectContentKey(const KeyId& key_id, + OEMCryptoCipherMode cipher_mode); + virtual OEMCryptoResult SetDecryptHash(uint32_t frame_number, + const uint8_t* hash, + size_t hash_length); + virtual OEMCryptoResult GetHashErrorCode(uint32_t* failed_frame_number); + const Key* current_content_key(void) { return current_content_key_; } + void set_mac_key_server(const std::vector& mac_key_server) { + mac_key_server_ = mac_key_server; + } + const std::vector& mac_key_server() { return mac_key_server_; } + void set_mac_key_client(const std::vector& mac_key_client) { + mac_key_client_ = mac_key_client; + } + const std::vector& mac_key_client() { return mac_key_client_; } + + void set_encryption_key(const std::vector& enc_key) { + encryption_key_ = enc_key; + } + const std::vector& encryption_key() { return encryption_key_; } + uint32_t allowed_schemes() const { return allowed_schemes_; } + + // Return true if nonce was set. + bool set_nonce(uint32_t nonce); + uint32_t nonce() const { return nonce_values_.nonce; } + ODK_NonceValues& nonce_values() { return nonce_values_; } + + bool CheckNonce(uint32_t nonce) const { + return nonce != 0 && nonce == nonce_values_.nonce; + }; + + virtual OEMCryptoResult CreateNewUsageEntry(uint32_t* usage_entry_number); + virtual OEMCryptoResult LoadUsageEntry(uint32_t index, + const std::vector& buffer); + virtual OEMCryptoResult UpdateUsageEntry(uint8_t* header_buffer, + size_t* header_buffer_length, + uint8_t* entry_buffer, + size_t* entry_buffer_length); + virtual OEMCryptoResult DeactivateUsageEntry(const std::vector& pst); + virtual OEMCryptoResult ReportUsage(const std::vector& pst, + uint8_t* buffer, size_t* buffer_length); + OEMCryptoResult MoveEntry(uint32_t new_index); + bool usage_entry_present() const { return usage_entry_ != nullptr; } + + protected: + // Signature size of the currently loaded private key. + size_t CertSignatureSize(); + // Signature size when using a keybox or OEM Cert's private key. + size_t ROTSignatureSize(); + virtual OEMCryptoResult GenerateCertSignature(const uint8_t* message, + size_t message_length, + uint8_t* signature, + size_t* signature_length); + virtual OEMCryptoResult GenerateSignature(const uint8_t* message, + size_t message_length, + uint8_t* signature, + size_t* signature_length); + bool DeriveKey(const std::vector& key, + const std::vector& context, int counter, + std::vector* out); + bool DecryptMessage(const std::vector& key, + const std::vector& iv, + const std::vector& message, + std::vector* decrypted, + uint32_t key_size); // AES key size, in bits. + // Either verify the nonce or usage entry, as required by the key control + // block. + OEMCryptoResult CheckNonceOrEntry(const KeyControlBlock& key_control_block); + // If there is a usage entry, check that it is not inactive. + // It also updates the status of the entry if needed. + bool CheckUsageEntry(); + // Check that the usage entry status is valid for online use. + OEMCryptoResult CheckStatusOnline(uint32_t nonce, uint32_t control); + // Check that the usage entry status is valid for offline use. + OEMCryptoResult CheckStatusOffline(uint32_t nonce, uint32_t control); + + OEMCryptoResult DecryptSubsample( + const EntitledKeySession* key_session, + const OEMCrypto_SubSampleDescription& subsample, + const uint8_t* cipher_data, uint8_t* clear_data, + OEMCryptoBufferType buffer_type, const uint8_t (&iv)[wvoec::KEY_IV_SIZE], + const OEMCrypto_CENCEncryptPatternDesc* pattern); + OEMCryptoResult ChooseDecrypt(const EntitledKeySession* key_session, + const uint8_t* iv, size_t block_offset, + const OEMCrypto_CENCEncryptPatternDesc* pattern, + const uint8_t* cipher_data, + size_t cipher_data_length, uint8_t* clear_data, + OEMCryptoBufferType buffer_type); + OEMCryptoResult PatternDecryptCBC( + const uint8_t* key, const uint8_t* iv, + const OEMCrypto_CENCEncryptPatternDesc* pattern, + const uint8_t* cipher_data, size_t cipher_data_length, + uint8_t* clear_data); + OEMCryptoResult DecryptCTR(const uint8_t* key_u8, const uint8_t* iv, + size_t block_offset, const uint8_t* cipher_data, + size_t cipher_data_length, uint8_t* clear_data); + // Checks if the key is allowed for the specified type. If there is a usage + // entry, it also checks the usage entry. + OEMCryptoResult CheckKeyUse(const std::string& log_string, uint32_t use_type, + OEMCryptoBufferType buffer_type); + OEMCryptoResult CheckKeyControlBlockUse(const KeyControlBlock& control, + const std::string& log_string, + uint32_t use_type, + OEMCryptoBufferType buffer_type); + RSA* rsa_key() { return rsa_key_.get(); } + + bool valid_; + CryptoEngine* ce_; + SessionId id_; + std::vector mac_key_server_; + std::vector mac_key_client_; + std::vector encryption_key_; + std::vector session_key_; + const Key* current_content_key_; + std::unique_ptr session_keys_; + ODK_NonceValues nonce_values_; + uint8_t license_request_hash_[ODK_SHA256_HASH_SIZE]; + RSA_shared_ptr rsa_key_; + uint32_t allowed_schemes_; // for RSA signatures. + bool decrypt_started_; // If the license has been used in this session. + ODK_TimerLimits timer_limits_; + ODK_ClockValues clock_values_; + std::unique_ptr usage_entry_; + SRMVersionStatus srm_requirements_status_; + enum UsageEntryStatus { + kNoUsageEntry, // No entry loaded for this session. + kUsageEntryNew, // After entry was created. + kUsageEntryLoaded, // After loading entry or loading keys. + }; + UsageEntryStatus usage_entry_status_; + + // These are used when doing full decrypt path testing. + bool compute_hash_; // True if the current frame needs a hash. + uint32_t current_hash_; // Running CRC hash of frame. + uint32_t given_hash_; // True CRC hash of frame. + uint32_t current_frame_number_; // Current frame for CRC hash. + uint32_t bad_frame_number_; // Frame number with bad hash. + OEMCryptoResult hash_error_; // Error code for first bad frame. + + // The bare minimum state machine is to only call each of these function + // categories at most once. + bool state_nonce_created_; + bool state_request_signed_; + bool state_response_loaded_; + + CORE_DISALLOW_COPY_AND_ASSIGN(SessionContext); +}; + +} // namespace wvoec_ref + +#endif // REF_OEMCRYPTO_SESSION_H_ diff --git a/oemcrypto/ref/src/oemcrypto_session_key_table.cpp b/oemcrypto/ref/src/oemcrypto_session_key_table.cpp new file mode 100644 index 0000000..ae29130 --- /dev/null +++ b/oemcrypto/ref/src/oemcrypto_session_key_table.cpp @@ -0,0 +1,44 @@ +// 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. +// +// Reference implementation of OEMCrypto APIs +// +#include "oemcrypto_session_key_table.h" + +namespace wvoec_ref { + +bool SessionKeyTable::Insert(const KeyId& key_id, + std::unique_ptr key_data) { + if (keys_.find(key_id) != keys_.end()) { + return false; + } + keys_[key_id] = std::move(key_data); + return true; +} + +Key* SessionKeyTable::Find(const KeyId& key_id) { + if (keys_.find(key_id) == keys_.end()) { + return nullptr; + } + return keys_[key_id].get(); +} + +Key* SessionKeyTable::FirstKey() { + return keys_.empty() ? nullptr : keys_.begin()->second.get(); +} + +void SessionKeyTable::Remove(const KeyId& key_id) { + if (keys_.find(key_id) == keys_.end()) { + return; + } + keys_.erase(key_id); +} + +void SessionKeyTable::UpdateDuration(const KeyControlBlock& control) { + for (auto& key : keys_) { + key.second->UpdateDuration(control); + } +} + +} // namespace wvoec_ref diff --git a/oemcrypto/ref/src/oemcrypto_session_key_table.h b/oemcrypto/ref/src/oemcrypto_session_key_table.h new file mode 100644 index 0000000..e808160 --- /dev/null +++ b/oemcrypto/ref/src/oemcrypto_session_key_table.h @@ -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. +// +// Reference implementation of OEMCrypto APIs +// +#ifndef REF_OEMCRYPTO_SESSION_KEY_TABLE_H_ +#define REF_OEMCRYPTO_SESSION_KEY_TABLE_H_ + +#include + +#include +#include +#include + +#include "OEMCryptoCAS.h" +#include "oemcrypto_key_ref.h" + +namespace wvoec_ref { + +typedef std::vector KeyId; + +// SessionKeyTable holds the keys for the current session +class SessionKeyTable { + public: + explicit SessionKeyTable(OEMCrypto_LicenseType type) : type_(type){}; + SessionKeyTable(const SessionKeyTable&) = delete; + SessionKeyTable(SessionKeyTable&&) = delete; + ~SessionKeyTable() = default; + + bool Insert(const KeyId& key_id, std::unique_ptr key_data); + Key* Find(const KeyId& key_id); + Key* FirstKey(); + void Remove(const KeyId& key_id); + void UpdateDuration(const KeyControlBlock& control); + + OEMCrypto_LicenseType type() { return type_; } + size_t size() const { return keys_.size(); } + + private: + std::map> keys_; + const OEMCrypto_LicenseType type_; +}; + +} // namespace wvoec_ref + +#endif // REF_OEMCRYPTO_SESSION_KEY_TABLE_H_ diff --git a/oemcrypto/ref/src/oemcrypto_usage_table_ref.cpp b/oemcrypto/ref/src/oemcrypto_usage_table_ref.cpp new file mode 100644 index 0000000..133b663 --- /dev/null +++ b/oemcrypto/ref/src/oemcrypto_usage_table_ref.cpp @@ -0,0 +1,703 @@ +// 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. +// +// Reference implementation of OEMCrypto APIs +// +#include "oemcrypto_usage_table_ref.h" + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include "file_store.h" +#include "log.h" +#include "odk.h" +#include "oemcrypto_engine_ref.h" +// TODO(fredgc): Setting the device files base bath is currently broken as +// wvutil::Properties is no longer used by the reference code. +//#include "properties.h" +#include "pst_report.h" +#include "string_conversions.h" + +namespace wvoec_ref { +namespace { +const size_t kMagicLength = 8; +const char* kEntryVerification = "USEENTRY"; +const char* kHeaderVerification = "USEHEADR"; +// Offset into a signed block where we start encrypting. We need to +// skip the signature and the iv. +const size_t kEncryptionOffset = SHA256_DIGEST_LENGTH + SHA256_DIGEST_LENGTH; + +// A structure that holds an usage entry and its signature. +struct SignedEntryBlock { + uint8_t signature[SHA256_DIGEST_LENGTH]; + uint8_t iv[SHA256_DIGEST_LENGTH]; + uint8_t verification[kMagicLength]; + StoredUsageEntry data; +}; + +// This has the data in the header of constant size. There is also an array +// of generation numbers. +struct SignedHeaderBlock { + uint8_t signature[SHA256_DIGEST_LENGTH]; + uint8_t iv[SHA256_DIGEST_LENGTH]; + uint8_t verification[kMagicLength]; + int64_t master_generation; + uint64_t count; +}; + +} // namespace + +UsageTableEntry::UsageTableEntry(UsageTable* table, uint32_t index, + int64_t generation) + : usage_table_(table), recent_decrypt_(false), forbid_report_(true) { + memset(&data_, 0, sizeof(data_)); + data_.generation_number = generation; + data_.index = index; +} + +UsageTableEntry::~UsageTableEntry() { usage_table_->ReleaseEntry(data_.index); } + +OEMCryptoResult UsageTableEntry::SetPST(const uint8_t* pst, size_t pst_length) { + if (pst_length > kMaxPSTLength) return OEMCrypto_ERROR_BUFFER_TOO_LARGE; + data_.pst_length = pst_length; + if (!pst || !pst_length) return OEMCrypto_ERROR_INVALID_CONTEXT; + memcpy(data_.pst, pst, pst_length); + data_.time_of_license_received = usage_table_->ce_->SystemTime(); + return OEMCrypto_SUCCESS; +} + +bool UsageTableEntry::VerifyPST(const uint8_t* pst, size_t pst_length) { + if (pst_length > kMaxPSTLength) return false; + if (data_.pst_length != pst_length) return false; + if (!pst || !pst_length) return false; + return 0 == CRYPTO_memcmp(pst, data_.pst, pst_length); +} + +bool UsageTableEntry::VerifyMacKeys(const std::vector& server, + const std::vector& client) { + return (server.size() == wvoec::MAC_KEY_SIZE) && + (client.size() == wvoec::MAC_KEY_SIZE) && + (0 == CRYPTO_memcmp(&server[0], data_.mac_key_server, + wvoec::MAC_KEY_SIZE)) && + (0 == + CRYPTO_memcmp(&client[0], data_.mac_key_client, wvoec::MAC_KEY_SIZE)); +} + +bool UsageTableEntry::SetMacKeys(const std::vector& server, + const std::vector& client) { + if ((server.size() != wvoec::MAC_KEY_SIZE) || + (client.size() != wvoec::MAC_KEY_SIZE)) + return false; + memcpy(data_.mac_key_server, &server[0], wvoec::MAC_KEY_SIZE); + memcpy(data_.mac_key_client, &client[0], wvoec::MAC_KEY_SIZE); + return true; +} + +void UsageTableEntry::ForbidReport() { + forbid_report_ = true; + data_.generation_number++; + usage_table_->IncrementGeneration(); +} + +OEMCryptoResult UsageTableEntry::ReportUsage(const std::vector& pst, + uint8_t* buffer, + size_t* buffer_length) { + if (forbid_report_) return OEMCrypto_ERROR_ENTRY_NEEDS_UPDATE; + if (recent_decrypt_) return OEMCrypto_ERROR_ENTRY_NEEDS_UPDATE; + if (pst.size() == 0 || pst.size() > kMaxPSTLength || + pst.size() != data_.pst_length) { + LOGE("ReportUsage: bad pst length = %d, should be %d.", pst.size(), + data_.pst_length); + return OEMCrypto_ERROR_WRONG_PST; + } + if (CRYPTO_memcmp(&pst[0], data_.pst, data_.pst_length)) { + LOGE("ReportUsage: wrong pst %s, should be %s.", wvutil::b2a_hex(pst).c_str(), + wvutil::HexEncode(data_.pst, data_.pst_length).c_str()); + return OEMCrypto_ERROR_WRONG_PST; + } + size_t length_needed = wvutil::Unpacked_PST_Report::report_size(pst.size()); + if (*buffer_length < length_needed) { + *buffer_length = length_needed; + return OEMCrypto_ERROR_SHORT_BUFFER; + } + if (!buffer) { + LOGE("ReportUsage: buffer was null pointer."); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + wvutil::Unpacked_PST_Report pst_report(buffer); + int64_t now = usage_table_->ce_->SystemTime(); + pst_report.set_seconds_since_license_received(now - + data_.time_of_license_received); + pst_report.set_seconds_since_first_decrypt(now - data_.time_of_first_decrypt); + pst_report.set_seconds_since_last_decrypt(now - data_.time_of_last_decrypt); + pst_report.set_status(data_.status); + pst_report.set_clock_security_level(kSecureTimer); + pst_report.set_pst_length(data_.pst_length); + memcpy(pst_report.pst(), data_.pst, data_.pst_length); + unsigned int md_len = SHA_DIGEST_LENGTH; + if (!HMAC(EVP_sha1(), data_.mac_key_client, wvoec::MAC_KEY_SIZE, + buffer + SHA_DIGEST_LENGTH, length_needed - SHA_DIGEST_LENGTH, + pst_report.signature(), &md_len)) { + LOGE("ReportUsage: could not compute signature."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + return OEMCrypto_SUCCESS; +} + +void UsageTableEntry::UpdateAndIncrement(ODK_ClockValues* clock_values) { + if (recent_decrypt_) { + data_.time_of_last_decrypt = usage_table_->ce_->SystemTime(); + recent_decrypt_ = false; + } + data_.time_of_license_received = clock_values->time_of_license_signed; + data_.time_of_first_decrypt = clock_values->time_of_first_decrypt; + // Use the most recent time_of_last_decrypt. + if (static_cast(data_.time_of_last_decrypt) < + clock_values->time_of_last_decrypt) { + // For the reference implementation, we update the clock_values on every + // decrypt. + data_.time_of_last_decrypt = clock_values->time_of_last_decrypt; + } else { + // For this reference implementation of OEMCrypto, we regularly update + // clock_values->time_of_last_decrypt and we could just update + // data_.time_of_last_decrypt here. However, I'm including the line below to + // make it clear that you could do it the other way around. When this + // function is called, the two values should be synced so that the usage + // entry can be saved with the correct value. + clock_values->time_of_last_decrypt = data_.time_of_last_decrypt; + } + data_.status = clock_values->status; + data_.generation_number++; + usage_table_->IncrementGeneration(); + forbid_report_ = false; +} + +OEMCryptoResult UsageTableEntry::SaveData(CryptoEngine* ce, + SessionContext* session, + uint8_t* signed_buffer, + size_t buffer_size) { + // buffer_size was determined by calling function. + if (buffer_size != SignedEntrySize()) return OEMCrypto_ERROR_UNKNOWN_FAILURE; + std::vector clear_buffer(buffer_size); + memset(&clear_buffer[0], 0, buffer_size); + memset(signed_buffer, 0, buffer_size); + SignedEntryBlock* clear = + reinterpret_cast(&clear_buffer[0]); + SignedEntryBlock* encrypted = + reinterpret_cast(signed_buffer); + clear->data = data_; // Copy the current data. + memcpy(clear->verification, kEntryVerification, kMagicLength); + + // This should be encrypted and signed with a device specific key. + // For the reference implementation, I'm just going to use the keybox key. + const std::vector& key = ce->DeviceRootKey(); + if (key.empty()) { + LOGE("SaveUsageEntry: DeviceRootKey is unexpectedly empty."); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + + // Encrypt the entry. + RAND_bytes(encrypted->iv, wvoec::KEY_IV_SIZE); + uint8_t iv_buffer[wvoec::KEY_IV_SIZE]; // working iv buffer. + memcpy(iv_buffer, encrypted->iv, wvoec::KEY_IV_SIZE); + AES_KEY aes_key; + AES_set_encrypt_key(&key[0], 128, &aes_key); + AES_cbc_encrypt( + &clear_buffer[kEncryptionOffset], &signed_buffer[kEncryptionOffset], + buffer_size - kEncryptionOffset, &aes_key, iv_buffer, AES_ENCRYPT); + + // Sign the entry. + unsigned int sig_length = SHA256_DIGEST_LENGTH; + if (!HMAC(EVP_sha256(), &key[0], key.size(), + &signed_buffer[SHA256_DIGEST_LENGTH], + buffer_size - SHA256_DIGEST_LENGTH, encrypted->signature, + &sig_length)) { + LOGE("SaveUsageEntry: Could not sign entry."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + return OEMCrypto_SUCCESS; +} + +OEMCryptoResult UsageTableEntry::LoadData(CryptoEngine* ce, uint32_t index, + const std::vector& buffer, + ODK_ClockValues* clock_values) { + if (buffer.size() < SignedEntrySize()) return OEMCrypto_ERROR_SHORT_BUFFER; + if (buffer.size() > SignedEntrySize()) + LOGW("LoadUsageTableEntry: buffer is large. %d > %d", buffer.size(), + SignedEntrySize()); + std::vector clear_buffer(buffer.size()); + SignedEntryBlock* clear = + reinterpret_cast(&clear_buffer[0]); + const SignedEntryBlock* encrypted = + reinterpret_cast(&buffer[0]); + + // This should be encrypted and signed with a device specific key. + // For the reference implementation, I'm just going to use the keybox key. + const std::vector& key = ce->DeviceRootKey(); + if (key.empty()) { + LOGE("LoadUsageEntry: DeviceRootKey is unexpectedly empty."); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + + // Verify the signature of the usage entry. Sign encrypted into clear buffer. + unsigned int sig_length = SHA256_DIGEST_LENGTH; + if (!HMAC(EVP_sha256(), &key[0], key.size(), &buffer[SHA256_DIGEST_LENGTH], + buffer.size() - SHA256_DIGEST_LENGTH, clear->signature, + &sig_length)) { + LOGE("LoadUsageEntry: Could not sign entry."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (CRYPTO_memcmp(clear->signature, encrypted->signature, + SHA256_DIGEST_LENGTH)) { + LOGE("LoadUsageEntry: Signature did not match."); + LOGE("LoadUsageEntry: Invalid signature given: %s", + wvutil::HexEncode(encrypted->signature, sig_length).c_str()); + LOGE("LoadUsageEntry: Invalid signature computed: %s", + wvutil::HexEncode(clear->signature, sig_length).c_str()); + return OEMCrypto_ERROR_SIGNATURE_FAILURE; + } + + // Next, decrypt the entry. + uint8_t iv_buffer[wvoec::KEY_IV_SIZE]; + memcpy(iv_buffer, encrypted->iv, wvoec::KEY_IV_SIZE); + AES_KEY aes_key; + AES_set_decrypt_key(&key[0], 128, &aes_key); + AES_cbc_encrypt(&buffer[kEncryptionOffset], &clear_buffer[kEncryptionOffset], + buffer.size() - kEncryptionOffset, &aes_key, iv_buffer, + AES_DECRYPT); + + // Check the verification string is correct. + if (memcmp(kEntryVerification, clear->verification, kMagicLength)) { + LOGE("LoadUsageEntry: Invalid magic: %s=%8.8s expected: %s=%8.8s", + wvutil::HexEncode(clear->verification, kMagicLength).c_str(), + clear->verification, + wvutil::HexEncode(reinterpret_cast(kEntryVerification), + kMagicLength) + .c_str(), + reinterpret_cast(kEntryVerification)); + return OEMCrypto_ERROR_BAD_MAGIC; + } + + // Check that the index is correct. + if (index != clear->data.index) { + LOGE("LoadUsageEntry: entry says index is %d, not %d", clear->data.index, + index); + return OEMCrypto_ERROR_INVALID_SESSION; + } + if (clear->data.status > kInactiveUnused) { + LOGE("LoadUsageEntry: entry has bad status %d", clear->data.status); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + data_ = clear->data; + return ODK_ReloadClockValues( + clock_values, data_.time_of_license_received, data_.time_of_first_decrypt, + data_.time_of_last_decrypt, data_.status, ce->SystemTime()); +} + +size_t UsageTableEntry::SignedEntrySize() { + size_t base = sizeof(SignedEntryBlock); + // round up to make even number of blocks: + size_t blocks = (base - 1) / wvoec::KEY_IV_SIZE + 1; + return blocks * wvoec::KEY_IV_SIZE; +} + +UsageTable::~UsageTable() {} + +size_t UsageTable::SignedHeaderSize(size_t count) { + size_t base = sizeof(SignedHeaderBlock) + count * sizeof(int64_t); + // round up to make even number of blocks: + size_t blocks = (base - 1) / wvoec::KEY_IV_SIZE + 1; + return blocks * wvoec::KEY_IV_SIZE; +} + +OEMCryptoResult UsageTable::UpdateUsageEntry( + SessionContext* session, UsageTableEntry* entry, uint8_t* header_buffer, + size_t* header_buffer_length, uint8_t* entry_buffer, + size_t* entry_buffer_length, ODK_ClockValues* clock_values) { + size_t signed_header_size = SignedHeaderSize(generation_numbers_.size()); + if (*entry_buffer_length < UsageTableEntry::SignedEntrySize() || + *header_buffer_length < signed_header_size) { + *entry_buffer_length = UsageTableEntry::SignedEntrySize(); + *header_buffer_length = signed_header_size; + return OEMCrypto_ERROR_SHORT_BUFFER; + } + *entry_buffer_length = UsageTableEntry::SignedEntrySize(); + *header_buffer_length = signed_header_size; + if ((!header_buffer) || (!entry_buffer)) + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + entry->UpdateAndIncrement(clock_values); + generation_numbers_[entry->index()] = entry->generation_number(); + OEMCryptoResult result = + entry->SaveData(ce_, session, entry_buffer, *entry_buffer_length); + if (result != OEMCrypto_SUCCESS) return result; + result = SaveUsageTableHeader(header_buffer, *header_buffer_length); + return result; +} + +UsageTableEntry* UsageTable::MakeEntry(uint32_t index) { + return new UsageTableEntry(this, index, master_generation_number_); +} + +OEMCryptoResult UsageTable::CreateNewUsageEntry( + SessionContext* session, std::unique_ptr* entry, + uint32_t* usage_entry_number) { + if (!header_loaded_) { + LOGE("CreateNewUsageEntry: Header not loaded."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (!entry) return OEMCrypto_ERROR_UNKNOWN_FAILURE; + if (!usage_entry_number) return OEMCrypto_ERROR_UNKNOWN_FAILURE; + uint32_t index = generation_numbers_.size(); + size_t max = ce_->max_usage_table_size(); + if (max > 0 && index >= max) { + LOGE("Too many usage entries: %d/%d", index, max); + return OEMCrypto_ERROR_INSUFFICIENT_RESOURCES; + } + UsageTableEntry* new_entry = MakeEntry(index); + generation_numbers_.push_back(master_generation_number_); + sessions_.push_back(session); + master_generation_number_++; + entry->reset(new_entry); + *usage_entry_number = index; + return OEMCrypto_SUCCESS; +} + +OEMCryptoResult UsageTable::LoadUsageEntry( + SessionContext* session, std::unique_ptr* entry, + uint32_t index, const std::vector& buffer, + ODK_ClockValues* clock_values) { + if (!header_loaded_) { + LOGE("CreateNewUsageEntry: Header not loaded."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (!entry) return OEMCrypto_ERROR_UNKNOWN_FAILURE; + if (index >= generation_numbers_.size()) + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + if (sessions_[index]) { + LOGE("LoadUsageEntry: index %d used by other session.", index); + return OEMCrypto_ERROR_INVALID_SESSION; + } + size_t max = ce_->max_usage_table_size(); + if (max > 0 && index >= max) { + LOGE("Too many usage entries: %d/%d", index, max); + return OEMCrypto_ERROR_INSUFFICIENT_RESOURCES; + } + std::unique_ptr new_entry(MakeEntry(index)); + + OEMCryptoResult status = + new_entry->LoadData(ce_, index, buffer, clock_values); + if (status != OEMCrypto_SUCCESS) { + return status; + } + if (new_entry->generation_number() != generation_numbers_[index]) { + LOGE("Generation SKEW: %ld -> %ld", new_entry->generation_number(), + generation_numbers_[index]); + if ((new_entry->generation_number() + 1 < generation_numbers_[index]) || + (new_entry->generation_number() - 1 > generation_numbers_[index])) { + return OEMCrypto_ERROR_GENERATION_SKEW; + } + status = OEMCrypto_WARNING_GENERATION_SKEW; + } + sessions_[index] = session; + *entry = std::move(new_entry); + return status; +} + +OEMCryptoResult UsageTable::ShrinkUsageTableHeader( + uint32_t new_table_size, uint8_t* header_buffer, + size_t* header_buffer_length) { + if (new_table_size > generation_numbers_.size()) { + LOGE("OEMCrypto_ShrinkUsageTableHeader: %d > %zd.", new_table_size, + generation_numbers_.size()); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + size_t signed_header_size = SignedHeaderSize(new_table_size); + if (*header_buffer_length < signed_header_size) { + *header_buffer_length = signed_header_size; + return OEMCrypto_ERROR_SHORT_BUFFER; + } + *header_buffer_length = signed_header_size; + if (!header_buffer) { + LOGE("OEMCrypto_ShrinkUsageTableHeader: buffer null."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + for (size_t i = new_table_size; i < sessions_.size(); ++i) { + if (sessions_[i]) { + LOGE("ShrinkUsageTableHeader: session open for %d", i); + return OEMCrypto_ERROR_ENTRY_IN_USE; + } + } + generation_numbers_.resize(new_table_size); + sessions_.resize(new_table_size); + master_generation_number_++; + return SaveUsageTableHeader(header_buffer, *header_buffer_length); +} + +OEMCryptoResult UsageTable::SaveUsageTableHeader(uint8_t* signed_buffer, + size_t buffer_size) { + if (!SaveGenerationNumber()) return OEMCrypto_ERROR_UNKNOWN_FAILURE; + size_t count = generation_numbers_.size(); + // buffer_size was determined by calling function. + if (buffer_size != SignedHeaderSize(count)) + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + std::vector clear_buffer(buffer_size); + memset(&clear_buffer[0], 0, buffer_size); + memset(signed_buffer, 0, buffer_size); + SignedHeaderBlock* clear = + reinterpret_cast(&clear_buffer[0]); + SignedHeaderBlock* encrypted = + reinterpret_cast(signed_buffer); + + // Pack the clear data into the clear buffer. + memcpy(clear->verification, kHeaderVerification, kMagicLength); + clear->master_generation = master_generation_number_; + clear->count = count; + // This points to the variable size part of the buffer. + int64_t* stored_generations = + reinterpret_cast(&clear_buffer[sizeof(SignedHeaderBlock)]); + std::copy(generation_numbers_.begin(), generation_numbers_.begin() + count, + stored_generations); + + // This should be encrypted and signed with a device specific key. + // For the reference implementation, I'm just going to use the keybox key. + const std::vector& key = ce_->DeviceRootKey(); + if (key.empty()) { + LOGE("SaveUsageTableHeader: DeviceRootKey is unexpectedly empty."); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + + // Encrypt the entry. + RAND_bytes(encrypted->iv, wvoec::KEY_IV_SIZE); + uint8_t iv_buffer[wvoec::KEY_IV_SIZE]; // working iv buffer. + memcpy(iv_buffer, encrypted->iv, wvoec::KEY_IV_SIZE); + AES_KEY aes_key; + AES_set_encrypt_key(&key[0], 128, &aes_key); + AES_cbc_encrypt( + &clear_buffer[kEncryptionOffset], &signed_buffer[kEncryptionOffset], + buffer_size - kEncryptionOffset, &aes_key, iv_buffer, AES_ENCRYPT); + + // Sign the entry. + unsigned int sig_length = SHA256_DIGEST_LENGTH; + if (!HMAC(EVP_sha256(), &key[0], key.size(), + &signed_buffer[SHA256_DIGEST_LENGTH], + buffer_size - SHA256_DIGEST_LENGTH, encrypted->signature, + &sig_length)) { + LOGE("SaveUsageHeader: Could not sign entry."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + return OEMCrypto_SUCCESS; +} + +OEMCryptoResult UsageTable::LoadUsageTableHeader( + const std::vector& buffer) { + if (!LoadGenerationNumber(false)) return OEMCrypto_ERROR_UNKNOWN_FAILURE; + + if (buffer.size() < SignedHeaderSize(0)) return OEMCrypto_ERROR_SHORT_BUFFER; + size_t max = ce_->max_usage_table_size(); + if (max > 0 && buffer.size() > SignedHeaderSize(max)) { + LOGE("Header too big: %zd bytes/%zd bytes", + buffer.size(), SignedHeaderSize(max)); + return OEMCrypto_ERROR_INSUFFICIENT_RESOURCES; + } + std::vector clear_buffer(buffer.size()); + SignedHeaderBlock* clear = + reinterpret_cast(&clear_buffer[0]); + const SignedHeaderBlock* encrypted = + reinterpret_cast(&buffer[0]); + + // This should be encrypted and signed with a device specific key. + // For the reference implementation, I'm just going to use the keybox key. + const std::vector& key = ce_->DeviceRootKey(); + if (key.empty()) { + LOGE("LoadUsageTableHeader: DeviceRootKey is unexpectedly empty."); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + + // Verify the signature of the usage entry. Sign encrypted into clear buffer. + unsigned int sig_length = SHA256_DIGEST_LENGTH; + if (!HMAC(EVP_sha256(), &key[0], key.size(), &buffer[SHA256_DIGEST_LENGTH], + buffer.size() - SHA256_DIGEST_LENGTH, clear->signature, + &sig_length)) { + LOGE("LoadUsageTableHeader: Could not sign entry."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (CRYPTO_memcmp(clear->signature, encrypted->signature, + SHA256_DIGEST_LENGTH)) { + LOGE("LoadUsageTableHeader: Signature did not match."); + LOGE("LoadUsageTableHeader: Invalid signature given: %s", + wvutil::HexEncode(encrypted->signature, sig_length).c_str()); + LOGE("LoadUsageTableHeader: Invalid signature computed: %s", + wvutil::HexEncode(clear->signature, sig_length).c_str()); + return OEMCrypto_ERROR_SIGNATURE_FAILURE; + } + + // Next, decrypt the entry. + uint8_t iv_buffer[wvoec::KEY_IV_SIZE]; + memcpy(iv_buffer, encrypted->iv, wvoec::KEY_IV_SIZE); + AES_KEY aes_key; + AES_set_decrypt_key(&key[0], 128, &aes_key); + AES_cbc_encrypt(&buffer[kEncryptionOffset], &clear_buffer[kEncryptionOffset], + buffer.size() - kEncryptionOffset, &aes_key, iv_buffer, + AES_DECRYPT); + + // Check the verification string is correct. + if (memcmp(kHeaderVerification, clear->verification, kMagicLength)) { + LOGE("LoadUsageTableHeader: Invalid magic: %s=%8.8s expected: %s=%8.8s", + wvutil::HexEncode(clear->verification, kMagicLength).c_str(), + clear->verification, + wvutil::HexEncode(reinterpret_cast(kHeaderVerification), + kMagicLength) + .c_str(), + reinterpret_cast(kHeaderVerification)); + return OEMCrypto_ERROR_BAD_MAGIC; + } + + // Check that size is correct, now that we know what it should be. + if (buffer.size() < SignedHeaderSize(clear->count)) { + return OEMCrypto_ERROR_SHORT_BUFFER; + } + if (buffer.size() > SignedHeaderSize(clear->count)) { + LOGW("LoadUsageTableHeader: buffer is large. %d > %d", buffer.size(), + SignedHeaderSize(clear->count)); + } + + OEMCryptoResult status = OEMCrypto_SUCCESS; + if (clear->master_generation != master_generation_number_) { + LOGE("Generation SKEW: %ld -> %ld", clear->master_generation, + master_generation_number_); + if ((clear->master_generation + 1 < master_generation_number_) || + (clear->master_generation - 1 > master_generation_number_)) { + return OEMCrypto_ERROR_GENERATION_SKEW; + } + status = OEMCrypto_WARNING_GENERATION_SKEW; + } + int64_t* stored_generations = + reinterpret_cast(&clear_buffer[0] + sizeof(SignedHeaderBlock)); + generation_numbers_.assign(stored_generations, + stored_generations + clear->count); + sessions_.clear(); + sessions_.resize(clear->count); + header_loaded_ = true; + return status; +} + +OEMCryptoResult UsageTable::MoveEntry(UsageTableEntry* entry, + uint32_t new_index) { + if (new_index >= generation_numbers_.size()) { + LOGE("MoveEntry: index beyond end of usage table %d >= %d", new_index, + generation_numbers_.size()); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if (sessions_[new_index]) { + LOGE("MoveEntry: session open for %d", new_index); + return OEMCrypto_ERROR_ENTRY_IN_USE; + } + if (!entry) { + LOGE("MoveEntry: null entry"); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + sessions_[new_index] = sessions_[entry->index()]; + sessions_[entry->index()] = 0; + + entry->set_index(new_index); + generation_numbers_[new_index] = master_generation_number_; + entry->set_generation_number(master_generation_number_); + master_generation_number_++; + return OEMCrypto_SUCCESS; +} + +void UsageTable::IncrementGeneration() { + master_generation_number_++; + SaveGenerationNumber(); +} + +bool UsageTable::SaveGenerationNumber() { + wvutil::FileSystem* file_system = ce_->file_system(); + std::string path; + // Note: this path is OK for a real implementation, but using security level 1 + // would be better. + // TODO(jfore, rfrias): Address how this property is presented to the ref. + // For now, the path is empty. + /*if (!Properties::GetDeviceFilesBasePath(kSecurityLevelL3, + &path)) { + LOGE("UsageTable: Unable to get base path"); + return false; + }*/ + // On a real implementation, you should NOT put the generation number in + // a file in user space. It should be stored in secure memory. + std::string filename = path + "GenerationNumber.dat"; + auto file = file_system->Open( + filename, wvutil::FileSystem::kCreate | wvutil::FileSystem::kTruncate); + if (!file) { + LOGE("UsageTable: File open failed: %s", path.c_str()); + return false; + } + file->Write(reinterpret_cast(&master_generation_number_), + sizeof(int64_t)); + return true; +} + +bool UsageTable::LoadGenerationNumber(bool or_make_new_one) { + wvutil::FileSystem* file_system = ce_->file_system(); + std::string path; + // Note: this path is OK for a real implementation, but using security level 1 + // would be better. + // TODO(jfore, rfrias): Address how this property is presented to the ref. + // For now, the path is empty. + /*if (!Properties::GetDeviceFilesBasePath(kSecurityLevelL3, + &path)) { + LOGE("UsageTable: Unable to get base path"); + return false; + }*/ + // On a real implementation, you should NOT put the generation number in + // a file in user space. It should be stored in secure memory. + std::string filename = path + "GenerationNumber.dat"; + auto file = file_system->Open(filename, wvutil::FileSystem::kReadOnly); + if (!file) { + if (or_make_new_one) { + RAND_bytes(reinterpret_cast(&master_generation_number_), + sizeof(int64_t)); + return true; + } + LOGE("UsageTable: File open failed: %s (clearing table)", path.c_str()); + master_generation_number_ = 0; + return false; + } + file->Read(reinterpret_cast(&master_generation_number_), + sizeof(int64_t)); + return true; +} + +OEMCryptoResult UsageTable::CreateUsageTableHeader( + uint8_t* header_buffer, size_t* header_buffer_length) { + size_t signed_header_size = SignedHeaderSize(0); + if (*header_buffer_length < signed_header_size) { + *header_buffer_length = signed_header_size; + return OEMCrypto_ERROR_SHORT_BUFFER; + } + *header_buffer_length = signed_header_size; + if (!LoadGenerationNumber(true)) return OEMCrypto_ERROR_UNKNOWN_FAILURE; + // Make sure there are no entries that are currently tied to an open session. + for (size_t i = 0; i < sessions_.size(); ++i) { + if (sessions_[i] != nullptr) { + LOGE("CreateUsageTableHeader: index %d used by session.", i); + return OEMCrypto_ERROR_INVALID_SESSION; + } + } + sessions_.clear(); + generation_numbers_.clear(); + header_loaded_ = true; + return SaveUsageTableHeader(header_buffer, *header_buffer_length); +} + +} // namespace wvoec_ref diff --git a/oemcrypto/ref/src/oemcrypto_usage_table_ref.h b/oemcrypto/ref/src/oemcrypto_usage_table_ref.h new file mode 100644 index 0000000..f0fdcce --- /dev/null +++ b/oemcrypto/ref/src/oemcrypto_usage_table_ref.h @@ -0,0 +1,131 @@ +// 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. +// +// Reference implementation of OEMCrypto APIs +// +#ifndef OEMCRYPTO_USAGE_TABLE_REF_H_ +#define OEMCRYPTO_USAGE_TABLE_REF_H_ + +#include +#include +#include +#include + +#include "OEMCryptoCAS.h" +#include "odk_structs.h" +#include "oemcrypto_types.h" +#include "openssl/sha.h" + +namespace wvoec_ref { + +class SessionContext; +class CryptoEngine; +class UsageTable; + +const size_t kMaxPSTLength = 255; +// This is the data we store offline. +struct StoredUsageEntry { + int64_t generation_number; + int64_t time_of_license_received; + int64_t time_of_first_decrypt; + int64_t time_of_last_decrypt; + enum OEMCrypto_Usage_Entry_Status status; + uint8_t mac_key_server[wvoec::MAC_KEY_SIZE]; + uint8_t mac_key_client[wvoec::MAC_KEY_SIZE]; + uint32_t index; + uint8_t pst[kMaxPSTLength+1]; // add 1 for padding. + uint8_t pst_length; +}; + +class UsageTableEntry { + public: + UsageTableEntry(UsageTable* table, uint32_t index, int64_t generation); + virtual ~UsageTableEntry(); // Free memory, remove reference in header. + bool Inactive() { return data_.status >= kInactive; } + // Mark this entry as modified and forbid a usage report until the data has + // been saved. This is done on important events like first decrypt and + // deactivation. + void ForbidReport(); + OEMCryptoResult SetPST(const uint8_t* pst, size_t pst_length); + bool VerifyPST(const uint8_t* pst, size_t pst_length); + bool VerifyMacKeys(const std::vector& server, + const std::vector& client); + bool SetMacKeys(const std::vector& server, + const std::vector& client); + virtual OEMCryptoResult ReportUsage(const std::vector& pst, + uint8_t* buffer, size_t* buffer_length); + virtual void UpdateAndIncrement(ODK_ClockValues* clock_values); + // Save all data to the given buffer. This should be called after updating the + // data. + OEMCryptoResult SaveData(CryptoEngine* ce, SessionContext* session, + uint8_t* signed_buffer, size_t buffer_size); + // Load all data from the buffer, and then update clock_values. + OEMCryptoResult LoadData(CryptoEngine* ce, uint32_t index, + const std::vector& buffer, + ODK_ClockValues* clock_values); + int64_t generation_number() { return data_.generation_number; } + void set_generation_number(int64_t value) { data_.generation_number = value; } + void set_index(int32_t index) { data_.index = index; } + uint32_t index() { return data_.index; } + void set_recent_decrypt(bool recent_decrypt) { + recent_decrypt_ = recent_decrypt; + } + static size_t SignedEntrySize(); + const uint8_t* mac_key_server() const { return data_.mac_key_server; } + const uint8_t* mac_key_client() const { return data_.mac_key_client; } + + protected: + UsageTable* usage_table_; // Owner of this object. + bool recent_decrypt_; + bool forbid_report_; + StoredUsageEntry data_; +}; + +class UsageTable { + public: + explicit UsageTable(CryptoEngine* ce) : ce_(ce), header_loaded_(false){}; + virtual ~UsageTable(); + + OEMCryptoResult CreateNewUsageEntry(SessionContext* session, + std::unique_ptr* entry, + uint32_t* usage_entry_number); + OEMCryptoResult LoadUsageEntry(SessionContext* session, + std::unique_ptr* entry, + uint32_t index, + const std::vector& buffer, + ODK_ClockValues* clock_values); + OEMCryptoResult UpdateUsageEntry( + SessionContext* session, UsageTableEntry* entry, uint8_t* header_buffer, + size_t* header_buffer_length, uint8_t* entry_buffer, + size_t* entry_buffer_length, ODK_ClockValues* clock_values); + OEMCryptoResult MoveEntry(UsageTableEntry* entry, uint32_t new_index); + OEMCryptoResult CreateUsageTableHeader(uint8_t* header_buffer, + size_t* header_buffer_length); + OEMCryptoResult LoadUsageTableHeader(const std::vector& buffer); + OEMCryptoResult ShrinkUsageTableHeader(uint32_t new_table_size, + uint8_t* header_buffer, + size_t* header_buffer_length); + void ReleaseEntry(uint32_t index) { sessions_[index] = 0; } + void IncrementGeneration(); + static size_t SignedHeaderSize(size_t count); + + protected: + virtual UsageTableEntry* MakeEntry(uint32_t index); + virtual OEMCryptoResult SaveUsageTableHeader(uint8_t* signed_buffer, + size_t buffer_size); + virtual bool SaveGenerationNumber(); + virtual bool LoadGenerationNumber(bool or_make_new_one); + + CryptoEngine* ce_; + bool header_loaded_; + int64_t master_generation_number_; + std::vector generation_numbers_; + std::vector sessions_; + + friend class UsageTableEntry; +}; + +} // namespace wvoec_ref + +#endif // OEMCRYPTO_USAGE_TABLE_REF_H_ diff --git a/oemcrypto/ref/src/ts_parser.cpp b/oemcrypto/ref/src/ts_parser.cpp new file mode 100644 index 0000000..17c8b70 --- /dev/null +++ b/oemcrypto/ref/src/ts_parser.cpp @@ -0,0 +1,57 @@ +// 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. +#include "ts_parser.h" + +namespace wvoec_ref { + +uint8_t TransportStreamParser::sync_byte() const { return data_[kSyncByte]; } + +uint16_t TransportStreamParser::pid_number() const { + return ((data_[kFlagByte] << 8) | data_[kPIDByte]) & kPIDMask; +} + +bool TransportStreamParser::is_valid_pid() const { + return pid_number() >= kValidPIDLowBound && + pid_number() <= kValidPIDUpperBound; +} + +bool TransportStreamParser::is_uint_start() const { + return kPUSIndicator & data_[kFlagByte]; +} + +bool TransportStreamParser::is_transport_error() const { + return KTErrIndicator & data_[kFlagByte]; +} + +bool TransportStreamParser::is_transport_priority() const { + return kTprioIndicator & data_[kFlagByte]; +} + +uint8_t TransportStreamParser::scrambling_bits() const { + return (data_[kScrCtlByte] & kScrambleMask) >> kScrambleShift; +} + +uint8_t TransportStreamParser::adaption_bits() const { + return (data_[kScrCtlByte] & kAdaptMask) >> kAdaptShift; +} + +uint8_t TransportStreamParser::continuity_counter() const { + return data_[kScrCtlByte] & 0xF; +} + +uint8_t TransportStreamParser::header_size() const { + // The 4th byte is the adaptation field length (excluding the byte itself). + return adaption_bits() >> 1 ? kTSHeaderSize + data_[kTSHeaderSize] + 1 + : kTSHeaderSize; +} + +const uint8_t* TransportStreamParser::payload_data() const { + return data_ + header_size(); +} + +uint8_t TransportStreamParser::payload_data_size() const { + return kTSPacketSize > header_size() ? kTSPacketSize - header_size() : 0; +} + +} // namespace wvoec_ref diff --git a/oemcrypto/ref/src/ts_parser.h b/oemcrypto/ref/src/ts_parser.h new file mode 100644 index 0000000..222dd84 --- /dev/null +++ b/oemcrypto/ref/src/ts_parser.h @@ -0,0 +1,87 @@ +// 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 WVOEC_REF_TS_PARSER_H_ +#define WVOEC_REF_TS_PARSER_H_ + +#include +#include + +#include "disallow_copy_and_assign.h" + +namespace wvoec_ref { + +static const size_t kTSPacketSize = 188; +static const uint8_t kTSSyncByte = 0x47; +static const size_t kTSHeaderSize = 4; +static const uint16_t kValidPIDLowBound = 0x0010; +static const uint16_t kValidPIDUpperBound = 0x1FFE; + +// Class wrapping packet header of MPEG-2 TS +// +======================================== +// | 8 | sync byte +// +---------------------------------------- +// | 1 | transport error indicator +// +---------------------------------------- +// | 1 | payload unit start indicator +// +---------------------------------------- +// | 1 | transport priority +// +---------------------------------------- +// | 13 | PID +// +---------------------------------------- +// | 2 | transport scrambling +// +---------------------------------------- +// | 2 | adaptation field control +// +---------------------------------------- +// | 4 | continuity counter +// +======= OPTIONAL ======================= +// | ~~~ | adaptation field +// +---------------------------------------- +// | ~~~ | payload data +// +======================================== +class TransportStreamParser { + public: + enum { kSyncByte = 0, kFlagByte = 1, kPIDByte = 2, kScrCtlByte = 3 }; + typedef enum { + kClear = 0x0, + kRSVD = 0x40, + kEvenKey = 0x80, + kOddKey = 0xC0, + kScrambleMask = 0xC0, + kScrambleShift = 6 + } ScramblingBits_t; + typedef enum { kAdaptMask = 0x30, kAdaptShift = 4 } AdaptionBits_t; + static const uint16_t kPIDMask = 0x1FFF; + static const uint8_t KTErrIndicator = 0x80; + static const uint8_t kPUSIndicator = 0x40; + static const uint8_t kTprioIndicator = 0x20; + + explicit TransportStreamParser(const uint8_t* buffer) : data_(buffer) {} + virtual ~TransportStreamParser() = default; + + uint8_t sync_byte() const; + uint16_t pid_number() const; + bool is_uint_start() const; + bool is_transport_error() const; + bool is_transport_priority() const; + uint8_t scrambling_bits() const; + uint8_t adaption_bits() const; + uint8_t continuity_counter() const; + + // Check if pid value is within the expected range. + bool is_valid_pid() const; + // Returns the pointer to the payload start. + const uint8_t* payload_data() const; + // Payload data size not counting TS header nor adaptation filed. + uint8_t payload_data_size() const; + + private: + uint8_t header_size() const; + + const uint8_t* data_; + + CORE_DISALLOW_COPY_AND_ASSIGN(TransportStreamParser); +}; +} // namespace wvoec_ref + +#endif // WVOEC_REF_TS_PARSER_H_ diff --git a/oemcrypto/ref/src/wvcrc.cpp b/oemcrypto/ref/src/wvcrc.cpp new file mode 100644 index 0000000..b3d0454 --- /dev/null +++ b/oemcrypto/ref/src/wvcrc.cpp @@ -0,0 +1,108 @@ +// 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. +// +// Compute CRC32 Checksum. Needed for verification of WV Keybox. +// +#include "platform.h" +#include "wvcrc32.h" + +namespace wvoec_ref { + +#define INIT_CRC32 0xffffffff + +uint32_t wvrunningcrc32(const uint8_t* p_begin, int i_count, uint32_t i_crc) { + static uint32_t CRC32[256] = { + 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, + 0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005, + 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, + 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, + 0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, + 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75, + 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, + 0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd, + 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, + 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, + 0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81, + 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d, + 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, + 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95, + 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, + 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, + 0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae, + 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072, + 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, + 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca, + 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, + 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, + 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066, + 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, + 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, + 0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692, + 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, + 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, + 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e, + 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, + 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, + 0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a, + 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, + 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, + 0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, + 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53, + 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, + 0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b, + 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, + 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, + 0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, + 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b, + 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, + 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3, + 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, + 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, + 0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f, + 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3, + 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, + 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c, + 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, + 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, + 0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30, + 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, + 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, + 0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654, + 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, + 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, + 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18, + 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, + 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, + 0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c, + 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, + 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4 + }; + + /* Calculate the CRC */ + while (i_count > 0) { + i_crc = (i_crc << 8) ^ CRC32[(i_crc >> 24) ^ ((uint32_t) * p_begin)]; + p_begin++; + i_count--; + } + + return(i_crc); +} + +uint32_t wvcrc32(const uint8_t* p_begin, int i_count) { + return(wvrunningcrc32(p_begin, i_count, INIT_CRC32)); +} + +uint32_t wvcrc32Init() { + return INIT_CRC32; +} + +uint32_t wvcrc32Cont(const uint8_t* p_begin, int i_count, uint32_t prev_crc) { + return(wvrunningcrc32(p_begin, i_count, prev_crc)); +} + +uint32_t wvcrc32n(const uint8_t* p_begin, int i_count) { + return htonl(wvrunningcrc32(p_begin, i_count, INIT_CRC32)); +} + +} // namespace wvoec_ref diff --git a/oemcrypto/ref/src/wvcrc32.h b/oemcrypto/ref/src/wvcrc32.h new file mode 100644 index 0000000..99677ec --- /dev/null +++ b/oemcrypto/ref/src/wvcrc32.h @@ -0,0 +1,23 @@ +// 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. +// +// Compute CRC32 Checksum. Needed for verification of WV Keybox. +// +#ifndef WVCRC32_H_ +#define WVCRC32_H_ + +#include + +namespace wvoec_ref { + +uint32_t wvcrc32(const uint8_t* p_begin, int i_count); +uint32_t wvcrc32Init(); +uint32_t wvcrc32Cont(const uint8_t* p_begin, int i_count, uint32_t prev_crc); + +// Convert to network byte order +uint32_t wvcrc32n(const uint8_t* p_begin, int i_count); + +} // namespace wvoec_ref + +#endif // WVCRC32_H_ diff --git a/oemcrypto/renewal/Makefile b/oemcrypto/renewal/Makefile new file mode 100644 index 0000000..f2bdd38 --- /dev/null +++ b/oemcrypto/renewal/Makefile @@ -0,0 +1,3 @@ +derive_key: derive_key.cpp + g++ -g -o derive_key derive_key.cpp -lssl -lcrypto + diff --git a/oemcrypto/renewal/derive_key.cpp b/oemcrypto/renewal/derive_key.cpp new file mode 100644 index 0000000..9cde5a9 --- /dev/null +++ b/oemcrypto/renewal/derive_key.cpp @@ -0,0 +1,176 @@ +// 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. +// +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +// clang-format off + +// This is a test keybox. It will not be accepted by production systems. +static std::vector test_keybox = { + // sample keybox used for test vectors + // deviceID = WidevineTestOnlyKeybox000 + 0x57, 0x69, 0x64, 0x65, 0x76, 0x69, 0x6e, 0x65, + 0x54, 0x65, 0x73, 0x74, 0x4f, 0x6e, 0x6c, 0x79, + 0x4b, 0x65, 0x79, 0x62, 0x6f, 0x78, 0x30, 0x30, + 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + // key + 0xe4, 0xff, 0x57, 0x4c, 0x32, 0x2e, 0xf5, 0x34, + 0x26, 0x21, 0x2c, 0xb3, 0xed, 0x37, 0xf3, 0x5e, + + // data (system ID 7912 = 1EE8). + 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x1e, 0xe8, + 0xca, 0x1e, 0x71, 0x7c, 0xfb, 0xe8, 0xa3, 0x94, + 0x52, 0x0a, 0x6b, 0x71, 0x37, 0xd2, 0x69, 0xfa, + 0x5a, 0xc6, 0xb5, 0x4c, 0x6b, 0x46, 0x63, 0x9b, + 0xbe, 0x80, 0x3d, 0xbb, 0x4f, 0xf7, 0x4c, 0x5f, + 0x6f, 0x55, 0x0e, 0x3d, 0x3d, 0x9a, 0xcf, 0x81, + 0x12, 0x5d, 0x52, 0xe0, 0x47, 0x8c, 0xda, 0x0b, + 0xf4, 0x31, 0x41, 0x13, 0xd0, 0xd5, 0x2d, 0xa0, + 0x5b, 0x20, 0x9a, 0xed, 0x51, 0x5d, 0x13, 0xd6, + + // magic + 0x6b, 0x62, 0x6f, 0x78, + + // Crc + 0x39, 0xf2, 0x94, 0xa7, +}; + +const size_t DEVICE_KEY_OFFSET = 0x20; +const size_t CA_TOKEN_OFFSET = 0x30; +const size_t KEYBOX_VERSION_OFFSET = CA_TOKEN_OFFSET; +const size_t SYSTEM_ID_OFFSET = CA_TOKEN_OFFSET + sizeof(uint32_t); +const size_t CA_TOKEN_SIZE = 0x48; +const size_t CA_TOKEN_END = CA_TOKEN_OFFSET + CA_TOKEN_SIZE; + +// sample renewal key for testing +static const std::vector renewal_key = { + 0xfa, 0xfd, 0xc1, 0x1b, 0x55, 0x6f, 0xac, 0xd3, + 0x14, 0x62, 0x50, 0xa0, 0xf7, 0xa5, 0x1a, 0x0e +}; + +static const std::string label = "Keyboxv3"; +static const std::vector new_system_id = {0x00, 0x00, 0x23, 0x45}; +static const std::vector new_keybox_version = {0x00, 0x00, 0x00, 0x03}; + +// clang-format on + +static std::string GetSSLError() { + char error_buffer[128]; + ERR_error_string_n(ERR_get_error(), error_buffer, sizeof(error_buffer)); + return error_buffer; +} + +// Derive the new device key from renewal key and context using +// NIST 800-108 key derivation with 128-bit AES-128-CMAC as the pseudorandom +// function in counter mode: +// +// New Device Key := PRF(renewal_key, 1 || label || 0x00 || context || L) +// PRF := AES-128-CMAC +// label := “Keyboxv3” +// L=(unsigned long)0x80: 0x00|0x00|0x00|0x80 +static bool DeriveKey(const std::vector& key, + const std::vector& context, + std::vector* out) { + if (key.empty() || context.empty() || out == nullptr) { + std::cerr << "DeriveKey(): Invalid inputs" << std::endl; + return false; + } + + std::vector cmac_input; + cmac_input.push_back(1); + std::copy(label.begin(), label.end(), std::back_inserter(cmac_input)); + cmac_input.push_back(0x00); + std::copy(context.begin(), context.end(), std::back_inserter(cmac_input)); + cmac_input.push_back(0x00); + cmac_input.push_back(0x00); + cmac_input.push_back(0x00); + cmac_input.push_back(0x80); + + const EVP_CIPHER* cipher = EVP_aes_128_cbc(); + CMAC_CTX* cmac_ctx = CMAC_CTX_new(); + + if (!cmac_ctx) { + std::cerr << "DeriveKey(): Failed to create context: " << GetSSLError() + << std::endl; + return false; + } + + if (!CMAC_Init(cmac_ctx, &key[0], key.size(), cipher, 0)) { + std::cerr << "DeriveKey(): Failed to initialize: " << GetSSLError() + << std::endl; + CMAC_CTX_free(cmac_ctx); + return false; + } + + if (!CMAC_Update(cmac_ctx, &cmac_input[0], cmac_input.size())) { + std::cerr << "DeriveKey(): Failed to update: " << GetSSLError() + << std::endl; + CMAC_CTX_free(cmac_ctx); + return false; + } + + size_t reslen; + uint8_t res[128]; + if (!CMAC_Final(cmac_ctx, res, &reslen)) { + std::cerr << "DeriveKey(): Failed to finalize: " << GetSSLError() + << std::endl; + CMAC_CTX_free(cmac_ctx); + return false; + } + out->assign(res, res + reslen); + CMAC_CTX_free(cmac_ctx); + + return true; +} + +int main(int argc, char** argv) { + if (argc != 2) { + std::cerr << "usage: " << argv[0] << " " << std::endl; + exit(0); + } + + std::string filename = argv[1]; + std::ofstream new_key_file; + new_key_file.open(filename, std::ios::binary); + if (!new_key_file) { + std::cerr << "unable to open " << filename << " for writing" << std::endl; + exit(-1); + } + + // patch the keybox with version 3 and system ID + std::copy(new_keybox_version.begin(), new_keybox_version.end(), + test_keybox.begin() + KEYBOX_VERSION_OFFSET); + std::copy(new_system_id.begin(), new_system_id.end(), + test_keybox.begin() + SYSTEM_ID_OFFSET); + + // context is Device Key || CA token + std::vector context(test_keybox.begin() + DEVICE_KEY_OFFSET, + test_keybox.begin() + CA_TOKEN_END); + + // derive the new device key + std::vector new_device_key; + if (!DeriveKey(renewal_key, context, &new_device_key)) { + std::cerr << "Failed to derive new renewal key" << std::endl; + exit(-1); + } else { + std::ostream_iterator output_iterator(new_key_file); + std::copy(new_device_key.begin(), new_device_key.end(), output_iterator); + new_key_file.close(); + std::cout << "New key written to " << filename << std::endl; + ; + } +} diff --git a/oemcrypto/test/Android.bp b/oemcrypto/test/Android.bp new file mode 100644 index 0000000..be7f594 --- /dev/null +++ b/oemcrypto/test/Android.bp @@ -0,0 +1,38 @@ +cc_binary { + name: "oemcrypto_cas_test", + proprietary: true, + srcs: [ + "oec_device_features.cpp", + "oec_decrypt_fallback_chain.cpp", + "oec_key_deriver.cpp", + "oec_session_util.cpp", + "oemcrypto_corpus_generator_helper.cpp", + "oemcrypto_session_tests_helper.cpp", + "oemcrypto_test.cpp", + "oemcrypto_test_android.cpp", + "oemcrypto_test_main.cpp", + "wvcrc.cpp", + "test_sleep.cpp", + ], + header_libs: [ + "//vendor/widevine/libwvmediacas/oemcrypto:oemcastroheaders", + "media_plugin_headers", + ], + include_dirs: [ + "vendor/widevine/libwvmediacas/oemcrypto/odk/include", + ], + static_libs: [ + "//vendor/widevine/libwvmediacas/wvutil:libcasutil", + "//vendor/widevine/libwvmediacas/plugin:libwvcasplugins", + "libgmock", + "libgtest", + "libwvcas_odk", + "libwvcas_kdo", + ], + shared_libs: [ + "//vendor/widevine/libwvmediacas/oemcrypto/ref:libcasoemcrypto", + "libcrypto", + "libutils", + "liblog", + ], +} diff --git a/oemcrypto/test/Android.mk b/oemcrypto/test/Android.mk new file mode 100644 index 0000000..3b8ed93 --- /dev/null +++ b/oemcrypto/test/Android.mk @@ -0,0 +1,23 @@ +LOCAL_PATH:= $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_C_INCLUDES := \ + vendor/widevine/libwvdrmengine/cdm/util/include \ + +LOCAL_MODULE:=oemcrypto_test +LOCAL_MODULE_TAGS := tests + +LOCAL_MODULE_OWNER := widevine +LOCAL_PROPRIETARY_MODULE := true + +# When built, explicitly put it in the DATA/nativetest directory. +LOCAL_MODULE_PATH := $(TARGET_OUT_DATA)/nativetest + +ifneq ($(TARGET_ENABLE_MEDIADRM_64), true) +LOCAL_MODULE_TARGET_ARCH := arm x86 mips +endif + +include $(LOCAL_PATH)/common.mk + +include $(BUILD_EXECUTABLE) diff --git a/oemcrypto/test/XtsTest.mk b/oemcrypto/test/XtsTest.mk new file mode 100644 index 0000000..e6ef7a9 --- /dev/null +++ b/oemcrypto/test/XtsTest.mk @@ -0,0 +1,13 @@ +LOCAL_PATH:= $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE := XtsOEMCryptoTestCases +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE_PATH := $(TARGET_OUT_DATA) + +LOCAL_XTS_TEST_PACKAGE := google.oemcrypto + +include $(LOCAL_PATH)/common.mk + +include $(BUILD_XTS_EXECUTABLE) diff --git a/oemcrypto/test/common.mk b/oemcrypto/test/common.mk new file mode 100644 index 0000000..c6f5d0d --- /dev/null +++ b/oemcrypto/test/common.mk @@ -0,0 +1,53 @@ +LOCAL_PATH:= $(call my-dir) + +ifeq ($(filter mips mips64, $(TARGET_ARCH)),) +# Tests need to be compatible with devices that do not support gnu hash-style +LOCAL_LDFLAGS+=-Wl,--hash-style=both +endif + +# The unit tests can access v15 functions through the dynamic adapter: +LOCAL_CFLAGS += -DTEST_OEMCRYPTO_V15 + +LOCAL_SRC_FILES:= \ + oec_device_features.cpp \ + oec_decrypt_fallback_chain.cpp \ + oec_key_deriver.cpp \ + oec_session_util.cpp \ + oemcrypto_corpus_generator_helper.cpp \ + oemcrypto_session_tests_helper.cpp \ + oemcrypto_test.cpp \ + oemcrypto_test_android.cpp \ + oemcrypto_test_main.cpp \ + wvcrc.cpp \ + ../../cdm/util/test/test_sleep.cpp \ + +LOCAL_C_INCLUDES += \ + $(LOCAL_PATH)/../include \ + $(LOCAL_PATH)/../odk/include \ + $(LOCAL_PATH)/../odk/kdo/include \ + $(LOCAL_PATH)/../ref/src \ + vendor/widevine/libwvdrmengine/cdm/core/include \ + vendor/widevine/libwvdrmengine/cdm/util/include \ + vendor/widevine/libwvdrmengine/cdm/util/test \ + +LOCAL_STATIC_LIBRARIES := \ + libcdm \ + libcdm_utils \ + libcrypto_static \ + libgtest \ + libgtest_main \ + libwvlevel3 \ + libcdm_protos \ + libcdm_utils \ + libwv_kdo \ + libwv_odk \ + +LOCAL_SHARED_LIBRARIES := \ + libbase \ + libdl \ + liblog \ + libmedia_omx \ + libprotobuf-cpp-lite \ + libstagefright_foundation \ + libutils \ + libz \ diff --git a/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/0ac99ac6565414c7f57a36bcf0c212327cc88ab3 b/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/0ac99ac6565414c7f57a36bcf0c212327cc88ab3 new file mode 100644 index 0000000..14f9e28 Binary files /dev/null and b/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/0ac99ac6565414c7f57a36bcf0c212327cc88ab3 differ diff --git a/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/25adc5d13d39231afeb8ed3da76a18f9658c681a b/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/25adc5d13d39231afeb8ed3da76a18f9658c681a new file mode 100644 index 0000000..ed6ddac --- /dev/null +++ b/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/25adc5d13d39231afeb8ed3da76a18f9658c681a @@ -0,0 +1 @@ +(c020:0d112d7ea200; \ No newline at end of file diff --git a/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/32556c2f870258f2b18c905c3cd017d7064927d7 b/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/32556c2f870258f2b18c905c3cd017d7064927d7 new file mode 100644 index 0000000..3ff9503 --- /dev/null +++ b/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/32556c2f870258f2b18c905c3cd017d7064927d7 @@ -0,0 +1 @@ +(e2!0;u \ No newline at end of file diff --git a/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/3cec58166305818af41d10666b1538024cfbe4ec b/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/3cec58166305818af41d10666b1538024cfbe4ec new file mode 100644 index 0000000..e453ee9 Binary files /dev/null and b/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/3cec58166305818af41d10666b1538024cfbe4ec differ diff --git a/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/3d152b926d295b49d73a968d1668c0b9125dd2da b/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/3d152b926d295b49d73a968d1668c0b9125dd2da new file mode 100644 index 0000000..b2cb12e --- /dev/null +++ b/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/3d152b926d295b49d73a968d1668c0b9125dd2da @@ -0,0 +1 @@ +0a4c08001248000000020000101907d9ffde13aa95c122678053362136bdf8408f8276e4c2d87ec52b61aa1b9f646e58734930acebe899b3e464189a14a87202fb02570640bd22ef44b2d7e3912250a230a14080112100915007caa9b5931b76a3a85f046523e10011a09393837363534333231180120002a0c313838363738373430350000 diff --git a/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/4879965ec6be329dcc7697d913b2e8971a9729d8 b/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/4879965ec6be329dcc7697d913b2e8971a9729d8 new file mode 100644 index 0000000..b1596cc --- /dev/null +++ b/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/4879965ec6be329dcc7697d913b2e8971a9729d8 @@ -0,0 +1 @@ +(2dea200;u \ No newline at end of file diff --git a/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/4cd2f2c92b644ee1284cd082feb8e6773499ebe7 b/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/4cd2f2c92b644ee1284cd082feb8e6773499ebe7 new file mode 100644 index 0000000..04a910c --- /dev/null +++ b/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/4cd2f2c92b644ee1284cd082feb8e6773499ebe7 @@ -0,0 +1 @@ +0a4c020:0d1190d79fef02570640bd22ef44b2d7e3912250a200 diff --git a/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/4d39a8df58267539e4db62ef45bda5d9573b00a4 b/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/4d39a8df58267539e4db62ef45bda5d9573b00a4 new file mode 100644 index 0000000..2a27021 --- /dev/null +++ b/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/4d39a8df58267539e4db62ef45bda5d9573b00a4 @@ -0,0 +1 @@ +e2!0;u \ No newline at end of file diff --git a/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/58e6b3a414a1e090dfc6029add0f3555ccba127f b/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/58e6b3a414a1e090dfc6029add0f3555ccba127f new file mode 100644 index 0000000..9cbe6ea --- /dev/null +++ b/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/58e6b3a414a1e090dfc6029add0f3555ccba127f @@ -0,0 +1 @@ +e \ No newline at end of file diff --git a/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/655668f52bfd904b9f658280d3f144491f1d2a36 b/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/655668f52bfd904b9f658280d3f144491f1d2a36 new file mode 100644 index 0000000..feb5341 --- /dev/null +++ b/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/655668f52bfd904b9f658280d3f144491f1d2a36 @@ -0,0 +1 @@ +(ea200;u \ No newline at end of file diff --git a/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/6923a33a0bb5c0694734b3063ecb212aa7873f5d b/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/6923a33a0bb5c0694734b3063ecb212aa7873f5d new file mode 100644 index 0000000..7ac6fcd --- /dev/null +++ b/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/6923a33a0bb5c0694734b3063ecb212aa7873f5d @@ -0,0 +1 @@ +0a(c020:0d112d7ea200; \ No newline at end of file diff --git a/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/71e064e7c3959c15c4b39d22e836fbfdc6b046b5 b/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/71e064e7c3959c15c4b39d22e836fbfdc6b046b5 new file mode 100644 index 0000000..e91578d --- /dev/null +++ b/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/71e064e7c3959c15c4b39d22e836fbfdc6b046b5 @@ -0,0 +1 @@ +0a4c000000200:0101907d9ffde02570640bd22ef44b2d7e3912250a230a1407363534333231180120002a0c313838363738373430350000 diff --git a/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/8edb36d75f26dc46aae4520b02deea1a645cfbc3 b/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/8edb36d75f26dc46aae4520b02deea1a645cfbc3 new file mode 100644 index 0000000..a4f7ea5 Binary files /dev/null and b/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/8edb36d75f26dc46aae4520b02deea1a645cfbc3 differ diff --git a/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/a121ddeb222a383990a85dfd75c93e3db6630a41 b/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/a121ddeb222a383990a85dfd75c93e3db6630a41 new file mode 100644 index 0000000..679be98 Binary files /dev/null and b/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/a121ddeb222a383990a85dfd75c93e3db6630a41 differ diff --git a/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/ad95e413da295fa777257154ed40dfcd8e32ba2b b/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/ad95e413da295fa777257154ed40dfcd8e32ba2b new file mode 100644 index 0000000..5360871 --- /dev/null +++ b/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/ad95e413da295fa777257154ed40dfcd8e32ba2b @@ -0,0 +1 @@ +0a4c000000220:01019dd79fef02570640bd22ef44b2d7e3912250a200 diff --git a/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/b65818237816f961d1138ef352d0b905d3eb9330 b/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/b65818237816f961d1138ef352d0b905d3eb9330 new file mode 100644 index 0000000..20673e1 Binary files /dev/null and b/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/b65818237816f961d1138ef352d0b905d3eb9330 differ diff --git a/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/bcadfd6aacc62927bdb3e0a9f04b9aa11c192b6d b/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/bcadfd6aacc62927bdb3e0a9f04b9aa11c192b6d new file mode 100644 index 0000000..8d6bcfc --- /dev/null +++ b/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/bcadfd6aacc62927bdb3e0a9f04b9aa11c192b6d @@ -0,0 +1 @@ +e; \ No newline at end of file diff --git a/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/c6f546c378e78c5a74b45ce792c88f925f34000f b/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/c6f546c378e78c5a74b45ce792c88f925f34000f new file mode 100644 index 0000000..39ca47b --- /dev/null +++ b/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/c6f546c378e78c5a74b45ce792c88f925f34000f @@ -0,0 +1 @@ +0a4c000000200:010197d9ffde02570640bd22ef44b2d7e3912250a230a1407363534333231180120002a0c313838363738373430350000 diff --git a/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/d6b1a032205b2b9ddeced35d07a9d7c7f27bbef2 b/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/d6b1a032205b2b9ddeced35d07a9d7c7f27bbef2 new file mode 100644 index 0000000..f6d9016 --- /dev/null +++ b/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/d6b1a032205b2b9ddeced35d07a9d7c7f27bbef2 @@ -0,0 +1 @@ +0a4c00000020000101907d9ffde02570640bd22ef44b2d7e3912250a230a14080112100915007caa9b5931b76a3a85f046523e10011a09393837363534333231180120002a0c313838363738373430350000 diff --git a/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/d7d4218b6a3c59f80d1fe0ece07ca13e5a2c209c b/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/d7d4218b6a3c59f80d1fe0ece07ca13e5a2c209c new file mode 100644 index 0000000..ad45249 --- /dev/null +++ b/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/d7d4218b6a3c59f80d1fe0ece07ca13e5a2c209c @@ -0,0 +1 @@ +0a4c020:0d112d7e3912250a200; \ No newline at end of file diff --git a/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/d992e60874495b187bc157e0f24d4fd8ad957094 b/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/d992e60874495b187bc157e0f24d4fd8ad957094 new file mode 100644 index 0000000..7cea4d6 --- /dev/null +++ b/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/d992e60874495b187bc157e0f24d4fd8ad957094 @@ -0,0 +1 @@ +0a4c08001248000000020000101907d9ffde02570640bd22ef44b2d7e3912250a230a14080112100915007caa9b5931b76a3a85f046523e10011a09393837363534333231180120002a0c313838363738373430350000 diff --git a/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/e7064f0b80f61dbc65915311032d27baa569ae2a b/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/e7064f0b80f61dbc65915311032d27baa569ae2a new file mode 100644 index 0000000..e8a0f87 --- /dev/null +++ b/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/e7064f0b80f61dbc65915311032d27baa569ae2a @@ -0,0 +1 @@ +) \ No newline at end of file diff --git a/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/ead485157c8b58596faa57d6c0818e6c5b652a9e b/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/ead485157c8b58596faa57d6c0818e6c5b652a9e new file mode 100644 index 0000000..44f8acb --- /dev/null +++ b/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/ead485157c8b58596faa57d6c0818e6c5b652a9e @@ -0,0 +1 @@ +e2; \ No newline at end of file diff --git a/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/ecca89819d61866b1f41e756edc08510f8f70747 b/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/ecca89819d61866b1f41e756edc08510f8f70747 new file mode 100644 index 0000000..508d436 Binary files /dev/null and b/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/ecca89819d61866b1f41e756edc08510f8f70747 differ diff --git a/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/example.txt b/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/example.txt new file mode 100644 index 0000000..be5ddd8 --- /dev/null +++ b/oemcrypto/test/fuzz_tests/GenerateSignatureCorpus/example.txt @@ -0,0 +1 @@ +0a4c08001248000000020000101907d9ffde13aa95c122678053362136bdf8408f8276e4c2d87ec52b61aa1b9f646e58734930acebe899b3e464189a14a87202fb02574e70640bd22ef44b2d7e3912250a230a14080112100915007caa9b5931b76a3a85f046523e10011a09393837363534333231180120002a0c313838363738373430350000 diff --git a/oemcrypto/test/fuzz_tests/README.md b/oemcrypto/test/fuzz_tests/README.md new file mode 100644 index 0000000..b2d3579 --- /dev/null +++ b/oemcrypto/test/fuzz_tests/README.md @@ -0,0 +1,50 @@ +# OEMCRYPTO Fuzzing + +## Objective + +* Run fuzzing on OEMCrypto public APIs on linux using google + supported clusterfuzz infrastructure to find security vulnerabilities. + +## Generate Corpus + +* Once the fuzzer scripts are ready and running continuously using clusterfuzz + or android infrastructure, we can measure the efficiency of fuzzers by looking + at code coverage and number of new features that have been discovered by fuzzer + scripts here Fuzz script statistics. + + A fuzzer which tries to start from random inputs and figure out intelligent + inputs to crash the libraries can be time consuming and not effective. + A way to make fuzzers more effective is by providing a set of valid + and invalid inputs of the library so that fuzzer can use those as a starting point. + These sets of valid and invalid inputs are called corpus. + + The idea is to run OEMCrypto unit tests and read required data into binary corpus + files before calling into respective OEMCrypto APIs under test. Writing corpus data + to binary files is controlled by environment variable SHOULD_GENERATE_CORPUS. + + +### Get OEMCrypto and Build OEMCrypto unit tests to generate corpus: + +* Install Pre-requisites + + ```shell + $ sudo apt-get install gyp ninja-build + ``` + +* download cdm source code (including ODK & OEMCrypto unit tests): + + ```shell + $ git clone sso://widevine-internal/cdm + ``` + +* Build OEMCrypto unit tests and run with --generate_corpus flag to + generate corpus files: + + ```shell + $ cd /path/to/cdm/repo + $ export CDM_DIR=/path/to/cdm/repo + $ export PATH_TO_CDM_DIR=. + $ gyp --format=ninja --depth=$(pwd) oemcrypto/oemcrypto_unittests.gyp + $ ninja -C out/Default/ + $ ./out/Default/oemcrypto_unittests --generate_corpus + ``` diff --git a/oemcrypto/test/fuzz_tests/oemcrypto_fuzz_helper.cc b/oemcrypto/test/fuzz_tests/oemcrypto_fuzz_helper.cc new file mode 100644 index 0000000..2984ff0 --- /dev/null +++ b/oemcrypto/test/fuzz_tests/oemcrypto_fuzz_helper.cc @@ -0,0 +1,8 @@ +// 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 "oemcrypto_fuzz_helper.h" + +namespace wvoec { +void RedirectStdoutToFile() { freopen("log.txt", "a", stdout); } +} // namespace wvoec diff --git a/oemcrypto/test/fuzz_tests/oemcrypto_fuzz_helper.h b/oemcrypto/test/fuzz_tests/oemcrypto_fuzz_helper.h new file mode 100644 index 0000000..7a54df4 --- /dev/null +++ b/oemcrypto/test/fuzz_tests/oemcrypto_fuzz_helper.h @@ -0,0 +1,73 @@ +// 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 OEMCRYPTO_FUZZ_HELPER_H_ +#define OEMCRYPTO_FUZZ_HELPER_H_ + +#include + +#include "OEMCryptoCAS.h" +#include "oec_device_features.h" +#include "oec_session_util.h" +#include "oemcrypto_corpus_generator_helper.h" +#include "oemcrypto_session_tests_helper.h" +namespace wvoec { +// Initial setup to create a valid OEMCrypto state such as initializing crypto +// firmware/hardware, installing golden key box etc. in order to fuzz +// OEMCrypto APIs. +class InitializeFuzz : public SessionUtil { + public: + InitializeFuzz() { + wvoec::global_features.Initialize(); + OEMCrypto_SetSandbox(kTestSandbox, sizeof(kTestSandbox)); + OEMCrypto_Initialize(); + EnsureTestKeys(); + } + + ~InitializeFuzz() { OEMCrypto_Terminate(); } +}; + +class OEMCryptoLicenseAPIFuzz : public InitializeFuzz { + public: + OEMCryptoLicenseAPIFuzz() : license_messages_(&session_) { + session_.open(); + InstallTestRSAKey(&session_); + session_.GenerateNonce(); + } + + ~OEMCryptoLicenseAPIFuzz() { + session_.close(); + } + + LicenseRoundTrip& license_messages() { return license_messages_; } + + private: + Session session_; + LicenseRoundTrip license_messages_; +}; + +class OEMCryptoProvisioningAPIFuzz : public InitializeFuzz { + public: + OEMCryptoProvisioningAPIFuzz() + : provisioning_messages_(&session_, encoded_rsa_key_) { + // Opens a session and Generates Nonce. + provisioning_messages_.PrepareSession(keybox_); + } + + ~OEMCryptoProvisioningAPIFuzz() { session_.close(); } + + ProvisioningRoundTrip& provisioning_messages() { + return provisioning_messages_; + } + + private: + Session session_; + ProvisioningRoundTrip provisioning_messages_; +}; + +// Redirect printf and log statements from oemcrypto functions to a file to +// reduce noise +void RedirectStdoutToFile(); +} // namespace wvoec + +#endif // OEMCRYPTO_FUZZ_HELPER_H_ diff --git a/oemcrypto/test/fuzz_tests/oemcrypto_generate_signature.cc b/oemcrypto/test/fuzz_tests/oemcrypto_generate_signature.cc new file mode 100644 index 0000000..19b60b7 --- /dev/null +++ b/oemcrypto/test/fuzz_tests/oemcrypto_generate_signature.cc @@ -0,0 +1,36 @@ +#include "properties.h" +#include "oemcrypto_session_tests_helper.h" + +using namespace wvoec; + +static bool is_init = false; + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + SessionUtil session_helper; + if (!is_init) { + wvoec::global_features.Initialize(); + wvoec::global_features.RestrictFilter("*"); + wvutil::Properties::Init(); + is_init = true; + } + + OEMCrypto_Initialize(); + session_helper.EnsureTestKeys(); + + Session s; + s.open(); + s.GenerateDerivedKeysFromKeybox(session_helper.keybox_); + + static const uint32_t SignatureBufferMaxLength = size; + vector signature(SignatureBufferMaxLength); + size_t signature_length = signature.size(); + + OEMCryptoResult sts; + sts = OEMCrypto_GenerateSignature(s.session_id(), data, size, + &signature[0], &signature_length); + + s.close(); + OEMCrypto_Terminate(); + + return 0; +} diff --git a/oemcrypto/test/fuzz_tests/oemcrypto_load_license_fuzz.cc b/oemcrypto/test/fuzz_tests/oemcrypto_load_license_fuzz.cc new file mode 100644 index 0000000..480992a --- /dev/null +++ b/oemcrypto/test/fuzz_tests/oemcrypto_load_license_fuzz.cc @@ -0,0 +1,24 @@ +// 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 "oemcrypto_fuzz_helper.h" + +namespace wvoec { +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + // Redirect printf and log statements from oemcrypto functions to a file to + // reduce noise + RedirectStdoutToFile(); + if (size < sizeof(ODK_ParsedLicense) + sizeof(MessageData)) { + return 0; + } + OEMCryptoLicenseAPIFuzz license_api_fuzz; + license_api_fuzz.license_messages().SignAndVerifyRequest(); + // Interpreting input fuzz data as unencrypted (core_response + license + // message data) from license server. + license_api_fuzz.license_messages().InjectFuzzedResponseData(data, size); + license_api_fuzz.license_messages().EncryptAndSignResponse(); + license_api_fuzz.license_messages().LoadResponse(); + return 0; +} +} // namespace wvoec diff --git a/oemcrypto/test/fuzz_tests/oemcrypto_load_provisioning_fuzz.cc b/oemcrypto/test/fuzz_tests/oemcrypto_load_provisioning_fuzz.cc new file mode 100644 index 0000000..739f79f --- /dev/null +++ b/oemcrypto/test/fuzz_tests/oemcrypto_load_provisioning_fuzz.cc @@ -0,0 +1,27 @@ +// 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 "oemcrypto_fuzz_helper.h" + +namespace wvoec { + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + // Redirect printf and log statements from oemcrypto functions to a file to + // reduce noise + RedirectStdoutToFile(); + if (size < sizeof(ODK_ParsedProvisioning) + sizeof(RSAPrivateKeyMessage)) { + return 0; + } + + OEMCryptoProvisioningAPIFuzz provisioning_api_fuzz; + provisioning_api_fuzz.provisioning_messages().SignAndVerifyRequest(); + // Interpreting input fuzz data as unencrypted(core_response + provisioning + // message data) from provisioning server. + provisioning_api_fuzz.provisioning_messages().InjectFuzzedResponseData(data, + size); + provisioning_api_fuzz.provisioning_messages().EncryptAndSignResponse(); + provisioning_api_fuzz.provisioning_messages().LoadResponse(); + return 0; +} +} // namespace wvoec diff --git a/oemcrypto/test/fuzz_tests/sample_test.cc b/oemcrypto/test/fuzz_tests/sample_test.cc new file mode 100644 index 0000000..6890393 --- /dev/null +++ b/oemcrypto/test/fuzz_tests/sample_test.cc @@ -0,0 +1,10 @@ +#include +#include + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + if (size > 0 && data[0] == 'H') + if (size > 1 && data[1] == 'I') + if (size > 2 && data[2] == '!') + __builtin_trap(); + return 0; +} diff --git a/oemcrypto/test/oec_decrypt_fallback_chain.cpp b/oemcrypto/test/oec_decrypt_fallback_chain.cpp new file mode 100644 index 0000000..fbed992 --- /dev/null +++ b/oemcrypto/test/oec_decrypt_fallback_chain.cpp @@ -0,0 +1,178 @@ +// 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. + +#include "oec_decrypt_fallback_chain.h" + +#include +#include + +#include "oemcrypto_types.h" +#include "string_conversions.h" + +namespace { + +void advance_dest_buffer(OEMCrypto_DestBufferDesc* dest_buffer, size_t bytes) { + switch (dest_buffer->type) { + case OEMCrypto_BufferType_Clear: + dest_buffer->buffer.clear.address += bytes; + dest_buffer->buffer.clear.address_length -= bytes; + break; + + case OEMCrypto_BufferType_Secure: + dest_buffer->buffer.secure.offset += bytes; + break; + + case OEMCrypto_BufferType_Direct: + // Nothing to do for this buffer type. + break; + } +} + +void advance_iv_ctr(uint8_t (*subsample_iv)[wvoec::KEY_IV_SIZE], size_t bytes) { + uint64_t counter; + constexpr size_t half_iv_size = wvoec::KEY_IV_SIZE / 2; + memcpy(&counter, &(*subsample_iv)[half_iv_size], half_iv_size); + + const size_t increment = + bytes / wvoec::AES_128_BLOCK_SIZE; // The truncation here is intentional + counter = wvutil::htonll64(wvutil::ntohll64(counter) + increment); + + memcpy(&(*subsample_iv)[half_iv_size], &counter, half_iv_size); +} + +} // namespace + +namespace wvoec { + +// Decrypts the given array of samples. Handles fallback behavior correctly if +// the OEMCrypto implementation does not accept multiple samples. +OEMCryptoResult DecryptFallbackChain::Decrypt( + OEMCrypto_SESSION session_id, const OEMCrypto_SampleDescription* samples, + size_t samples_length, OEMCryptoCipherMode cipher_mode, + const OEMCrypto_CENCEncryptPatternDesc* pattern) { + OEMCryptoResult sts = + OEMCrypto_DecryptCENC(session_id, samples, samples_length, pattern); + + // No need for a fallback. Abort early. + if (sts != OEMCrypto_ERROR_BUFFER_TOO_LARGE) return sts; + + // Fall back to decrypting individual samples. + for (size_t i = 0; i < samples_length; ++i) { + sts = DecryptSample(session_id, samples[i], cipher_mode, pattern); + if (sts != OEMCrypto_SUCCESS) return sts; + } + + return sts; +} + +// Decrypts the given sample. Handles fallback behavior correctly if the +// OEMCrypto implementation does not accept full samples. +OEMCryptoResult DecryptFallbackChain::DecryptSample( + OEMCrypto_SESSION session_id, const OEMCrypto_SampleDescription& sample, + OEMCryptoCipherMode cipher_mode, + const OEMCrypto_CENCEncryptPatternDesc* pattern) { + OEMCryptoResult sts = OEMCrypto_DecryptCENC(session_id, &sample, 1, pattern); + + // No need for a fallback. Abort early. + if (sts != OEMCrypto_ERROR_BUFFER_TOO_LARGE) return sts; + + // Fall back to decrypting individual subsamples. + OEMCrypto_SampleDescription fake_sample = sample; + for (size_t i = 0; i < sample.subsamples_length; ++i) { + const OEMCrypto_SubSampleDescription& subsample = sample.subsamples[i]; + + const size_t length = + subsample.num_bytes_clear + subsample.num_bytes_encrypted; + fake_sample.buffers.input_data_length = length; + fake_sample.subsamples = &subsample; + fake_sample.subsamples_length = 1; + + sts = DecryptSubsample(session_id, fake_sample, pattern); + if (sts != OEMCrypto_SUCCESS) return sts; + + fake_sample.buffers.input_data += length; + advance_dest_buffer(&fake_sample.buffers.output_descriptor, length); + if (cipher_mode == OEMCrypto_CipherMode_CTR) { + advance_iv_ctr(&fake_sample.iv, + subsample.block_offset + subsample.num_bytes_encrypted); + } + } + + return sts; +} + +// Decrypts the given subsample. Handles fallback behavior correctly if the +// OEMCrypto implementation does not accept full subsamples. +OEMCryptoResult DecryptFallbackChain::DecryptSubsample( + OEMCrypto_SESSION session_id, const OEMCrypto_SampleDescription& sample, + const OEMCrypto_CENCEncryptPatternDesc* pattern) { + OEMCryptoResult sts = OEMCrypto_DecryptCENC(session_id, &sample, 1, pattern); + + // No need for a fallback. Abort early. + if (sts != OEMCrypto_ERROR_BUFFER_TOO_LARGE) return sts; + + // Fall back to decrypting individual subsample halves. + const OEMCrypto_SubSampleDescription& subsample = sample.subsamples[0]; + OEMCrypto_SampleDescription fake_sample = sample; + OEMCrypto_SubSampleDescription fake_subsample; + fake_sample.subsamples = &fake_subsample; + fake_sample.subsamples_length = 1; + + if (subsample.num_bytes_clear > 0) { + fake_sample.buffers.input_data_length = subsample.num_bytes_clear; + fake_subsample.num_bytes_clear = subsample.num_bytes_clear; + fake_subsample.num_bytes_encrypted = 0; + fake_subsample.block_offset = 0; + + fake_subsample.subsample_flags = 0; + if (subsample.subsample_flags & OEMCrypto_FirstSubsample) + fake_subsample.subsample_flags |= OEMCrypto_FirstSubsample; + if (subsample.subsample_flags & OEMCrypto_LastSubsample && + subsample.num_bytes_encrypted == 0) + fake_subsample.subsample_flags |= OEMCrypto_LastSubsample; + + sts = DecryptSubsampleHalf(session_id, fake_sample, pattern); + if (sts != OEMCrypto_SUCCESS) return sts; + + // Advance the buffers for the other half, in case they're needed. + fake_sample.buffers.input_data += subsample.num_bytes_clear; + advance_dest_buffer(&fake_sample.buffers.output_descriptor, + subsample.num_bytes_clear); + } + + if (subsample.num_bytes_encrypted > 0) { + fake_sample.buffers.input_data_length = subsample.num_bytes_encrypted; + fake_subsample.num_bytes_clear = 0; + fake_subsample.num_bytes_encrypted = subsample.num_bytes_encrypted; + fake_subsample.block_offset = subsample.block_offset; + + fake_subsample.subsample_flags = 0; + if (subsample.subsample_flags & OEMCrypto_FirstSubsample && + subsample.num_bytes_clear == 0) + fake_subsample.subsample_flags |= OEMCrypto_FirstSubsample; + if (subsample.subsample_flags & OEMCrypto_LastSubsample) + fake_subsample.subsample_flags |= OEMCrypto_LastSubsample; + + sts = DecryptSubsampleHalf(session_id, fake_sample, pattern); + if (sts != OEMCrypto_SUCCESS) return sts; + } + + return sts; +} + +// Decrypts the given subsample half. There is no fallback behavior after this; +// an OEMCrypto_ERROR_BUFFER_TOO_LARGE produced here will be returned to the +// caller. +OEMCryptoResult DecryptFallbackChain::DecryptSubsampleHalf( + OEMCrypto_SESSION session_id, const OEMCrypto_SampleDescription& sample, + const OEMCrypto_CENCEncryptPatternDesc* pattern) { + return OEMCrypto_DecryptCENC(session_id, &sample, 1, pattern); + // In a real CDM, you would want some fallback here to handle the case where + // the buffer is too big for the OEMCrypto implementation. But in the case of + // the tests, we won't be passing a buffer that's too big unless we are trying + // to test that failure condition, so there's no need to handle that case + // here. +} + +} // namespace wvoec diff --git a/oemcrypto/test/oec_decrypt_fallback_chain.h b/oemcrypto/test/oec_decrypt_fallback_chain.h new file mode 100644 index 0000000..f2500fa --- /dev/null +++ b/oemcrypto/test/oec_decrypt_fallback_chain.h @@ -0,0 +1,59 @@ +// 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 CDM_OEC_DECRYPT_FALLBACK_CHAIN_H_ +#define CDM_OEC_DECRYPT_FALLBACK_CHAIN_H_ + +#include "OEMCryptoCAS.h" +#include "disallow_copy_and_assign.h" + +namespace wvoec { + +// This class groups static methods relating to providing proper fallback +// behavior when calling DecryptCENC in OEMCrypto v16. Outside code can leverage +// this behavior by passing the samples to be decrypted to Decrypt(), which will +// set off the chain of fallback functions as needed. +// +// The behavior of this class is pathological. For each block of data, it will +// greedily try every possible way of passing data to OEMCrypto until one works. +// In the order tried, the ways to send data are: +// 1) Multiple Samples at once +// 2) Individual Samples one at a time +// 3) Individual Subsamples one at a time +// 4) Individual Half-Subsamples one at a time +// On a device that only accepts half-subsamples, the way OEMCrypto v15 did, +// this results in many needless roundtrips to OEMCrypto. This would be +// inefficient behavior for a real CDM, but for the sake of testing, we want to +// use the maximal way the OEMCrypto implementation will accept the data. And, +// for implementations that do not accept multiple samples or subsamples per +// call, we want to test that they correctly reject larger calls. +class DecryptFallbackChain { + public: + static OEMCryptoResult Decrypt( + OEMCrypto_SESSION session_id, const OEMCrypto_SampleDescription* samples, + size_t samples_length, OEMCryptoCipherMode cipher_mode, + const OEMCrypto_CENCEncryptPatternDesc* pattern); + + private: + static OEMCryptoResult DecryptSample( + OEMCrypto_SESSION session_id, const OEMCrypto_SampleDescription& sample, + OEMCryptoCipherMode cipher_mode, + const OEMCrypto_CENCEncryptPatternDesc* pattern); + + static OEMCryptoResult DecryptSubsample( + OEMCrypto_SESSION session_id, const OEMCrypto_SampleDescription& sample, + const OEMCrypto_CENCEncryptPatternDesc* pattern); + + static OEMCryptoResult DecryptSubsampleHalf( + OEMCrypto_SESSION session_id, const OEMCrypto_SampleDescription& sample, + const OEMCrypto_CENCEncryptPatternDesc* pattern); + + // There is no reason to have an instance of this class. + DecryptFallbackChain() = delete; + CORE_DISALLOW_COPY_AND_ASSIGN(DecryptFallbackChain); +}; + +} // namespace wvoec + +#endif // CDM_OEC_DECRYPT_FALLBACK_CHAIN_H_ diff --git a/oemcrypto/test/oec_device_features.cpp b/oemcrypto/test/oec_device_features.cpp new file mode 100644 index 0000000..cc7a319 --- /dev/null +++ b/oemcrypto/test/oec_device_features.cpp @@ -0,0 +1,260 @@ +// 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. +// +// OEMCrypto device features for unit tests +// +#include "oec_device_features.h" + +#include + +#include + +#include "oec_test_data.h" +#include "test_sleep.h" + +namespace wvoec { + +DeviceFeatures global_features; + +void DeviceFeatures::Initialize() { + if (initialized_) return; + uses_keybox = false; + loads_certificate = false; + generic_crypto = false; + usage_table = false; + supports_rsa_3072 = false; + api_version = 0; + derive_key_method = NO_METHOD; + OEMCrypto_SetSandbox(kTestSandbox, sizeof(kTestSandbox)); + if (OEMCrypto_SUCCESS != OEMCrypto_Initialize()) { + printf("OEMCrypto_Initialize failed. All tests will fail.\n"); + return; + } + uint8_t buffer[1]; + uint8_t iv[16] = {}; + size_t size = 0; + provisioning_method = OEMCrypto_GetProvisioningMethod(); + printf("provisioning_method = %s\n", + ProvisioningMethodName(provisioning_method)); + uses_keybox = + (OEMCrypto_ERROR_NOT_IMPLEMENTED != OEMCrypto_GetKeyData(buffer, &size)); + printf("uses_keybox = %s.\n", uses_keybox ? "true" : "false"); + OEMCrypto_SESSION session; + OEMCryptoResult result = OEMCrypto_OpenSession(&session); + if (result != OEMCrypto_SUCCESS) { + printf("--- ERROR: Could not open session: %d ----\n", result); + } + // If the device uses a keybox, check to see if loading a certificate is + // installed. + if (provisioning_method == OEMCrypto_Keybox || + provisioning_method == OEMCrypto_OEMCertificate) { + // Devices with a keybox or OEM Certificate are required to support loading + // a DRM certificate. + loads_certificate = true; + } else { + // Other devices are either broken, or they have a baked in certificate. + loads_certificate = false; + } + printf("loads_certificate = %s.\n", loads_certificate ? "true" : "false"); + generic_crypto = + (OEMCrypto_ERROR_NOT_IMPLEMENTED != + OEMCrypto_Generic_Encrypt(session, buffer, 0, iv, + OEMCrypto_AES_CBC_128_NO_PADDING, buffer)); + printf("generic_crypto = %s.\n", generic_crypto ? "true" : "false"); + OEMCrypto_CloseSession(session); + api_version = OEMCrypto_APIVersion(); + printf("api_version = %d.\n", api_version); + // These unit tests only work with new usage tables. We do not test v12 + // usage tables. + if (api_version > 12) usage_table = OEMCrypto_SupportsUsageTable(); + printf("usage_table = %s.\n", usage_table ? "true" : "false"); + PickDerivedKey(); + if (api_version >= 13) { + uint32_t supported_cert = OEMCrypto_SupportedCertificates(); + if (supported_cert & OEMCrypto_Supports_RSA_CAST) { + cast_receiver = true; + } + if (supported_cert & OEMCrypto_Supports_RSA_3072bit) { + supports_rsa_3072 = true; + } + } + printf("cast_receiver = %s.\n", cast_receiver ? "true" : "false"); + resource_rating = OEMCrypto_ResourceRatingTier(); + printf("resource_rating = %d, security level %s.\n", resource_rating, + OEMCrypto_SecurityLevel()); + uint32_t decrypt_hash_type = OEMCrypto_SupportsDecryptHash(); + supports_crc = (decrypt_hash_type == OEMCrypto_CRC_Clear_Buffer); + if (supports_crc) { + printf("Decrypt hashes will be tested.\n"); + } else { + printf("Decrypt hashes will not be tested -- %s.\n", + decrypt_hash_type == OEMCrypto_Hash_Not_Supported + ? "not supported" + : "partner defined hash"); + } + switch (derive_key_method) { + case NO_METHOD: + printf("NO_METHOD: Cannot derive known session keys.\n"); + // Note: cast_receiver left unchanged because set by user on command line. + uses_keybox = false; + loads_certificate = false; + generic_crypto = false; + usage_table = false; + break; + case LOAD_TEST_KEYBOX: + printf("LOAD_TEST_KEYBOX: Call LoadTestKeybox before deriving keys.\n"); + break; + case LOAD_TEST_RSA_KEY: + printf("LOAD_TEST_RSA_KEY: Call LoadTestRSAKey before deriving keys.\n"); + break; + case TEST_PROVISION_30: + printf("TEST_PROVISION_30: Device provisioed with OEM Cert.\n"); + break; + } + std::string security_level = OEMCrypto_SecurityLevel(); + supports_level_1 = (security_level == "L1"); + printf("SecurityLevel is %s (%s)\n", + supports_level_1 ? "Level 1" : "Not Level 1", + security_level.c_str()); + CheckSecureBuffers(); + OEMCrypto_Terminate(); + initialized_ = true; +} + +std::string DeviceFeatures::RestrictFilter(const std::string& initial_filter) { + std::string filter = initial_filter; + // clang-format off + if (!uses_keybox) FilterOut(&filter, "*KeyboxTest*"); + if (!loads_certificate) FilterOut(&filter, "OEMCryptoLoadsCert*"); + if (!generic_crypto) FilterOut(&filter, "*GenericCrypto*"); + if (!cast_receiver) FilterOut(&filter, "*CastReceiver*"); + if (!usage_table) FilterOut(&filter, "*UsageTable*"); + if (derive_key_method == NO_METHOD) FilterOut(&filter, "*SessionTest*"); + if (provisioning_method + != OEMCrypto_OEMCertificate) FilterOut(&filter, "*Prov30*"); + if (!supports_rsa_3072) FilterOut(&filter, "*RSAKey3072*"); + if (api_version < 9) FilterOut(&filter, "*API09*"); + if (api_version < 10) FilterOut(&filter, "*API10*"); + if (api_version < 11) FilterOut(&filter, "*API11*"); + if (api_version < 12) FilterOut(&filter, "*API12*"); + if (api_version < 13) FilterOut(&filter, "*API13*"); + if (api_version < 14) FilterOut(&filter, "*API14*"); + if (api_version < 15) FilterOut(&filter, "*API15*"); + if (api_version < 16) FilterOut(&filter, "*API16*"); + // clang-format on + // Some tests may require root access. If user is not root, filter these tests + // out. + if (!wvutil::TestSleep::CanChangeSystemTime()) { + printf("Filtering out TimeRollbackPrevention.\n"); + FilterOut(&filter, "*TimeRollbackPrevention*"); + } else { + printf("Can change time. I will run TimeRollbackPrevention.\n"); + } + // Performance tests take a long time. Filter them out if they are not + // specifically requested. + if (filter.find("Performance") == std::string::npos) { + FilterOut(&filter, "*Performance*"); + } + return filter; +} + +void DeviceFeatures::PickDerivedKey() { + if (api_version >= 12) { + switch (provisioning_method) { + case OEMCrypto_OEMCertificate: + derive_key_method = TEST_PROVISION_30; + return; + case OEMCrypto_DrmCertificate: + if (OEMCrypto_ERROR_NOT_IMPLEMENTED != OEMCrypto_LoadTestRSAKey()) { + derive_key_method = LOAD_TEST_RSA_KEY; + } + return; + case OEMCrypto_Keybox: + // Fall through to api_version < 12 case. + break; + case OEMCrypto_ProvisioningError: + printf( + "ERROR: OEMCrypto_GetProvisioningMethod() returns " + "OEMCrypto_ProvisioningError\n"); + // Then fall through to api_version < 12 case. + break; + } + } + if (uses_keybox) { + // If device uses a keybox, try to load the test keybox. + if (OEMCrypto_ERROR_NOT_IMPLEMENTED != + OEMCrypto_LoadTestKeybox(nullptr, 0)) { + derive_key_method = LOAD_TEST_KEYBOX; + } + } else if (OEMCrypto_ERROR_NOT_IMPLEMENTED != OEMCrypto_LoadTestRSAKey()) { + derive_key_method = LOAD_TEST_RSA_KEY; + } +} + +void DeviceFeatures::CheckSecureBuffers() { + output_types_.push_back({false, OEMCrypto_BufferType_Clear}); + output_types_.push_back({true, OEMCrypto_BufferType_Clear}); + test_secure_buffers = false; + OEMCrypto_SESSION session; + OEMCryptoResult result = OEMCrypto_OpenSession(&session); + if (result != OEMCrypto_SUCCESS) { + printf("--- ERROR: Could not open session: %d ----\n", result); + return; + } + OEMCrypto_DestBufferDesc output_descriptor; + output_descriptor.type = OEMCrypto_BufferType_Secure; + int secure_fd; + result = OEMCrypto_AllocateSecureBuffer(session, 42, &output_descriptor, + &secure_fd); + if (result == OEMCrypto_ERROR_NOT_IMPLEMENTED) { + printf("Secure buffers will not be tested\n"); + return; + } + if (result != OEMCrypto_SUCCESS) { + printf("--- ERROR: Could not create secure buffer: %d ----\n", result); + return; + } + result = OEMCrypto_FreeSecureBuffer(session, &output_descriptor, secure_fd); + if (result != OEMCrypto_SUCCESS) { + printf("--- ERROR: Could not free secure buffer: %d ----\n", result); + return; + } + printf("Secure buffers will be tested\n"); + output_types_.push_back({false, OEMCrypto_BufferType_Secure}); + test_secure_buffers = true; +} + +void DeviceFeatures::FilterOut(std::string* current_filter, + const std::string& new_filter) { + if (current_filter->find('-') == std::string::npos) { + *current_filter += "-" + new_filter; + } else { + *current_filter += ":" + new_filter; + } +} + +// Return the list of output types for the decrypt tests. +const std::vector& DeviceFeatures::GetOutputTypes() { + if (!initialized_) { + Initialize(); + } + return output_types_; +} + +const char* ProvisioningMethodName(OEMCrypto_ProvisioningMethod method) { + switch (method) { + case OEMCrypto_ProvisioningError: + return "OEMCrypto_ProvisioningError"; + case OEMCrypto_DrmCertificate: + return "OEMCrypto_DrmCertificate"; + case OEMCrypto_Keybox: + return "OEMCrypto_Keybox"; + case OEMCrypto_OEMCertificate: + return "OEMCrypto_OEMCertificate"; + } + // Not reachable + return ""; +} + +} // namespace wvoec diff --git a/oemcrypto/test/oec_device_features.h b/oemcrypto/test/oec_device_features.h new file mode 100644 index 0000000..7f793ab --- /dev/null +++ b/oemcrypto/test/oec_device_features.h @@ -0,0 +1,88 @@ +#ifndef CDM_OEC_DEVICE_FEATURES_H_ +#define CDM_OEC_DEVICE_FEATURES_H_ + +#include +#include + +#include "OEMCryptoCAS.h" +#include "oemcrypto_types.h" + +namespace wvoec { + +// These tests are designed to work for this version: +constexpr unsigned int kCurrentAPI = 16; +// The API version when Core Messages were introduced. +constexpr unsigned int kCoreMessagesAPI = 16; + +// An output type for testing. The type field is secure, clear, or direct. If +// the type is clear, then decrypt_inplace could be true. Otherwise, +// decrypt_inplace is false. +struct OutputType { + bool decrypt_inplace; + OEMCryptoBufferType type; +}; + +// Keeps track of which features are supported by the version of OEMCrypto being +// tested. See the integration guide for a list of optional features. +class DeviceFeatures { + public: + // There are several possible methods used to derive a set of known session + // keys. For example, the test can install a known test keybox, or it can + // parse the OEM certificate. + enum DeriveMethod { // Method to use derive session keys. + NO_METHOD, // Cannot derive known session keys. + LOAD_TEST_KEYBOX, // Call LoadTestKeybox before deriving keys. + LOAD_TEST_RSA_KEY, // Call LoadTestRSAKey before deriving keys. + TEST_PROVISION_30, // Device has OEM Certificate installed. + }; + + enum DeriveMethod derive_key_method; + bool uses_keybox; // Device uses a keybox to derive session keys. + bool loads_certificate; // Device can load a certificate from the server. + bool generic_crypto; // Device supports generic crypto. + bool cast_receiver; // Device supports alternate rsa signature padding. + bool usage_table; // Device saves usage information. + bool supports_rsa_3072; // Device supports 3072 bit RSA keys. + bool supports_level_1; // Device supports Level 1 security. + uint32_t resource_rating; // Device's resource rating tier. + bool supports_crc; // Supported decrypt hash type CRC. + bool test_secure_buffers; // If we can create a secure buffer for testing. + uint32_t api_version; + OEMCrypto_ProvisioningMethod provisioning_method; + + // This should be called from the test program's main procedure. + void Initialize(); + void set_cast_receiver(bool is_cast_receiver) { + cast_receiver = is_cast_receiver; + } + // Generate a GTest filter of tests that should not be run. This should be + // called after Initialize. Tests are filtered out based on which features + // are not supported. For example, a device that uses Provisioning 3.0 will + // have all keybox tests filtered out. + std::string RestrictFilter(const std::string& initial_filter); + + // Get a list of output types that should be tested. + const std::vector& GetOutputTypes(); + + private: + // Decide which method should be used to derive session keys, based on + // supported featuers. + void PickDerivedKey(); + // Decide if secure buffers can be created, and initialize output_types_. + void CheckSecureBuffers(); + // Add a GTest filter restriction to the current filter. + void FilterOut(std::string* current_filter, const std::string& new_filter); + + // A list of possible output types. + std::vector output_types_; + bool initialized_ = false; +}; + +// There is one global set of features for the version of OEMCrypto being +// tested. This should be initialized in the test program's main procedure. +extern DeviceFeatures global_features; +const char* ProvisioningMethodName(OEMCrypto_ProvisioningMethod method); + +} // namespace wvoec + +#endif // CDM_OEC_DEVICE_FEATURES_H_ diff --git a/oemcrypto/test/oec_key_deriver.cpp b/oemcrypto/test/oec_key_deriver.cpp new file mode 100644 index 0000000..2a31dd7 --- /dev/null +++ b/oemcrypto/test/oec_key_deriver.cpp @@ -0,0 +1,168 @@ +// 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. +// +// OEMCrypto unit tests +// + +#include "oec_session_util.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "OEMCryptoCAS.h" +#include "disallow_copy_and_assign.h" +#include "log.h" +#include "oec_device_features.h" +#include "oec_test_data.h" +#include "oemcrypto_types.h" +#include "platform.h" +#include "string_conversions.h" + +using namespace std; + +namespace wvoec { + +void Encryptor::set_enc_key(const std::vector& enc_key) { + enc_key_ = enc_key; +} + +void Encryptor::CBCEncrypt(const uint8_t* data, uint8_t* encrypted_data, + size_t data_length, + const uint8_t (&iv)[KEY_IV_SIZE]) const { + ASSERT_EQ(enc_key_.size(), KEY_SIZE); + ASSERT_NE(data, nullptr); + ASSERT_NE(encrypted_data, nullptr); + AES_KEY aes_key; + static const int key_size = KEY_SIZE * 8; // in bits. + AES_set_encrypt_key(enc_key_.data(), key_size, &aes_key); + uint8_t iv_buffer[KEY_IV_SIZE]; + memcpy(iv_buffer, iv, KEY_IV_SIZE); + AES_cbc_encrypt(data, encrypted_data, data_length, &aes_key, iv_buffer, + AES_ENCRYPT); +} + +void Encryptor::PadAndEncryptProvisioningMessage( + RSAPrivateKeyMessage* data, RSAPrivateKeyMessage* encrypted) const { + EXPECT_EQ(1, GetRandBytes(data->rsa_key_iv, KEY_IV_SIZE)); + ASSERT_EQ(enc_key_.size(), KEY_SIZE); + *encrypted = *data; + size_t padding = AES_BLOCK_SIZE - (data->rsa_key_length % AES_BLOCK_SIZE); + memset(data->rsa_key + data->rsa_key_length, static_cast(padding), + padding); + encrypted->rsa_key_length = data->rsa_key_length + padding; + AES_KEY aes_key; + static const int key_size = KEY_SIZE * 8; // in bits. + AES_set_encrypt_key(enc_key_.data(), key_size, &aes_key); + uint8_t iv_buffer[KEY_IV_SIZE]; + memcpy(iv_buffer, &data->rsa_key_iv[0], KEY_IV_SIZE); + AES_cbc_encrypt(&data->rsa_key[0], &encrypted->rsa_key[0], + encrypted->rsa_key_length, &aes_key, iv_buffer, AES_ENCRYPT); +} + +// This generates the data for deriving one key. If there are failures in +// this function, then there is something wrong with the test program and its +// dependency on BoringSSL. +void KeyDeriver::DeriveKey(const uint8_t* key, const vector& context, + int counter, vector* out) { + ASSERT_NE(key, nullptr); + ASSERT_FALSE(context.empty()); + ASSERT_GE(4, counter); + ASSERT_LE(1, counter); + ASSERT_NE(out, nullptr); + + const EVP_CIPHER* cipher = EVP_aes_128_cbc(); + CMAC_CTX* cmac_ctx = CMAC_CTX_new(); + ASSERT_NE(nullptr, cmac_ctx); + + ASSERT_TRUE(CMAC_Init(cmac_ctx, key, KEY_SIZE, cipher, 0)); + + std::vector message; + message.push_back(static_cast(counter)); + message.insert(message.end(), context.begin(), context.end()); + + ASSERT_TRUE(CMAC_Update(cmac_ctx, message.data(), message.size())); + + size_t reslen; + uint8_t res[128]; + ASSERT_TRUE(CMAC_Final(cmac_ctx, res, &reslen)); + + out->assign(res, res + reslen); + CMAC_CTX_free(cmac_ctx); +} + +// This generates the data for deriving a set of keys. If there are failures in +// this function, then there is something wrong with the test program and its +// dependency on BoringSSL. +void KeyDeriver::DeriveKeys(const uint8_t* master_key, + const vector& mac_key_context, + const vector& enc_key_context) { + // Generate derived key for mac key + std::vector mac_key_part2; + DeriveKey(master_key, mac_key_context, 1, &mac_key_server_); + DeriveKey(master_key, mac_key_context, 2, &mac_key_part2); + mac_key_server_.insert(mac_key_server_.end(), mac_key_part2.begin(), + mac_key_part2.end()); + + DeriveKey(master_key, mac_key_context, 3, &mac_key_client_); + DeriveKey(master_key, mac_key_context, 4, &mac_key_part2); + mac_key_client_.insert(mac_key_client_.end(), mac_key_part2.begin(), + mac_key_part2.end()); + + // Generate derived key for encryption key + std::vector enc_key; + DeriveKey(master_key, enc_key_context, 1, &enc_key); + set_enc_key(enc_key); +} + +void KeyDeriver::set_mac_keys(const uint8_t* mac_keys) { + ASSERT_EQ(mac_key_server_.size(), MAC_KEY_SIZE); + ASSERT_EQ(mac_key_client_.size(), MAC_KEY_SIZE); + memcpy(mac_key_server_.data(), mac_keys, MAC_KEY_SIZE); + memcpy(mac_key_client_.data(), mac_keys + MAC_KEY_SIZE, MAC_KEY_SIZE); +} + +void KeyDeriver::ServerSignBuffer(const uint8_t* data, size_t data_length, + std::vector* signature) const { + ASSERT_EQ(mac_key_server_.size(), MAC_KEY_SIZE); + signature->assign(SHA256_DIGEST_LENGTH, 0); + unsigned int sig_len = SHA256_DIGEST_LENGTH; + ASSERT_TRUE(HMAC(EVP_sha256(), mac_key_server_.data(), mac_key_server_.size(), + data, data_length, signature->data(), &sig_len)); +} + +void KeyDeriver::ClientSignBuffer(const vector& buffer, + std::vector* signature) const { + ASSERT_EQ(mac_key_client_.size(), MAC_KEY_SIZE); + signature->assign(SHA256_DIGEST_LENGTH, 0); + unsigned int sig_len = SHA256_DIGEST_LENGTH; + ASSERT_TRUE(HMAC(EVP_sha256(), mac_key_client_.data(), mac_key_client_.size(), + buffer.data(), buffer.size(), signature->data(), &sig_len)); +} + +void KeyDeriver::ClientSignPstReport(const vector& pst_report_buffer, + std::vector* signature) const { + ASSERT_EQ(mac_key_client_.size(), MAC_KEY_SIZE); + signature->assign(SHA_DIGEST_LENGTH, 0); + unsigned int sig_len = SHA_DIGEST_LENGTH; + ASSERT_TRUE(HMAC(EVP_sha1(), mac_key_client_.data(), mac_key_client_.size(), + &pst_report_buffer[SHA_DIGEST_LENGTH], + pst_report_buffer.size() - SHA_DIGEST_LENGTH, + signature->data(), &sig_len)); +} + +} // namespace wvoec diff --git a/oemcrypto/test/oec_key_deriver.h b/oemcrypto/test/oec_key_deriver.h new file mode 100644 index 0000000..7e05038 --- /dev/null +++ b/oemcrypto/test/oec_key_deriver.h @@ -0,0 +1,93 @@ +// 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 CDM_OEC_KEY_DERIVER_H_ +#define CDM_OEC_KEY_DERIVER_H_ + +#include +#include +#include +#include +#include + +#include "oec_device_features.h" +#include "oemcrypto_types.h" +#include "pst_report.h" + +namespace wvoec { + +constexpr size_t kMaxTestRSAKeyLength = 2000; // Rough estimate. +constexpr size_t kMaxCoreProvRequest = 150; // Rough estimate. + +// This structure will be signed to simulate a provisioning response from the +// server. +struct RSAPrivateKeyMessage { + uint8_t rsa_key[kMaxTestRSAKeyLength]; + uint8_t rsa_key_iv[KEY_IV_SIZE]; + size_t rsa_key_length; + uint8_t enc_message_key[kMaxTestRSAKeyLength]; + size_t enc_message_key_length; + uint32_t nonce; +}; + +// Holds an encryption key and can encrypt a provisioning message. It also can +// encrypt short buffers using CBC, such as content keys in a license. +class Encryptor { + public: + Encryptor() : enc_key_(KEY_SIZE, 0) {} + Encryptor(const std::vector& enc_key) { set_enc_key(enc_key); }; + Encryptor& operator=(const Encryptor&) = default; + void set_enc_key(const std::vector& enc_key); + + // This encrypts an RSAPrivateKeyMessage with encryption_key so that it may be + // loaded with OEMCrypto_LoadProvisioningResponse. + // This modifies the clear data: it adds padding and generates a random iv. + void PadAndEncryptProvisioningMessage(RSAPrivateKeyMessage* data, + RSAPrivateKeyMessage* encrypted) const; + + void CBCEncrypt(const uint8_t* data, uint8_t* encrypted_data, + size_t data_length, const uint8_t (&iv)[KEY_IV_SIZE]) const; + + private: + std::vector enc_key_; +}; + +// Holds encryption and mac keys derived from a master key. +// Can be used to sign a buffer as either a server or client. +class KeyDeriver : public Encryptor { + public: + KeyDeriver() + : mac_key_server_(MAC_KEY_SIZE, 0), mac_key_client_(MAC_KEY_SIZE, 0) {} + KeyDeriver& operator=(const KeyDeriver&) = default; + + // Generate mac and enc keys give the master key. + void DeriveKeys(const uint8_t* master_key, + const std::vector& mac_key_context, + const std::vector& enc_key_context); + // Sign the buffer with server's mac key. + void ServerSignBuffer(const uint8_t* data, size_t data_length, + std::vector* signature) const; + // Sign the buffer with client's known mac key. Known test keys must be + // installed first. This uses HMAC with SHA256, so is suitable for a message. + void ClientSignBuffer(const std::vector& buffer, + std::vector* signature) const; + // Sign the pst buffer with client's known mac key. Known test keys must be + // installed first. This uses HMAC with SHA128, and skips the beginning of the + // buffer, so is only suitable for a pst report. + void ClientSignPstReport(const std::vector& pst_report_buffer, + std::vector* signature) const; + void set_mac_keys(const uint8_t* mac_keys); + + private: + // Internal utility function to derive key using CMAC-128 + void DeriveKey(const uint8_t* key, const std::vector& context, + int counter, std::vector* out); + + std::vector mac_key_server_; + std::vector mac_key_client_; +}; + +} // namespace wvoec + +#endif // CDM_OEC_KEY_DERIVER_H_ diff --git a/oemcrypto/test/oec_session_util.cpp b/oemcrypto/test/oec_session_util.cpp new file mode 100644 index 0000000..d62201f --- /dev/null +++ b/oemcrypto/test/oec_session_util.cpp @@ -0,0 +1,1512 @@ +// 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. +// +// OEMCrypto unit tests +// + +#include "oec_session_util.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "OEMCryptoCAS.h" +#include "clock.h" +#include "core_message_deserialize.h" +#include "core_message_serialize.h" +#include "disallow_copy_and_assign.h" +#include "log.h" +#include "oec_device_features.h" +#include "oec_test_data.h" +#include "oemcrypto_corpus_generator_helper.h" +#include "oemcrypto_types.h" +#include "platform.h" +#include "string_conversions.h" +#include "test_sleep.h" + +using namespace std; + +// GTest requires PrintTo to be in the same namespace as the thing it prints, +// which is std::vector in this case. +namespace std { +void PrintTo(const vector& value, ostream* os) { + *os << wvutil::b2a_hex(value); +} +} // namespace std + +namespace { +constexpr size_t kTestSubsampleSectionSize = 256; +} // namespace + +namespace wvoec { + +int GetRandBytes(unsigned char* buf, int num) { + // returns 1 on success, -1 if not supported, or 0 if other failure. + return RAND_bytes(buf, num); +} + +// Does the boilerplate to fill out sample and subsample descriptions for +// decrypting a single contiguous block of encrypted data to clear memory, which +// is a common operation for tests. Generates a random IV which can be used to +// encrypt the input buffer. +void GenerateSimpleSampleDescription( + const std::vector& in, std::vector& out, + OEMCrypto_SampleDescription* sample, + OEMCrypto_SubSampleDescription* subsample) { + ASSERT_NE(sample, nullptr); + ASSERT_NE(subsample, nullptr); + + // Generate test data + EXPECT_EQ(GetRandBytes(&sample->iv[0], KEY_IV_SIZE), 1); + + // Describe the test data + sample->buffers.input_data = in.data(); + sample->buffers.input_data_length = in.size(); + subsample->num_bytes_clear = 0; + subsample->num_bytes_encrypted = sample->buffers.input_data_length; + subsample->subsample_flags = + OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample; + subsample->block_offset = 0; + sample->subsamples = subsample; + sample->subsamples_length = 1; + + // Describe the output + OEMCrypto_DestBufferDesc& out_buffer_descriptor = + sample->buffers.output_descriptor; + out_buffer_descriptor.type = OEMCrypto_BufferType_Clear; + out_buffer_descriptor.buffer.clear.address = out.data(); + out_buffer_descriptor.buffer.clear.address_length = out.size(); +} + +// Increment counter for AES-CTR. The CENC spec specifies we increment only +// the low 64 bits of the IV counter, and leave the high 64 bits alone. This +// is different from the BoringSSL implementation, so we implement the CTR loop +// ourselves. +void ctr128_inc64(int64_t increaseBy, uint8_t* iv) { + ASSERT_NE(nullptr, iv); + uint64_t* counterBuffer = reinterpret_cast(&iv[8]); + (*counterBuffer) = + wvutil::htonll64(wvutil::ntohll64(*counterBuffer) + increaseBy); +} + +// Some compilers don't like the macro htonl within an ASSERT_EQ. +uint32_t htonl_fnc(uint32_t x) { return htonl(x); } + +void dump_boringssl_error() { + while (unsigned long err = ERR_get_error()) { + char buffer[120]; + ERR_error_string_n(err, buffer, sizeof(buffer)); + cout << "BoringSSL Error -- " << buffer << "\n"; + } +} + +// A smart pointer for BoringSSL objects. It uses the specified free function +// to release resources and free memory when the pointer is deleted. +template +class boringssl_ptr { + public: + explicit boringssl_ptr(T* p = nullptr) : ptr_(p) {} + ~boringssl_ptr() { + if (ptr_) func(ptr_); + } + T& operator*() const { return *ptr_; } + T* operator->() const { return ptr_; } + T* get() const { return ptr_; } + bool NotNull() const { return ptr_; } + + private: + T* ptr_; + CORE_DISALLOW_COPY_AND_ASSIGN(boringssl_ptr); +}; + +Test_PST_Report::Test_PST_Report(const std::string& pst_in, + OEMCrypto_Usage_Entry_Status status_in) + : status(status_in), pst(pst_in) { + time_created = wvutil::Clock().GetCurrentTime(); +} + +template +void RoundTrip::SignAndVerifyRequest() { + // In the real world, a message should be signed by the client and + // verified by the server. This simulates that. + size_t gen_signature_length = 0; + size_t core_message_length = 0; + constexpr size_t small_size = 42; // arbitrary. + size_t message_size = + std::max(required_message_size_, core_message_length + small_size); + vector data(message_size, 0); + for (size_t i = 0; i < data.size(); i++) data[i] = i & 0xFF; + ASSERT_EQ( + PrepAndSignRequest(session()->session_id(), data.data(), data.size(), + &core_message_length, nullptr, &gen_signature_length), + OEMCrypto_ERROR_SHORT_BUFFER); + // Make the message buffer a little bigger than the core message, or the + // required size, whichever is larger. + message_size = + std::max(required_message_size_, core_message_length + small_size); + data.resize(message_size); + for (size_t i = 0; i < data.size(); i++) data[i] = i & 0xFF; + + vector gen_signature(gen_signature_length); + ASSERT_EQ(PrepAndSignRequest(session()->session_id(), data.data(), + data.size(), &core_message_length, + gen_signature.data(), &gen_signature_length), + OEMCrypto_SUCCESS); + if (global_features.api_version >= kCoreMessagesAPI) { + ASSERT_GT(data.size(), core_message_length); + std::string core_message(reinterpret_cast(data.data()), + core_message_length); + FillAndVerifyCoreRequest(core_message); + } + VerifyRequestSignature(data, gen_signature, core_message_length); +} + +template +OEMCrypto_Substring RoundTrip::FindSubstring(const void* pointer, + size_t length) { + OEMCrypto_Substring substring; + if (length == 0 || pointer == nullptr) { + substring.offset = 0; + substring.length = 0; + } else { + substring.offset = reinterpret_cast(pointer) - + reinterpret_cast(&response_data_); + substring.length = length; + } + return substring; +} + +void ProvisioningRoundTrip::PrepareSession( + const wvoec::WidevineKeybox& keybox) { + ASSERT_NO_FATAL_FAILURE(session_->open()); + session_->GenerateNonce(); + if (global_features.provisioning_method == OEMCrypto_Keybox) { + session_->GenerateDerivedKeysFromKeybox(keybox); + encryptor_ = session_->key_deriver(); + } else { + EXPECT_EQ(global_features.provisioning_method, OEMCrypto_OEMCertificate); + session_->LoadOEMCert(true); + session_->GenerateRSASessionKey(&message_key_, &encrypted_message_key_); + encryptor_.set_enc_key(message_key_); + } +} + +void ProvisioningRoundTrip::VerifyRequestSignature( + const vector& data, const vector& generated_signature, + size_t /* core_message_length */) { + if (global_features.provisioning_method == OEMCrypto_OEMCertificate) { + session()->VerifyRSASignature(data, generated_signature.data(), + generated_signature.size(), kSign_RSASSA_PSS); + } else { + EXPECT_EQ(global_features.provisioning_method, OEMCrypto_Keybox); + ASSERT_EQ(HMAC_SHA256_SIGNATURE_SIZE, generated_signature.size()); + std::vector expected_signature; + session()->key_deriver().ClientSignBuffer(data, &expected_signature); + ASSERT_EQ(expected_signature, generated_signature); + } +} + +void ProvisioningRoundTrip::FillAndVerifyCoreRequest( + const std::string& core_message_string) { + EXPECT_TRUE( + oemcrypto_core_message::deserialize::CoreProvisioningRequestFromMessage( + core_message_string, &core_request_)); + EXPECT_EQ(global_features.api_version, core_request_.api_major_version); + EXPECT_EQ(session()->nonce(), core_request_.nonce); + EXPECT_EQ(session()->session_id(), core_request_.session_id); + size_t device_id_length = core_request_.device_id.size(); + std::vector device_id(device_id_length); + EXPECT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GetDeviceID(device_id.data(), &device_id_length)); + EXPECT_EQ(core_request_.device_id.size(), device_id_length); + std::string device_id_string(reinterpret_cast(device_id.data()), + device_id_length); + EXPECT_EQ(device_id_string, core_request_.device_id); +} + +void ProvisioningRoundTrip::CreateDefaultResponse() { + if (allowed_schemes_ != kSign_RSASSA_PSS) { + uint32_t algorithm_n = htonl(allowed_schemes_); + memcpy(response_data_.rsa_key, "SIGN", 4); + memcpy(response_data_.rsa_key + 4, &algorithm_n, 4); + memcpy(response_data_.rsa_key + 8, encoded_rsa_key_.data(), + encoded_rsa_key_.size()); + response_data_.rsa_key_length = 8 + encoded_rsa_key_.size(); + } else { + memcpy(response_data_.rsa_key, encoded_rsa_key_.data(), + encoded_rsa_key_.size()); + response_data_.rsa_key_length = encoded_rsa_key_.size(); + } + response_data_.nonce = session_->nonce(); + if (encrypted_message_key_.size() > 0) { + ASSERT_LE(encrypted_message_key_.size(), kMaxTestRSAKeyLength); + memcpy(response_data_.enc_message_key, encrypted_message_key_.data(), + encrypted_message_key_.size()); + response_data_.enc_message_key_length = encrypted_message_key_.size(); + } else { + response_data_.enc_message_key_length = 0; + } + core_response_.key_type = OEMCrypto_RSA_Private_Key; + core_response_.enc_private_key = + FindSubstring(response_data_.rsa_key, response_data_.rsa_key_length); + core_response_.enc_private_key_iv = FindSubstring( + response_data_.rsa_key_iv, sizeof(response_data_.rsa_key_iv)); + core_response_.encrypted_message_key = FindSubstring( + response_data_.enc_message_key, response_data_.enc_message_key_length); +} + +void ProvisioningRoundTrip::EncryptAndSignResponse() { + encryptor_.PadAndEncryptProvisioningMessage(&response_data_, + &encrypted_response_data_); + core_response_.enc_private_key.length = + encrypted_response_data_.rsa_key_length; + if (global_features.api_version >= kCoreMessagesAPI) { + ASSERT_TRUE( + oemcrypto_core_message::serialize::CreateCoreProvisioningResponse( + core_response_, core_request_, &serialized_core_message_)); + } + // Make the message buffer a just big enough, or the + // required size, whichever is larger. + const size_t message_size = + std::max(required_message_size_, serialized_core_message_.size() + + sizeof(encrypted_response_data_)); + // Stripe the encrypted message. + encrypted_response_.resize(message_size); + for (size_t i = 0; i < encrypted_response_.size(); i++) { + encrypted_response_[i] = i & 0xFF; + } + ASSERT_GE(encrypted_response_.size(), serialized_core_message_.size()); + memcpy(encrypted_response_.data(), serialized_core_message_.data(), + serialized_core_message_.size()); + ASSERT_GE(encrypted_response_.size(), + serialized_core_message_.size() + sizeof(encrypted_response_data_)); + memcpy(encrypted_response_.data() + serialized_core_message_.size(), + reinterpret_cast(&encrypted_response_data_), + sizeof(encrypted_response_data_)); + if (global_features.provisioning_method == OEMCrypto_OEMCertificate) { + session()->GenerateDerivedKeysFromSessionKey(); + } + session()->key_deriver().ServerSignBuffer(encrypted_response_.data(), + encrypted_response_.size(), + &response_signature_); +} + +OEMCryptoResult ProvisioningRoundTrip::LoadResponse(Session* session) { + EXPECT_NE(session, nullptr); + size_t wrapped_key_length = 0; + const OEMCryptoResult sts = LoadResponseNoRetry(session, &wrapped_key_length); + if (sts != OEMCrypto_ERROR_SHORT_BUFFER) return sts; + wrapped_rsa_key_.clear(); + wrapped_rsa_key_.assign(wrapped_key_length, 0); + return LoadResponseNoRetry(session, &wrapped_key_length); +} + +#ifdef TEST_OEMCRYPTO_V15 +// If this platform supports v15 functions, then will test with them: +# define OEMCrypto_RewrapDeviceRSAKey_V15 OEMCrypto_RewrapDeviceRSAKey +# define OEMCrypto_RewrapDeviceRSAKey30_V15 OEMCrypto_RewrapDeviceRSAKey30 + +#else +// If this platform does not support v15 functions, we just need to stub these +// out so that the tests compile. +OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey30_V15( + OEMCrypto_SESSION, const uint32_t*, const uint8_t*, size_t, const uint8_t*, + size_t, const uint8_t*, uint8_t*, size_t*) { + LOGE("Support for v15 functions not included. Define TEST_OEMCRYPTO_V15."); + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey_V15( + OEMCrypto_SESSION, const uint8_t*, size_t, const uint8_t*, size_t, + const uint32_t*, const uint8_t*, size_t, const uint8_t*, uint8_t*, + size_t*) { + LOGE("Support for v15 functions not included. Define TEST_OEMCRYPTO_V15."); + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} +#endif + +template +const T* ProvisioningRoundTrip::RemapPointer(const T* response_pointer) const { + const uint8_t* original_pointer = + reinterpret_cast(response_pointer); + size_t delta = + original_pointer - reinterpret_cast(&response_data_); + // Base offset should be 0 if this is a v15 message, which is the only time + // this function is called. + size_t base_offset = serialized_core_message_.size(); + const uint8_t* new_pointer = encrypted_response_.data() + delta + base_offset; + return reinterpret_cast(new_pointer); +} + +OEMCryptoResult ProvisioningRoundTrip::LoadResponseNoRetry( + Session* session, size_t* wrapped_key_length) { + EXPECT_NE(session, nullptr); + if (global_features.api_version >= kCoreMessagesAPI) { + return OEMCrypto_LoadProvisioning( + session->session_id(), encrypted_response_.data(), + encrypted_response_.size(), serialized_core_message_.size(), + response_signature_.data(), response_signature_.size(), + wrapped_rsa_key_.data(), wrapped_key_length); + } else if (global_features.provisioning_method == OEMCrypto_Keybox) { + return OEMCrypto_RewrapDeviceRSAKey_V15( + session->session_id(), encrypted_response_.data(), + encrypted_response_.size(), response_signature_.data(), + response_signature_.size(), RemapPointer(&response_data_.nonce), + RemapPointer(response_data_.rsa_key), + encrypted_response_data_.rsa_key_length, + RemapPointer(response_data_.rsa_key_iv), wrapped_rsa_key_.data(), + wrapped_key_length); + } else { + return OEMCrypto_RewrapDeviceRSAKey30_V15( + session->session_id(), &encrypted_response_data_.nonce, + RemapPointer(response_data_.enc_message_key), + response_data_.enc_message_key_length, + RemapPointer(response_data_.rsa_key), + encrypted_response_data_.rsa_key_length, + RemapPointer(response_data_.rsa_key_iv), wrapped_rsa_key_.data(), + wrapped_key_length); + } +} + +void ProvisioningRoundTrip::VerifyLoadFailed() { + if (wrapped_rsa_key_.size() == 0) return; + std::vector zero(wrapped_rsa_key_.size(), 0); + ASSERT_EQ(zero, wrapped_rsa_key_); +} + +void LicenseRoundTrip::VerifyRequestSignature( + const vector& data, const vector& generated_signature, + size_t core_message_length) { + const std::vector subdata(data.begin() + core_message_length, + data.end()); + session()->VerifyRSASignature(subdata, generated_signature.data(), + generated_signature.size(), kSign_RSASSA_PSS); + SHA256(data.data(), core_message_length, request_hash_); + // If the api version was not set by the test, then we record the api version + // from the request. Also, if the api was set to be higher than oemcrypto + // supports, then we lower it. This version will be used in the response. + if (api_version_ == 0) api_version_ = core_request_.api_major_version; + if (api_version_ > global_features.api_version) + api_version_ = global_features.api_version; +} + +void LicenseRoundTrip::FillAndVerifyCoreRequest( + const std::string& core_message_string) { + EXPECT_TRUE( + oemcrypto_core_message::deserialize::CoreLicenseRequestFromMessage( + core_message_string, &core_request_)); + EXPECT_EQ(global_features.api_version, core_request_.api_major_version); + // If we are testing the latest OEMCrypto version, make sure it is built with + // the latest ODK version, too: + if (global_features.api_version == ODK_MAJOR_VERSION) { + EXPECT_EQ(ODK_MINOR_VERSION, core_request_.api_minor_version); + } + if (expect_request_has_correct_nonce_) { + EXPECT_EQ(session()->nonce(), core_request_.nonce); + } + EXPECT_EQ(session()->session_id(), core_request_.session_id); +} + +void LicenseRoundTrip::CreateDefaultResponse() { + EXPECT_EQ(1, GetRandBytes(response_data_.mac_key_iv, + sizeof(response_data_.mac_key_iv))); + memset(response_data_.padding, 0, sizeof(response_data_.padding)); + EXPECT_EQ(1, GetRandBytes(response_data_.mac_keys, + sizeof(response_data_.mac_keys))); + // For backwards compatibility, we use the largest limit in timer_limits for + // each key's duration. + uint32_t key_duration = static_cast( + std::max({core_response_.timer_limits.rental_duration_seconds, + core_response_.timer_limits.total_playback_duration_seconds, + core_response_.timer_limits.initial_renewal_duration_seconds})); + // The key data for an entitlement license is an AES-256 key, otherwise the + // default is an AES_128 key. + uint32_t default_key_size = + (license_type_ == OEMCrypto_EntitlementLicense) ? KEY_SIZE * 2 : KEY_SIZE; + for (unsigned int i = 0; i < num_keys_; i++) { + memset(response_data_.keys[i].key_id, 0, kTestKeyIdMaxLength); + response_data_.keys[i].key_id_length = kDefaultKeyIdLength; + memset(response_data_.keys[i].key_id, i, + response_data_.keys[i].key_id_length); + EXPECT_EQ(1, GetRandBytes(response_data_.keys[i].key_data, + sizeof(response_data_.keys[i].key_data))); + response_data_.keys[i].key_data_length = default_key_size; + EXPECT_EQ(1, GetRandBytes(response_data_.keys[i].key_iv, + sizeof(response_data_.keys[i].key_iv))); + EXPECT_EQ(1, GetRandBytes(response_data_.keys[i].control_iv, + sizeof(response_data_.keys[i].control_iv))); + std::string kcVersion = "kc" + std::to_string(api_version_); + memcpy(response_data_.keys[i].control.verification, kcVersion.c_str(), 4); + response_data_.keys[i].control.duration = htonl(key_duration); + response_data_.keys[i].control.nonce = htonl(session_->nonce()); + response_data_.keys[i].control.control_bits = htonl(control_); + response_data_.keys[i].cipher_mode = OEMCrypto_CipherMode_CTR; + } + // Fill in the default core_response_ fields, except the substrings, which are + // filled in the next function. + core_response_.nonce_required = + ((wvoec::kControlNonceEnabled | wvoec::kControlNonceOrEntry | + wvoec::kControlNonceRequired) & + control_) + ? 1 + : 0; + core_response_.license_type = license_type_; + FillCoreResponseSubstrings(); +} + +void LicenseRoundTrip::CreateResponseWithGenericCryptoKeys() { + CreateDefaultResponse(); + response_data_.keys[0].control.control_bits |= + htonl(wvoec::kControlAllowEncrypt); + response_data_.keys[1].control.control_bits |= + htonl(wvoec::kControlAllowDecrypt); + response_data_.keys[2].control.control_bits |= + htonl(wvoec::kControlAllowSign); + response_data_.keys[3].control.control_bits |= + htonl(wvoec::kControlAllowVerify); + response_data_.keys[2].key_data_length = wvoec::MAC_KEY_SIZE; + response_data_.keys[3].key_data_length = wvoec::MAC_KEY_SIZE; + FillCoreResponseSubstrings(); +} + +void LicenseRoundTrip::FillCoreResponseSubstrings() { + if (update_mac_keys_) { + core_response_.enc_mac_keys_iv = FindSubstring( + response_data_.mac_key_iv, sizeof(response_data_.mac_key_iv)); + core_response_.enc_mac_keys = + FindSubstring(response_data_.mac_keys, sizeof(response_data_.mac_keys)); + } + if (pst_.size() > 0) { + ASSERT_LE(pst_.size(), sizeof(response_data_.pst)); + memcpy(response_data_.pst, pst_.c_str(), + min(sizeof(response_data_.pst), pst_.length())); + core_response_.pst = FindSubstring(response_data_.pst, pst_.size()); + } + if (minimum_srm_version_ > 0) { + const std::string verification = "HDCPDATA"; + ASSERT_EQ(verification.size(), + sizeof(response_data_.srm_restriction_data.verification)); + memcpy(response_data_.srm_restriction_data.verification, + verification.c_str(), verification.size()); + response_data_.srm_restriction_data.minimum_srm_version = + htonl(minimum_srm_version_); + core_response_.srm_restriction_data = + FindSubstring(&response_data_.srm_restriction_data, + sizeof(response_data_.srm_restriction_data)); + } + core_response_.key_array_length = num_keys_; + for (unsigned int i = 0; i < num_keys_; i++) { + core_response_.key_array[i].key_id = FindSubstring( + response_data_.keys[i].key_id, response_data_.keys[i].key_id_length); + core_response_.key_array[i].key_data_iv = FindSubstring( + response_data_.keys[i].key_iv, sizeof(response_data_.keys[i].key_iv)); + core_response_.key_array[i].key_data = + FindSubstring(response_data_.keys[i].key_data, + response_data_.keys[i].key_data_length); + core_response_.key_array[i].key_control_iv = + FindSubstring(response_data_.keys[i].control_iv, + sizeof(response_data_.keys[i].control_iv)); + core_response_.key_array[i].key_control = + FindSubstring(&response_data_.keys[i].control, + sizeof(response_data_.keys[i].control)); + } +} + +void LicenseRoundTrip::EncryptAndSignResponse() { + ASSERT_NO_FATAL_FAILURE(session_->GenerateDerivedKeysFromSessionKey()); + encrypted_response_data_ = response_data_; + uint8_t iv_buffer[KEY_IV_SIZE]; + memcpy(iv_buffer, &response_data_.mac_key_iv[0], KEY_IV_SIZE); + session_->key_deriver().CBCEncrypt( + &response_data_.mac_keys[0], &encrypted_response_data_.mac_keys[0], + 2 * MAC_KEY_SIZE, response_data_.mac_key_iv); + + for (unsigned int i = 0; i < num_keys_; i++) { + memcpy(iv_buffer, &response_data_.keys[i].control_iv[0], KEY_IV_SIZE); + AES_KEY aes_key; + AES_set_encrypt_key(&response_data_.keys[i].key_data[0], 128, &aes_key); + AES_cbc_encrypt( + reinterpret_cast(&response_data_.keys[i].control), + reinterpret_cast(&encrypted_response_data_.keys[i].control), + KEY_SIZE, &aes_key, iv_buffer, AES_ENCRYPT); + session_->key_deriver().CBCEncrypt( + &response_data_.keys[i].key_data[0], + &encrypted_response_data_.keys[i].key_data[0], + response_data_.keys[i].key_data_length, response_data_.keys[i].key_iv); + } + if (api_version_ < kCoreMessagesAPI) { + serialized_core_message_.resize(0); + } else { + std::string request_hash_string( + reinterpret_cast(request_hash_), sizeof(request_hash_)); + ASSERT_TRUE(oemcrypto_core_message::serialize::CreateCoreLicenseResponse( + core_response_, core_request_, request_hash_string, + &serialized_core_message_)); + } + + // Make the message buffer a just big enough, or the + // required size, whichever is larger. + const size_t message_size = + std::max(required_message_size_, serialized_core_message_.size() + + sizeof(encrypted_response_data_)); + // Stripe the encrypted message. + encrypted_response_.resize(message_size); + for (size_t i = 0; i < encrypted_response_.size(); i++) { + encrypted_response_[i] = i % 0x100; + } + ASSERT_GE(kMaxCoreMessage, serialized_core_message_.size()); + ASSERT_GE(encrypted_response_.size(), serialized_core_message_.size()); + memcpy(encrypted_response_.data(), serialized_core_message_.data(), + serialized_core_message_.size()); + ASSERT_GE(encrypted_response_.size(), + serialized_core_message_.size() + sizeof(encrypted_response_data_)); + memcpy(encrypted_response_.data() + serialized_core_message_.size(), + reinterpret_cast(&encrypted_response_data_), + sizeof(encrypted_response_data_)); + session()->key_deriver().ServerSignBuffer(encrypted_response_.data(), + encrypted_response_.size(), + &response_signature_); +} + +OEMCryptoResult LicenseRoundTrip::LoadResponse(Session* session) { + EXPECT_NE(session, nullptr); + // Write corpus for oemcrypto_load_license_fuzz. Fuzz script expects + // encrypted response from license server as input corpus data. + // Data will be signed again explicitly by fuzzer script after mutations. + if (ShouldGenerateCorpus()) { + const std::string file_name = GetFileName("load_license_corpus"); + // Corpus for license response fuzzer should be in the format: + // core_response + encrypted_response_data. + AppendToFile(file_name, reinterpret_cast(&core_response_), + sizeof(ODK_ParsedLicense)); + AppendToFile(file_name, + reinterpret_cast(&encrypted_response_data_), + sizeof(encrypted_response_data_)); + } + + // Some tests adjust the offset to be beyond the length of the message. Here, + // we create a duplicate of the main message buffer so that these offsets do + // not point to garbage data. The goal is to make sure OEMCrypto is verifying + // that the offset points outside of the message -- we don't want OEMCrypto to + // look at what offset points to and return an error if the data is + // garbage. Since the memory after the message buffer is an exact copy of the + // message, we can increment the offset by the message size and get valid + // data. + std::vector double_message = encrypted_response_; + double_message.insert( + double_message.end(), + reinterpret_cast(&encrypted_response_data_), + reinterpret_cast(&encrypted_response_data_) + + sizeof(encrypted_response_data_)); + OEMCryptoResult result; + if (api_version_ < kCoreMessagesAPI) { + result = OEMCrypto_LoadKeys( + session->session_id(), double_message.data(), + encrypted_response_.size(), response_signature_.data(), + response_signature_.size(), core_response_.enc_mac_keys_iv, + core_response_.enc_mac_keys, core_response_.key_array_length, + core_response_.key_array, core_response_.pst, + core_response_.srm_restriction_data, + static_cast(core_response_.license_type)); + } else { + result = OEMCrypto_LoadLicense( + session->session_id(), double_message.data(), + encrypted_response_.size(), serialized_core_message_.size(), + response_signature_.data(), response_signature_.size()); + } + if (result == OEMCrypto_SUCCESS) { + // Give the session object a copy of the license truth data so that it can + // call SelectKey, use key control information, and so that it has key data + // to verify decrypt operations. + session->set_license(response_data_); + // Also, if the license has new mac keys, then install them now. + if (core_response_.enc_mac_keys.length > 0) { + session->set_mac_keys(response_data_.mac_keys); + } + + // Note: we verify content licenses here. For entitlement license, we verify + // the key control blocks after loading entitled content keys. + if (license_type_ == OEMCrypto_ContentLicense) VerifyTestKeys(session); + } + return result; +} + +OEMCryptoResult LicenseRoundTrip::ReloadResponse(Session* session) { + session->GenerateDerivedKeysFromSessionKey(); + return LoadResponse(session); +} + +// This function verifies that the key control block reported by OEMCrypto agree +// with the truth key control block. Failures in this function probably +// indicate the OEMCrypto_LoadLicense/LoadKeys did not correctly process the key +// control block. +void LicenseRoundTrip::VerifyTestKeys(Session* session) { + for (unsigned int i = 0; i < num_keys_; i++) { + KeyControlBlock block; + size_t size = sizeof(block); + OEMCryptoResult sts = OEMCrypto_QueryKeyControl( + session->session_id(), response_data_.keys[i].key_id, + response_data_.keys[i].key_id_length, + reinterpret_cast(&block), &size); + if (sts != OEMCrypto_ERROR_NOT_IMPLEMENTED) { + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + ASSERT_EQ(sizeof(block), size); + // Note: we do not assume that duration is stored with each key after v16. + // control bits stored in network byte order. For printing + // we change to host byte order. + ASSERT_EQ(htonl_fnc(response_data_.keys[i].control.control_bits), + htonl_fnc(block.control_bits)) + << "For key " << i; + } + } +} + +void LicenseRoundTrip::SetKeyId(size_t index, const string& key_id) { + ASSERT_LT(index, num_keys_); + MessageKeyData& key = response_data_.keys[index]; + key.key_id_length = key_id.length(); + ASSERT_LE(key.key_id_length, kTestKeyIdMaxLength); + memcpy(key.key_id, key_id.data(), key.key_id_length); +} + +void EntitledMessage::FillKeyArray() { + for (size_t i = 0; i < license_messages_->num_keys(); ++i) { + MakeOneKey(i); + } +} + +void EntitledMessage::MakeOneKey(size_t entitlement_key_index) { + ASSERT_LT(entitlement_key_index, kMaxNumKeys); + ASSERT_LT(num_keys_, kMaxNumKeys); + EntitledContentKeyData* key_data = &entitled_key_data_[num_keys_]; + MessageKeyData* entitlement_key = + &license_messages_->response_data().keys[entitlement_key_index]; + OEMCrypto_EntitledContentKeyObject* offsets = &entitled_key_array_[num_keys_]; + num_keys_++; + + key_data->key_index = entitlement_key_index; + ASSERT_LE(entitlement_key->key_id_length, kTestKeyIdMaxLength); + memcpy(key_data->entitlement_key_id, entitlement_key->key_id, + entitlement_key->key_id_length); + key_data->entitlement_key_id_length = entitlement_key->key_id_length; + offsets->entitlement_key_id = FindSubstring(key_data->entitlement_key_id, + entitlement_key->key_id_length); + + key_data->content_key_id_length = kDefaultKeyIdLength; + // Fill the key ID as CnCnCnCn... so it's easy to see in debug logs. + memset(key_data->content_key_id, 0xC0 + num_keys_, + key_data->content_key_id_length); + offsets->content_key_id = + FindSubstring(key_data->content_key_id, key_data->content_key_id_length); + + EXPECT_EQ(1, GetRandBytes(key_data->content_key_data, + sizeof(key_data->content_key_data))); + // Note: we give the encrypted content key to OEMCrypto, not the clear + // content key. + offsets->content_key_data = + FindSubstring(key_data->encrypted_content_key_data, + sizeof(key_data->encrypted_content_key_data)); + + EXPECT_EQ(1, GetRandBytes(key_data->content_key_data_iv, + sizeof(key_data->content_key_data_iv))); + offsets->content_key_data_iv = FindSubstring( + key_data->content_key_data_iv, sizeof(key_data->content_key_data_iv)); +} + +void EntitledMessage::SetEntitlementKeyId(unsigned int index, + const std::string& key_id) { + ASSERT_LT(index, num_keys_); + ASSERT_LE(key_id.size(), kTestKeyIdMaxLength); + entitled_key_data_[index].entitlement_key_id_length = key_id.size(); + memcpy(entitled_key_data_[index].entitlement_key_id, + reinterpret_cast(key_id.c_str()), key_id.length()); + entitled_key_array_[index].entitlement_key_id = FindSubstring( + entitled_key_data_[index].entitlement_key_id, key_id.length()); +} + +void EntitledMessage::SetContentKeyId(unsigned int index, + const std::string& key_id) { + ASSERT_LT(index, num_keys_); + ASSERT_LE(key_id.size(), kTestKeyIdMaxLength); + entitled_key_data_[index].content_key_id_length = key_id.size(); + memcpy(entitled_key_data_[index].content_key_id, + reinterpret_cast(key_id.c_str()), key_id.length()); + entitled_key_array_[index].content_key_id = + FindSubstring(entitled_key_data_[index].content_key_id, key_id.length()); +} + +OEMCrypto_Substring EntitledMessage::FindSubstring(const void* ptr, + size_t size) { + OEMCrypto_Substring substring{0, 0}; + if (ptr != nullptr) { + substring.offset = reinterpret_cast(ptr) - + reinterpret_cast(entitled_key_data_); + substring.length = size; + } + return substring; +} + +void EntitledMessage::LoadKeys(OEMCryptoResult expected_sts) { + for (size_t i = 0; i < num_keys_; ++i) { + EntitledContentKeyData* key_data = &entitled_key_data_[i]; + const size_t entitlement_key_index = key_data->key_index; + MessageKeyData* entitlement_key = + &license_messages_->response_data().keys[entitlement_key_index]; + + // Load the entitlement key from |key_array_|. + AES_KEY aes_key; + AES_set_encrypt_key(entitlement_key->key_data, 256, &aes_key); + + // Encrypt the content key with the entitlement key. + uint8_t iv[16]; + memcpy(&iv[0], key_data->content_key_data_iv, KEY_IV_SIZE); + AES_cbc_encrypt(key_data->content_key_data, + key_data->encrypted_content_key_data, KEY_SIZE, &aes_key, + iv, AES_ENCRYPT); + } + ASSERT_EQ( + expected_sts, + OEMCrypto_LoadEntitledContentKeys( + key_session_, reinterpret_cast(entitled_key_data_), + sizeof(entitled_key_data_), num_keys_, entitled_key_array_)); + if (expected_sts != OEMCrypto_SUCCESS) { + return; + } + VerifyEntitlementTestKeys(); +} + +// This function verifies that the key control block reported by OEMCrypto agree +// with the truth key control block. Failures in this function probably +// indicate the OEMCrypto_LoadEntitledKeys did not correctly process the key +// control block. +void EntitledMessage::VerifyEntitlementTestKeys() { + for (unsigned int i = 0; i < num_keys_; i++) { + EntitledContentKeyData* key_data = &entitled_key_data_[i]; + const size_t entitlement_key_index = key_data->key_index; + MessageKeyData* entitlement_key = + &license_messages_->response_data().keys[entitlement_key_index]; + KeyControlBlock block; + size_t size = sizeof(block); + OEMCryptoResult sts = OEMCrypto_QueryKeyControl( + key_session_, key_data->content_key_id, key_data->content_key_id_length, + reinterpret_cast(&block), &size); + if (sts != OEMCrypto_ERROR_NOT_IMPLEMENTED) { + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + ASSERT_EQ(sizeof(block), size); + // control duration and bits stored in network byte order. For printing + // we change to host byte order. + ASSERT_EQ((htonl_fnc(entitlement_key->control.duration)), + (htonl_fnc(block.duration))) + << "For key " << i; + ASSERT_EQ(htonl_fnc(entitlement_key->control.control_bits), + htonl_fnc(block.control_bits)) + << "For key " << i; + } + } +} + +void RenewalRoundTrip::VerifyRequestSignature( + const vector& data, const vector& generated_signature, + size_t core_message_length) { + ASSERT_EQ(HMAC_SHA256_SIGNATURE_SIZE, generated_signature.size()); + std::vector expected_signature; + if (license_messages_->api_version() < kCoreMessagesAPI) { + // For v15 or earlier, we only sign the message body. Ignore the core + // message. + std::vector subdata(data.begin() + core_message_length, + data.end()); + session()->key_deriver().ClientSignBuffer(subdata, &expected_signature); + } else { + session()->key_deriver().ClientSignBuffer(data, &expected_signature); + } + ASSERT_EQ(expected_signature, generated_signature); +} + +void RenewalRoundTrip::FillAndVerifyCoreRequest( + const std::string& core_message_string) { + if (license_messages_->api_version() < kCoreMessagesAPI || is_release_) { + // For v15 or for a release, we expect that no core request was created. + EXPECT_FALSE( + oemcrypto_core_message::deserialize::CoreRenewalRequestFromMessage( + core_message_string, &core_request_)); + } else { + EXPECT_TRUE( + oemcrypto_core_message::deserialize::CoreRenewalRequestFromMessage( + core_message_string, &core_request_)); + EXPECT_EQ(license_messages_->core_request().api_major_version, + core_request_.api_major_version); + EXPECT_EQ(license_messages_->core_request().nonce, core_request_.nonce); + EXPECT_EQ(license_messages_->core_request().session_id, + core_request_.session_id); + } +} + +void RenewalRoundTrip::CreateDefaultResponse() { + if (license_messages_->api_version() < kCoreMessagesAPI || is_release_) { + uint32_t control = 0; + uint32_t nonce = 0; + // If this is a v15 device, and a v15 license, and the license used a nonce, + // then the response should require a new nonce, too. + if (global_features.api_version < kCoreMessagesAPI && + (license_messages_->control() & wvoec::kControlNonceEnabled)) { + control = wvoec::kControlNonceEnabled; + session_->GenerateNonce(); + nonce = session_->nonce(); + } + // A single key object with no key id should update all keys. + constexpr size_t index = 0; + response_data_.keys[index].key_id_length = 0; + response_data_.keys[index].key_id[0] = '\0'; + const uint32_t renewal_api = + std::max(core_request_.api_major_version, 15u); + std::string kcVersion = "kc" + std::to_string(renewal_api); + memcpy(response_data_.keys[index].control.verification, kcVersion.c_str(), + 4); + const uint32_t duration = static_cast( + license_messages_->core_response() + .timer_limits.initial_renewal_duration_seconds); + response_data_.keys[index].control.duration = htonl(duration); + response_data_.keys[index].control.nonce = htonl(nonce); + response_data_.keys[index].control.control_bits = htonl(control); + } +} + +void RenewalRoundTrip::EncryptAndSignResponse() { + // Renewal messages are not encrypted. + encrypted_response_data_ = response_data_; + // Either create a KeyRefreshObject for a call to RefreshKeys or a core + // response for a call to LoadRenewal. + if (license_messages_->api_version() < kCoreMessagesAPI) { + refresh_object_.key_id = FindSubstring(nullptr, 0); + refresh_object_.key_control_iv = FindSubstring(nullptr, 0); + refresh_object_.key_control = + FindSubstring(&response_data_.keys[0].control, + sizeof(response_data_.keys[0].control)); + serialized_core_message_.resize(0); + } else { + ASSERT_TRUE(oemcrypto_core_message::serialize::CreateCoreRenewalResponse( + core_request_, renewal_duration_seconds_, &serialized_core_message_)); + } + // Make the message buffer a just big enough, or the + // required size, whichever is larger. + const size_t message_size = + std::max(required_message_size_, serialized_core_message_.size() + + sizeof(encrypted_response_data_)); + // Stripe the encrypted message. + encrypted_response_.resize(message_size); + for (size_t i = 0; i < encrypted_response_.size(); i++) { + encrypted_response_[i] = i % 0x100; + } + // Concatenate the core message and the response. + ASSERT_GE(kMaxCoreMessage, serialized_core_message_.size()); + ASSERT_GE(encrypted_response_.size(), serialized_core_message_.size()); + memcpy(encrypted_response_.data(), serialized_core_message_.data(), + serialized_core_message_.size()); + ASSERT_GE(encrypted_response_.size(), + serialized_core_message_.size() + sizeof(encrypted_response_data_)); + memcpy(encrypted_response_.data() + serialized_core_message_.size(), + reinterpret_cast(&encrypted_response_data_), + sizeof(encrypted_response_data_)); + session()->key_deriver().ServerSignBuffer(encrypted_response_.data(), + encrypted_response_.size(), + &response_signature_); +} + +OEMCryptoResult RenewalRoundTrip::LoadResponse(Session* session) { + if (license_messages_->api_version() < kCoreMessagesAPI) { + return OEMCrypto_RefreshKeys( + session->session_id(), encrypted_response_.data(), + encrypted_response_.size(), response_signature_.data(), + response_signature_.size(), 1, &refresh_object_); + } else { + return OEMCrypto_LoadRenewal( + session->session_id(), encrypted_response_.data(), + encrypted_response_.size(), serialized_core_message_.size(), + response_signature_.data(), response_signature_.size()); + } +} + +Session::Session() + : open_(false), + forced_session_id_(false), + session_id_(0), + nonce_(0), + public_rsa_(0) {} + +Session::~Session() { + if (!forced_session_id_ && open_) close(); + if (public_rsa_) RSA_free(public_rsa_); +} + +void Session::open() { + EXPECT_FALSE(forced_session_id_); + EXPECT_FALSE(open_); + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_OpenSession(&session_id_)); + open_ = true; +} + +void Session::SetSessionId(uint32_t session_id) { + EXPECT_FALSE(open_); + session_id_ = session_id; + forced_session_id_ = true; +} + +void Session::close() { + EXPECT_TRUE(open_ || forced_session_id_); + if (open_) { + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CloseSession(session_id_)); + } + forced_session_id_ = false; + open_ = false; +} + +void Session::GenerateNonce(int* error_counter) { + // We make one attempt. If it fails, we assume there was a nonce flood. + if (OEMCrypto_SUCCESS == OEMCrypto_GenerateNonce(session_id(), &nonce_)) { + return; + } + if (error_counter) { + (*error_counter)++; + } else { + wvutil::TestSleep::Sleep(1); // wait a second, then try again. + // The following is after a 1 second pause, so it cannot be from a nonce + // flood. + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GenerateNonce(session_id(), &nonce_)); + } +} + +void Session::FillDefaultContext(vector* mac_context, + vector* enc_context) { + /* Context strings + * These context strings are normally created by the CDM layer + * from a license request message. + * They are used to test MAC and ENC key generation. + */ + *mac_context = wvutil::a2b_hex( + "41555448454e5449434154494f4e000a4c08001248000000020000101907d9ff" + "de13aa95c122678053362136bdf8408f8276e4c2d87ec52b61aa1b9f646e5873" + "4930acebe899b3e464189a14a87202fb02574e70640bd22ef44b2d7e3912250a" + "230a14080112100915007caa9b5931b76a3a85f046523e10011a093938373635" + "34333231180120002a0c31383836373837343035000000000200"); + *enc_context = wvutil::a2b_hex( + "454e4352595054494f4e000a4c08001248000000020000101907d9ffde13aa95" + "c122678053362136bdf8408f8276e4c2d87ec52b61aa1b9f646e58734930aceb" + "e899b3e464189a14a87202fb02574e70640bd22ef44b2d7e3912250a230a1408" + "0112100915007caa9b5931b76a3a85f046523e10011a09393837363534333231" + "180120002a0c31383836373837343035000000000080"); +} + +// This should only be called if the device uses Provisioning 2.0. A failure in +// this function is probably caused by a bad keybox. +void Session::GenerateDerivedKeysFromKeybox( + const wvoec::WidevineKeybox& keybox) { + vector mac_context; + vector enc_context; + FillDefaultContext(&mac_context, &enc_context); + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GenerateDerivedKeys( + session_id(), mac_context.data(), mac_context.size(), + enc_context.data(), enc_context.size())); + key_deriver_.DeriveKeys(keybox.device_key_, mac_context, enc_context); +} + +void Session::GenerateDerivedKeysFromSessionKey() { + // Uses test certificate. + vector session_key; + vector enc_session_key; + ASSERT_NE(public_rsa_, nullptr) << "No public RSA key loaded in test code."; + // A failure here probably indicates that there is something wrong with the + // test program and its dependency on BoringSSL. + ASSERT_TRUE(GenerateRSASessionKey(&session_key, &enc_session_key)); + vector mac_context; + vector enc_context; + FillDefaultContext(&mac_context, &enc_context); + // A failure here is probably caused by having the wrong RSA key loaded. + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_DeriveKeysFromSessionKey( + session_id(), enc_session_key.data(), enc_session_key.size(), + mac_context.data(), mac_context.size(), enc_context.data(), + enc_context.size())); + + key_deriver_.DeriveKeys(session_key.data(), mac_context, enc_context); +} + +void Session::EncryptCTR(const vector& in_buffer, const uint8_t* key, + const uint8_t* starting_iv, + vector* out_buffer) { + ASSERT_NE(nullptr, key); + ASSERT_NE(nullptr, starting_iv); + ASSERT_NE(nullptr, out_buffer); + AES_KEY aes_key; + AES_set_encrypt_key(key, AES_BLOCK_SIZE * 8, &aes_key); + out_buffer->resize(in_buffer.size()); + + uint8_t iv[AES_BLOCK_SIZE]; // Current iv. + + memcpy(iv, &starting_iv[0], AES_BLOCK_SIZE); + size_t l = 0; // byte index into encrypted subsample. + while (l < in_buffer.size()) { + uint8_t aes_output[AES_BLOCK_SIZE]; + AES_encrypt(iv, aes_output, &aes_key); + for (size_t n = 0; n < AES_BLOCK_SIZE && l < in_buffer.size(); n++, l++) { + (*out_buffer)[l] = aes_output[n] ^ in_buffer[l]; + } + ctr128_inc64(1, iv); + } +} + +void Session::TestDecryptCTR(bool select_key_first, + OEMCryptoResult expected_result, int key_index) { + OEMCryptoResult select_result = OEMCrypto_SUCCESS; + if (select_key_first) { + // Select the key (from FillSimpleMessage) + select_result = OEMCrypto_SelectKey( + session_id(), license_.keys[key_index].key_id, + license_.keys[key_index].key_id_length, OEMCrypto_CipherMode_CTR); + } + + // Create test sample description + vector unencrypted_data(kTestSubsampleSectionSize); + vector encrypted_data(unencrypted_data.size()); + vector output_buffer(unencrypted_data.size()); + OEMCrypto_SampleDescription sample_description; + OEMCrypto_SubSampleDescription subsample_description; + + ASSERT_NO_FATAL_FAILURE(GenerateSimpleSampleDescription( + encrypted_data, output_buffer, &sample_description, + &subsample_description)); + + // Generate test data + EXPECT_EQ(GetRandBytes(unencrypted_data.data(), unencrypted_data.size()), 1); + EncryptCTR(unencrypted_data, license_.keys[key_index].key_data, + &sample_description.iv[0], &encrypted_data); + + // Create the pattern description (always 0,0 for CTR) + OEMCrypto_CENCEncryptPatternDesc pattern = {0, 0}; + + // Decrypt the data + const OEMCryptoResult decrypt_result = + OEMCrypto_DecryptCENC(session_id(), &sample_description, 1, &pattern); + // We only have a few errors that we test are reported. + ASSERT_NO_FATAL_FAILURE( + TestDecryptResult(expected_result, select_result, decrypt_result)) + << "Either SelectKey or DecryptCENC should return " << expected_result + << ", but they returned " << select_result << " and " << decrypt_result + << ", respectively."; + if (expected_result == OEMCrypto_SUCCESS) { // No error. + ASSERT_EQ(unencrypted_data, output_buffer); + } else { + ASSERT_NE(unencrypted_data, output_buffer); + } +} + +void Session::TestDecryptResult(OEMCryptoResult expected_result, + OEMCryptoResult actual_select_result, + OEMCryptoResult actual_decrypt_result) { + // In most cases, we expect the result to come from either the select key or + // from the decrypt call. + if (expected_result == OEMCrypto_SUCCESS) { // No error. + ASSERT_EQ(OEMCrypto_SUCCESS, actual_select_result); + ASSERT_EQ(OEMCrypto_SUCCESS, actual_decrypt_result); + } else if (expected_result == OEMCrypto_ERROR_KEY_EXPIRED || + expected_result == OEMCrypto_ERROR_INSUFFICIENT_HDCP || + expected_result == OEMCrypto_ERROR_ANALOG_OUTPUT) { + // Key expired or output problems may be reported from select key or + // decrypt, but must be reported. + ASSERT_TRUE(actual_select_result == expected_result || + actual_decrypt_result == expected_result); + } else if (expected_result == OEMCrypto_WARNING_MIXED_OUTPUT_PROTECTION && + global_features.api_version >= kCoreMessagesAPI) { + // OEMCrypto is allowed to report either this warning or + // OEMCrypto_ERROR_INSUFFICIENT_HDCP depending on if it can disable + // restricted displays. + ASSERT_TRUE( + actual_select_result == OEMCrypto_WARNING_MIXED_OUTPUT_PROTECTION || + actual_select_result == OEMCrypto_ERROR_INSUFFICIENT_HDCP || + actual_decrypt_result == OEMCrypto_WARNING_MIXED_OUTPUT_PROTECTION || + actual_decrypt_result == OEMCrypto_ERROR_INSUFFICIENT_HDCP); + } else { + // OEM's can fine tune other error codes for debugging. + ASSERT_TRUE(actual_select_result != OEMCrypto_SUCCESS || + actual_decrypt_result != OEMCrypto_SUCCESS); + } +} + +void Session::TestSelectExpired(unsigned int key_index) { + if (global_features.api_version >= 13) { + OEMCryptoResult status = + OEMCrypto_SelectKey(session_id(), license().keys[key_index].key_id, + license().keys[key_index].key_id_length, + OEMCrypto_CipherMode_CTR); + // It is OK for SelectKey to succeed with an expired key, but if there is + // an error, it must be OEMCrypto_ERROR_KEY_EXIRED. + if (status != OEMCrypto_SUCCESS) { + ASSERT_EQ(OEMCrypto_ERROR_KEY_EXPIRED, status); + } + } +} + +void Session::LoadOEMCert(bool verify_cert) { + // Get the OEM Public Cert from OEMCrypto + vector public_cert; + size_t public_cert_length = 0; + ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, + OEMCrypto_GetOEMPublicCertificate(nullptr, &public_cert_length)); + ASSERT_LT(0u, public_cert_length); + public_cert.resize(public_cert_length); + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_GetOEMPublicCertificate( + public_cert.data(), &public_cert_length)); + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_LoadOEMPrivateKey(session_id())); + + // The cert is a PKCS7 signed data type. First, parse it into an OpenSSL + // structure and find the certificate list. + // + // We must make defensive copies of public_cert's properties because of how + // d2i_PKCS7() works. + const unsigned char* cert_data = + reinterpret_cast(public_cert.data()); + long cert_size = static_cast(public_cert.size()); + boringssl_ptr pkcs7( + d2i_PKCS7(NULL, &cert_data, cert_size)); + ASSERT_TRUE(pkcs7.NotNull()) << "Error parsing PKCS7 message"; + ASSERT_TRUE(PKCS7_type_is_signed(pkcs7.get())) + << "Unexpected PKCS7 message type"; + + STACK_OF(X509)* certs = pkcs7->d.sign->cert; + + // Load the public cert's key into public_rsa_ and verify, if requested + for (size_t i = 0; certs && i < static_cast(sk_X509_num(certs)); + ++i) { + X509* x509_cert = sk_X509_value(certs, i); + boringssl_ptr pubkey(X509_get_pubkey(x509_cert)); + ASSERT_TRUE(pubkey.NotNull()); + if (i == 0) { + public_rsa_ = EVP_PKEY_get1_RSA(pubkey.get()); + if (!public_rsa_) { + cout << "d2i_RSAPrivateKey failed.\n"; + dump_boringssl_error(); + ASSERT_TRUE(nullptr != public_rsa_); + } + } + if (verify_cert) { + vector buffer(80); + + X509_NAME* name = X509_get_subject_name(x509_cert); + printf(" OEM Certificate Name: %s\n", + X509_NAME_oneline(name, buffer.data(), buffer.size())); + boringssl_ptr store(X509_STORE_new()); + ASSERT_TRUE(store.NotNull()); + boringssl_ptr store_ctx( + X509_STORE_CTX_new()); + ASSERT_TRUE(store_ctx.NotNull()); + + X509_STORE_CTX_init(store_ctx.get(), store.get(), x509_cert, nullptr); + + // TODO(fredgc): Verify cert is signed by Google. + + int result = X509_verify_cert(store_ctx.get()); + ASSERT_GE(0, result) << " OEM Cert not valid. " << + X509_verify_cert_error_string( + X509_STORE_CTX_get_error(store_ctx.get())); + if (result == 0) { + printf("Cert not verified: %s.\n", + X509_verify_cert_error_string( + X509_STORE_CTX_get_error(store_ctx.get()))); + } + } + } +} + +void Session::PreparePublicKey(const uint8_t* rsa_key, size_t rsa_key_length) { + if (rsa_key == nullptr) { + rsa_key = kTestRSAPKCS8PrivateKeyInfo2_2048; + rsa_key_length = sizeof(kTestRSAPKCS8PrivateKeyInfo2_2048); + } + uint8_t* p = const_cast(rsa_key); + boringssl_ptr bio(BIO_new_mem_buf(p, rsa_key_length)); + ASSERT_TRUE(bio.NotNull()); + boringssl_ptr pkcs8_pki( + d2i_PKCS8_PRIV_KEY_INFO_bio(bio.get(), nullptr)); + ASSERT_TRUE(pkcs8_pki.NotNull()); + boringssl_ptr evp(EVP_PKCS82PKEY(pkcs8_pki.get())); + ASSERT_TRUE(evp.NotNull()); + if (public_rsa_) RSA_free(public_rsa_); + public_rsa_ = EVP_PKEY_get1_RSA(evp.get()); + if (!public_rsa_) { + cout << "d2i_RSAPrivateKey failed. "; + dump_boringssl_error(); + FAIL() << "Could not parse public RSA key."; + } + switch (RSA_check_key(public_rsa_)) { + case 1: // valid. + return; + case 0: // not valid. + dump_boringssl_error(); + FAIL() << "[rsa key not valid] "; + default: // -1 == check failed. + dump_boringssl_error(); + FAIL() << "[error checking rsa key] "; + } +} + +bool Session::VerifyPSSSignature(EVP_PKEY* pkey, const uint8_t* message, + size_t message_length, + const uint8_t* signature, + size_t signature_length) { + boringssl_ptr md_ctx(EVP_MD_CTX_new()); + EVP_PKEY_CTX* pkey_ctx = nullptr; + + if (EVP_DigestVerifyInit(md_ctx.get(), &pkey_ctx, EVP_sha1(), + nullptr /* no ENGINE */, pkey) != 1) { + LOGE("EVP_DigestVerifyInit failed in VerifyPSSSignature"); + goto err; + } + + if (EVP_PKEY_CTX_set_signature_md(pkey_ctx, + const_cast(EVP_sha1())) != 1) { + LOGE("EVP_PKEY_CTX_set_signature_md failed in VerifyPSSSignature"); + goto err; + } + + if (EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, RSA_PKCS1_PSS_PADDING) != 1) { + LOGE("EVP_PKEY_CTX_set_rsa_padding failed in VerifyPSSSignature"); + goto err; + } + + if (EVP_PKEY_CTX_set_rsa_pss_saltlen(pkey_ctx, SHA_DIGEST_LENGTH) != 1) { + LOGE("EVP_PKEY_CTX_set_rsa_pss_saltlen failed in VerifyPSSSignature"); + goto err; + } + + if (EVP_DigestVerifyUpdate(md_ctx.get(), message, message_length) != 1) { + LOGE("EVP_DigestVerifyUpdate failed in VerifyPSSSignature"); + goto err; + } + + if (EVP_DigestVerifyFinal(md_ctx.get(), const_cast(signature), + signature_length) != 1) { + LOGE( + "EVP_DigestVerifyFinal failed in VerifyPSSSignature. (Probably a bad " + "signature.)"); + goto err; + } + + return true; + +err: + dump_boringssl_error(); + return false; +} + +void Session::VerifyRSASignature(const vector& message, + const uint8_t* signature, + size_t signature_length, + RSA_Padding_Scheme padding_scheme) { + ASSERT_NE(public_rsa_, nullptr) << "No public RSA key loaded in test code."; + + ASSERT_EQ(static_cast(RSA_size(public_rsa_)), signature_length) + << "Signature size is wrong. " << signature_length << ", should be " + << RSA_size(public_rsa_); + + if (padding_scheme == kSign_RSASSA_PSS) { + boringssl_ptr pkey(EVP_PKEY_new()); + ASSERT_EQ(1, EVP_PKEY_set1_RSA(pkey.get(), public_rsa_)); + + const bool ok = VerifyPSSSignature( + pkey.get(), message.data(), message.size(), signature, + signature_length); + EXPECT_TRUE(ok) << "PSS signature check failed."; + } else if (padding_scheme == kSign_PKCS1_Block1) { + vector padded_digest(signature_length); + int size; + // RSA_public_decrypt decrypts the signature, and then verifies that + // it was padded with RSA PKCS1 padding. + size = RSA_public_decrypt( + signature_length, signature, padded_digest.data(), public_rsa_, + RSA_PKCS1_PADDING); + EXPECT_GT(size, 0); + padded_digest.resize(size); + EXPECT_EQ(message, padded_digest); + } else { + EXPECT_TRUE(false) << "Padding scheme not supported."; + } +} + +bool Session::GenerateRSASessionKey(vector* session_key, + vector* enc_session_key) { + if (!public_rsa_) { + cout << "No public RSA key loaded in test code.\n"; + return false; + } + *session_key = wvutil::a2b_hex("6fa479c731d2770b6a61a5d1420bb9d1"); + enc_session_key->assign(RSA_size(public_rsa_), 0); + int status = RSA_public_encrypt(session_key->size(), &(session_key->front()), + &(enc_session_key->front()), public_rsa_, + RSA_PKCS1_OAEP_PADDING); + int size = static_cast(RSA_size(public_rsa_)); + if (status != size) { + cout << "GenerateRSASessionKey error encrypting session key.\n"; + dump_boringssl_error(); + return false; + } + return true; +} + +void Session::InstallRSASessionTestKey(const vector& wrapped_rsa_key) { + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_LoadDRMPrivateKey(session_id(), OEMCrypto_RSA_Private_Key, + wrapped_rsa_key.data(), + wrapped_rsa_key.size())); +} + +void Session::CreateNewUsageEntry(OEMCryptoResult* status) { + OEMCryptoResult result = + OEMCrypto_CreateNewUsageEntry(session_id(), &usage_entry_number_); + if (status) { + *status = result; + return; + } + ASSERT_EQ(OEMCrypto_SUCCESS, result); +} + +void Session::UpdateUsageEntry(std::vector* header_buffer) { + size_t header_buffer_length = 0; + size_t entry_buffer_length = 0; + ASSERT_EQ( + OEMCrypto_ERROR_SHORT_BUFFER, + OEMCrypto_UpdateUsageEntry(session_id(), nullptr, &header_buffer_length, + nullptr, &entry_buffer_length)); + ASSERT_LT(0u, header_buffer_length); + header_buffer->resize(header_buffer_length); + ASSERT_LT(0u, entry_buffer_length); + encrypted_usage_entry_.resize(entry_buffer_length); + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_UpdateUsageEntry( + session_id(), header_buffer->data(), &header_buffer_length, + encrypted_usage_entry_.data(), &entry_buffer_length)); +} + +void Session::LoadUsageEntry(uint32_t index, const vector& buffer) { + usage_entry_number_ = index; + encrypted_usage_entry_ = buffer; + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_LoadUsageEntry( + session_id(), index, buffer.data(), buffer.size())); +} + +void Session::MoveUsageEntry(uint32_t new_index, + std::vector* header_buffer, + OEMCryptoResult expect_result) { + + ASSERT_NO_FATAL_FAILURE(open()); + ASSERT_NO_FATAL_FAILURE(ReloadUsageEntry()); + ASSERT_EQ(expect_result, OEMCrypto_MoveEntry(session_id(), new_index)); + if (expect_result == OEMCrypto_SUCCESS) { + usage_entry_number_ = new_index; + ASSERT_NO_FATAL_FAILURE(UpdateUsageEntry(header_buffer)); + } + ASSERT_NO_FATAL_FAILURE(close()); +} + +void Session::GenerateReport(const std::string& pst, + OEMCryptoResult expected_result, + Session* other) { + ASSERT_TRUE(open_); + if (other) { // If other is specified, copy mac keys. + key_deriver_ = other->key_deriver_; + } + size_t length = 0; + OEMCryptoResult sts = OEMCrypto_ReportUsage( + session_id(), reinterpret_cast(pst.c_str()), pst.length(), + pst_report_buffer_.data(), &length); + if (expected_result == OEMCrypto_SUCCESS) { + ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts); + } + if (sts == OEMCrypto_ERROR_SHORT_BUFFER) { + pst_report_buffer_.assign(length, 0xFF); // Fill with garbage values. + } + sts = OEMCrypto_ReportUsage(session_id(), + reinterpret_cast(pst.c_str()), + pst.length(), pst_report_buffer_.data(), &length); + ASSERT_EQ(expected_result, sts); + if (expected_result != OEMCrypto_SUCCESS) { + return; + } + EXPECT_EQ(wvutil::Unpacked_PST_Report::report_size(pst.length()), length); + vector computed_signature(SHA_DIGEST_LENGTH); + key_deriver_.ClientSignPstReport(pst_report_buffer_, &computed_signature); + EXPECT_EQ(0, memcmp(computed_signature.data(), pst_report().signature(), + SHA_DIGEST_LENGTH)); + EXPECT_GE(kInactiveUnused, pst_report().status()); + EXPECT_GE(kHardwareSecureClock, pst_report().clock_security_level()); + EXPECT_EQ(pst.length(), pst_report().pst_length()); + EXPECT_EQ(0, memcmp(pst.c_str(), pst_report().pst(), pst.length())); +} + +void Session::VerifyPST(const Test_PST_Report& expected) { + wvutil::Unpacked_PST_Report computed = pst_report(); + EXPECT_EQ(expected.status, computed.status()); + char* pst_ptr = reinterpret_cast(computed.pst()); + std::string computed_pst(pst_ptr, pst_ptr + computed.pst_length()); + ASSERT_EQ(expected.pst, computed_pst); + int64_t now = wvutil::Clock().GetCurrentTime(); + int64_t age = now - expected.time_created; // How old is this report. + EXPECT_NEAR(expected.seconds_since_license_received + age, + computed.seconds_since_license_received(), + kTimeTolerance); + // Decrypt times only valid on licenses that have been active. + if (expected.status == kActive || expected.status == kInactiveUsed) { + EXPECT_NEAR(expected.seconds_since_first_decrypt + age, + computed.seconds_since_first_decrypt(), + kUsageTableTimeTolerance); + EXPECT_NEAR(expected.seconds_since_last_decrypt + age, + computed.seconds_since_last_decrypt(), + kUsageTableTimeTolerance); + } + std::vector signature(SHA_DIGEST_LENGTH); + key_deriver_.ClientSignPstReport(pst_report_buffer_, &signature); + EXPECT_EQ(0, memcmp(computed.signature(), signature.data(), + SHA_DIGEST_LENGTH)); +} + +void Session::VerifyReport(Test_PST_Report expected, + int64_t time_license_received, + int64_t time_first_decrypt, + int64_t time_last_decrypt) { + const int64_t now = wvutil::Clock().GetCurrentTime(); + expected.seconds_since_license_received = + (time_license_received > 0 && time_license_received < now) + ? now - time_license_received + : 0; + expected.seconds_since_first_decrypt = + (time_first_decrypt > 0 && time_first_decrypt < now) + ? now - time_first_decrypt + : 0; + expected.seconds_since_last_decrypt = + (time_last_decrypt > 0 && time_last_decrypt < now) + ? now - time_last_decrypt + : 0; + ASSERT_NO_FATAL_FAILURE(VerifyPST(expected)); +} +} // namespace wvoec diff --git a/oemcrypto/test/oec_session_util.h b/oemcrypto/test/oec_session_util.h new file mode 100644 index 0000000..d694e0f --- /dev/null +++ b/oemcrypto/test/oec_session_util.h @@ -0,0 +1,608 @@ +#ifndef CDM_OEC_SESSION_UTIL_H_ +#define CDM_OEC_SESSION_UTIL_H_ + +// 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. +// +// OEMCrypto unit tests +// +#include +#include +#include +#include +#include + +#include "core_message_deserialize.h" +#include "core_message_serialize.h" +#include "odk.h" +#include "oec_device_features.h" +#include "oec_key_deriver.h" +#include "oemcrypto_types.h" +#include "pst_report.h" + +using namespace std; + +// GTest requires PrintTo to be in the same namespace as the thing it prints, +// which is std::vector in this case. +namespace std { + +void PrintTo(const vector& value, ostream* os); + +} // namespace std + +namespace wvoec { + +// Make sure this is larger than kMaxKeysPerSession, in oemcrypto_test.cpp +constexpr size_t kMaxNumKeys = 30; + +namespace { +#if defined(TEST_SPEED_MULTIPLIER) // Can slow test time limits when + // debugging is slowing everything. +constexpr int kSpeedMultiplier = TEST_SPEED_MULTIPLIER; +#else +constexpr int kSpeedMultiplier = 1; +#endif +constexpr int kShortSleep = 1 * kSpeedMultiplier; +constexpr int kLongSleep = 2 * kSpeedMultiplier; +constexpr uint32_t kDuration = 2 * kSpeedMultiplier; +constexpr uint32_t kLongDuration = 5 * kSpeedMultiplier; +constexpr int32_t kTimeTolerance = 3 * kSpeedMultiplier; +constexpr int64_t kUsageTableTimeTolerance = 10 * kSpeedMultiplier; +} // namespace + +typedef struct { + uint8_t verification[4]; + uint32_t duration; + uint32_t nonce; + uint32_t control_bits; +} KeyControlBlock; + +// Note: The API does not specify a maximum key id length. We specify a +// maximum just for these tests, so that we have a fixed message size. +constexpr size_t kTestKeyIdMaxLength = 16; + +// Most content will use a key id that is 16 bytes long. +constexpr int kDefaultKeyIdLength = 16; +constexpr size_t kMaxPSTLength = 255; // In specification. +constexpr size_t kMaxCoreMessage = 200 * kMaxNumKeys + 200; // Rough estimate. + +typedef struct { + uint8_t key_id[kTestKeyIdMaxLength]; + size_t key_id_length; + uint8_t key_data[MAC_KEY_SIZE]; + size_t key_data_length; + uint8_t key_iv[KEY_IV_SIZE]; + uint8_t control_iv[KEY_IV_SIZE]; + KeyControlBlock control; + // Note: cipher_mode may not be part of a real signed message. For these + // tests, it is convenient to keep it in this structure anyway. + OEMCryptoCipherMode cipher_mode; +} MessageKeyData; + +// This structure will be signed to simulate a message from the server. +struct MessageData { + MessageKeyData keys[kMaxNumKeys]; + uint8_t mac_key_iv[KEY_IV_SIZE]; + uint8_t padding[KEY_IV_SIZE]; + uint8_t mac_keys[2 * MAC_KEY_SIZE]; + uint8_t pst[kMaxPSTLength]; + SRM_Restriction_Data srm_restriction_data; +}; + +struct Test_PST_Report { + Test_PST_Report(const std::string& pst_in, + OEMCrypto_Usage_Entry_Status status_in); + + OEMCrypto_Usage_Entry_Status status; + int64_t seconds_since_license_received; + int64_t seconds_since_first_decrypt; + int64_t seconds_since_last_decrypt; + std::string pst; + int64_t time_created; +}; + +struct EntitledContentKeyData { + uint8_t entitlement_key_id[kTestKeyIdMaxLength]; + size_t entitlement_key_id_length; + uint8_t content_key_id[kTestKeyIdMaxLength]; + size_t content_key_id_length; + uint8_t content_key_data_iv[KEY_IV_SIZE]; + uint8_t content_key_data[KEY_SIZE]; + uint8_t encrypted_content_key_data[KEY_SIZE]; + size_t key_index; // Index into the license's key array. Only for testing. +}; + +// returns 1 on success, -1 if not supported, or 0 if other failure. +int GetRandBytes(unsigned char* buf, int num); + +void GenerateSimpleSampleDescription(const std::vector& in, + std::vector& out, + OEMCrypto_SampleDescription* sample, + OEMCrypto_SubSampleDescription* subsample); + +// Increment counter for AES-CTR. The CENC spec specifies we increment only +// the low 64 bits of the IV counter, and leave the high 64 bits alone. This +// is different from the OpenSSL implementation, so we implement the CTR loop +// ourselves. +void ctr128_inc64(int64_t increaseBy, uint8_t* iv); + +// Some compilers don't like the macro htonl within an ASSERT_EQ. +uint32_t htonl_fnc(uint32_t x); + +// Prints error string from BoringSSL +void dump_boringssl_error(); + +class Session; +// The prototype of the OEMCrypto function to prepare and sign a request. +typedef OEMCryptoResult (*PrepAndSignRequest_t)( + OEMCrypto_SESSION session, uint8_t* message, size_t message_length, + size_t* core_message_length, uint8_t* signature, size_t* signature_length); + +// A RoundTrip helps generate and verify a request message, helps generate the +// corresponding response, and then helps verify loading the response. +template +class RoundTrip { + public: + RoundTrip() = delete; + RoundTrip(Session* session) + : session_(session), + core_request_(), + core_response_(), + response_data_(), + encrypted_response_data_(), + required_message_size_(0) {} + virtual ~RoundTrip() {} + + // Have OEMCrypto sign a request message and then verify the signature and the + // core message. + virtual void SignAndVerifyRequest(); + // Create a default |response_data| and |core_response|. + virtual void CreateDefaultResponse() = 0; + // Copy fields from |response_data| to |padded_response_data|, encrypting + // those that should be encrypted. Serialize the core message. Then sign the + // response. + virtual void EncryptAndSignResponse() = 0; + // Attempt to load the response and return the error. Short buffer errors are + // handled by LoadResponse, not the caller. All other errors should be + // handled by the caller. + virtual OEMCryptoResult LoadResponse() { return LoadResponse(session_); } + // As with LoadResponse, but load into a different session. + virtual OEMCryptoResult LoadResponse(Session* session) = 0; + + // Accessors are all read/write because tests modify default values. + Session* session() { return session_; } + void set_session(Session* session) { session_ = session; } + CoreRequest& core_request() { return core_request_; } + CoreResponse& core_response() { return core_response_; } + ResponseData& response_data() { return response_data_; } + std::vector& encrypted_response_buffer() { + return encrypted_response_; + } + + // Set the size of the buffer used the encrypted license. + void set_message_size(size_t size) { required_message_size_ = size; } + std::vector& response_signature() { return response_signature_; } + const std::string& serialized_core_message() const { + return serialized_core_message_; + } + + protected: + // ---------------------------------------------------------------------- + // Specialized functionality for each message type. + + // Verify the signature of the request. + virtual void VerifyRequestSignature( + const vector& data, const vector& generated_signature, + size_t core_message_length) = 0; + // Verify the values of the core response. + virtual void FillAndVerifyCoreRequest( + const std::string& core_message_string) = 0; + // Find the given pointer in the response_data_. + virtual OEMCrypto_Substring FindSubstring(const void* pointer, size_t length); + + // ---------------------------------------------------------------------- + // Member variables. + Session* session_; + CoreRequest core_request_; + CoreResponse core_response_; + ResponseData response_data_, encrypted_response_data_; + // Message buffers will be at least this big. Tests for loading and signing + // messages will increase all buffers to this size. + size_t required_message_size_; + std::vector response_signature_; + std::string serialized_core_message_; + std::vector encrypted_response_; +}; + +class ProvisioningRoundTrip + : public RoundTrip< + /* CoreRequest */ oemcrypto_core_message::ODK_ProvisioningRequest, + OEMCrypto_PrepAndSignProvisioningRequest, + /* CoreResponse */ ODK_ParsedProvisioning, + /* ResponseData */ RSAPrivateKeyMessage> { + public: + ProvisioningRoundTrip(Session* session, + const std::vector& encoded_rsa_key) + : RoundTrip(session), + allowed_schemes_(kSign_RSASSA_PSS), + encryptor_(), + encoded_rsa_key_(encoded_rsa_key) {} + // Prepare the session for signing the request. + virtual void PrepareSession(const wvoec::WidevineKeybox& keybox); + void CreateDefaultResponse() override; + void EncryptAndSignResponse() override; + OEMCryptoResult LoadResponse() override { return LoadResponse(session_); } + OEMCryptoResult LoadResponse(Session* session) override; + void VerifyLoadFailed(); + const std::vector& encoded_rsa_key() { return encoded_rsa_key_; } + const std::vector& wrapped_rsa_key() { return wrapped_rsa_key_; } + void set_allowed_schemes(uint32_t allowed_schemes) { + allowed_schemes_ = allowed_schemes; + } + + protected: + void VerifyRequestSignature(const vector& data, + const vector& generated_signature, + size_t core_message_length) override; + // Verify the values of the core response. + virtual void FillAndVerifyCoreRequest( + const std::string& core_message_string) override; + // Load the response, without the retry. Called by LoadResponse. + OEMCryptoResult LoadResponseNoRetry(Session* session, + size_t* wrapped_key_length); + // This takes a pointer in the response_data_ and remaps it to the same + // pointer within the encrypted message. This is used for backwards + // compatibliity testing, so that a v15 oemcrypto will accept range checks. + template + const T* RemapPointer(const T* response_pointer) const; + + uint32_t allowed_schemes_; + Encryptor encryptor_; + // The message key used for Prov 3.0. + std::vector message_key_; + std::vector encrypted_message_key_; + std::vector encoded_rsa_key_; + std::vector wrapped_rsa_key_; +}; + +class LicenseRoundTrip + : public RoundTrip< + /* CoreRequest */ oemcrypto_core_message::ODK_LicenseRequest, + OEMCrypto_PrepAndSignLicenseRequest, + /* CoreResponse */ ODK_ParsedLicense, + /* ResponseData */ MessageData> { + public: + LicenseRoundTrip(Session* session) + : RoundTrip(session), + control_(wvoec::kControlNonceEnabled), + num_keys_(4), + pst_(""), + minimum_srm_version_(0), + update_mac_keys_(true), + api_version_(kCurrentAPI), + expect_request_has_correct_nonce_(true), + license_type_(OEMCrypto_ContentLicense), + request_hash_() {} + void CreateDefaultResponse() override; + // Create a license with four keys. Each key is responsible for one of generic + // encrypt (key 0), decrypt (key 1), sign (key 2) and verify (key 3). Each key + // is allowed only one type of operation. + void CreateResponseWithGenericCryptoKeys(); + // Fill the |core_response| substrings. + virtual void FillCoreResponseSubstrings(); + void EncryptAndSignResponse() override; + OEMCryptoResult LoadResponse() override { return LoadResponse(session_); } + OEMCryptoResult LoadResponse(Session* session) override; + // Reload an offline license into a different session. This derives new mac + // keys and then calls LoadResponse. + OEMCryptoResult ReloadResponse(Session* session); + void VerifyTestKeys(Session* session); + // Set the default key control block for all keys. This is used in + // CreateDefaultResponse. The key control block determines the restrictions + // that OEMCrypto should place on a key's use. For example, it specifies the + // minimum HDCP requirement and whether the key can only be used with a secure + // video path. See the section "Key Control Block" in the document "Widevine + // Modular DRM Security Integration Guide for CENC". + void set_control(uint32_t control) { control_ = control; } + uint32_t control() const { return control_; } + // Set the number of keys to use in the license. + void set_num_keys(uint32_t num_keys) { num_keys_ = num_keys; } + uint32_t num_keys() const { return num_keys_; } + // Get/Set the pst for the license and usage table entry. + const std::string& pst() const { return pst_; } + void set_pst(const std::string& pst) { pst_ = pst; } + // Set the minimum SRM version for the license. + void set_minimum_srm_version(uint32_t minimum_srm_version) { + minimum_srm_version_ = minimum_srm_version; + } + // Change the hash of the core request. This should cause the response to be + // rejected. + void BreakRequestHash() { request_hash_[3] ^= 42; } + // Set the API version for the license itself. This will be used in + // CreateDefaultResponse. + void set_api_version(uint32_t api_version) { api_version_ = api_version; } + uint32_t api_version() const { return api_version_; } + void set_update_mac_keys(bool update_mac_keys) { + update_mac_keys_ = update_mac_keys; + } + void set_license_type(OEMCrypto_LicenseType license_type) { + license_type_ = license_type; + } + // Skip the nonce check when verifying the license request. + void skip_nonce_check() { expect_request_has_correct_nonce_ = false; } + // This sets the key id of the specified key to the specified string. + // This is used to test with different key id lengths. + void SetKeyId(size_t index, const string& key_id); + + protected: + void VerifyRequestSignature(const vector& data, + const vector& generated_signature, + size_t core_message_length) override; + // Verify the values of the core response. + virtual void FillAndVerifyCoreRequest( + const std::string& core_message_string) override; + + // The default key control bits used with CreateDefaultResponse. + uint32_t control_; + // The number of keys in the license response. + uint32_t num_keys_; + // If non-empty, the license's provider session token. + std::string pst_; + // If non-zero, the minimum SRM version. + uint32_t minimum_srm_version_; + // If true, the license contains new mac keys for signing renewals. + bool update_mac_keys_; + // API version for the license itself. If this is 0 when the license request + // is signed, it will be set to the same as OEMCrypto's API version. It may + // be set to a lower value in order to test backwards compatibility. + uint32_t api_version_; + // If true, then we expect the nonce in the core request to match that in + // session. This is usually true, but when we are testing how OEMCrypto + // handles a bad nonce, we don't want to. + bool expect_request_has_correct_nonce_; + // Whether this is a content license or an entitlement license. Used in + // CreateDefaultResponse. + OEMCrypto_LicenseType license_type_; + uint8_t request_hash_[ODK_SHA256_HASH_SIZE]; +}; + +class RenewalRoundTrip + : public RoundTrip< + /* CoreRequest */ oemcrypto_core_message::ODK_RenewalRequest, + OEMCrypto_PrepAndSignRenewalRequest, + // Renewal response info is same as request: + /* CoreResponse */ oemcrypto_core_message::ODK_RenewalRequest, + /* ResponseData */ MessageData> { + public: + RenewalRoundTrip(LicenseRoundTrip* license_messages) + : RoundTrip(license_messages->session()), + license_messages_(license_messages), + refresh_object_(), + renewal_duration_seconds_( + license_messages->core_response() + .timer_limits.initial_renewal_duration_seconds), + is_release_(false) {} + void CreateDefaultResponse() override; + void EncryptAndSignResponse() override; + OEMCryptoResult LoadResponse() override { return LoadResponse(session_); } + OEMCryptoResult LoadResponse(Session* session) override; + uint64_t renewal_duration_seconds() const { + return renewal_duration_seconds_; + } + void set_renewal_duration_seconds(uint64_t renewal_duration_seconds) { + renewal_duration_seconds_ = renewal_duration_seconds; + } + void set_is_release(bool is_release) { is_release_ = is_release; } + + protected: + void VerifyRequestSignature(const vector& data, + const vector& generated_signature, + size_t core_message_length) override; + // Verify the values of the core response. + virtual void FillAndVerifyCoreRequest( + const std::string& core_message_string) override; + LicenseRoundTrip* license_messages_; + OEMCrypto_KeyRefreshObject refresh_object_; + uint64_t renewal_duration_seconds_; + bool is_release_; // If this is a license release, and not a real renewal. +}; + +class EntitledMessage { + public: + EntitledMessage(LicenseRoundTrip* license_messages) + : license_messages_(license_messages), num_keys_() {} + void FillKeyArray(); + void MakeOneKey(size_t entitlement_key_index); + void SetEntitledKeySession(uint32_t key_session) { + key_session_ = key_session; + } + void LoadKeys(OEMCryptoResult expected_sts); + void set_num_keys(uint32_t num_keys) { num_keys_ = num_keys; } + uint32_t num_keys() const { return num_keys_; } + void SetEntitlementKeyId(unsigned int index, const std::string& key_id); + void SetContentKeyId(unsigned int index, const std::string& key_id); + // Verify that key control blocks of the loaded keys. + void VerifyEntitlementTestKeys(); + + private: + // Find the offset of the give pointer, relative to |entitled_key_data_|. + OEMCrypto_Substring FindSubstring(const void* ptr, size_t size); + + LicenseRoundTrip* license_messages_; + uint32_t num_keys_; + // Clear Entitlement key data. This is the backing data for + // |entitled_key_array_|. + EntitledContentKeyData entitled_key_data_[kMaxNumKeys]; + // Entitled key object. Pointers are backed by |entitled_key_data_|. + OEMCrypto_EntitledContentKeyObject entitled_key_array_[kMaxNumKeys]; + uint32_t key_session_; +}; + +class Session { + public: + Session(); + ~Session(); + + // Returns the most recently generated nonce. + // Valid after call to GenerateNonce. + uint32_t nonce() const { return nonce_; } + // Valid after call to open(). + uint32_t session_id() const { return (uint32_t)session_id_; } + // Call OEMCrypto_OpenSession, with GTest ASSERTs. + void open(); + // Call OEMCrypto_CloseSession, with GTest ASSERTs. + void close(); + // Artifically set session id without calling OEMCrypto_OpenSession. This is + // used in core/test/generic_crypto_unittest.cpp. + void SetSessionId(uint32_t session_id); + uint32_t GetOecSessionId() { return session_id_; } + // Generates one nonce. If error_counter is null, this will sleep 1 second + // and try again if a nonce flood has been detected. If error_counter is + // not null, it will be incremented when a nonce flood is detected. + void GenerateNonce(int* error_counter = nullptr); + // Fill the vectors with test context which generate known mac and enc keys. + void FillDefaultContext(vector* mac_context, + vector* enc_context); + // Generate known mac and enc keys using OEMCrypto_GenerateDerivedKeys and + // also fill out enc_key_, mac_key_server_, and mac_key_client_. + void GenerateDerivedKeysFromKeybox(const wvoec::WidevineKeybox& keybox); + // Generate known mac and enc keys using OEMCrypto_DeriveKeysFromSessionKey + // and also fill out enc_key_, mac_key_server_, and mac_key_client_. + void GenerateDerivedKeysFromSessionKey(); + // Encrypt a block of data using CTR mode. + void EncryptCTR(const vector& in_buffer, const uint8_t* key, + const uint8_t* starting_iv, vector* out_buffer); + // Encrypt some data and pass to OEMCrypto_DecryptCENC to verify decryption. + void TestDecryptCTR(bool select_key_first = true, + OEMCryptoResult expected_result = OEMCrypto_SUCCESS, + int key_index = 0); + // Verify that an attempt to select an expired key either succeeds, or gives + // an actionable error code. + void TestSelectExpired(unsigned int key_index); + // Calls OEMCrypto_GetOEMPublicCertificate and OEMCrypto_LoadOEMPrivateKey and + // loads the OEM cert's public rsa key into public_rsa_. + void LoadOEMCert(bool verify_cert = false); + // Calls OEMCrypto_RewrapDeviceRSAKey with the given provisioning response + // message. If force is true, we assert that the key loads successfully. + void RewrapRSAKey(const struct RSAPrivateKeyMessage& encrypted, + size_t message_size, const std::vector& signature, + vector* wrapped_key, bool force); + // Loads the specified RSA public key into public_rsa_. If rsa_key is null, + // the default test key is loaded. + void PreparePublicKey(const uint8_t* rsa_key = nullptr, + size_t rsa_key_length = 0); + // Verifies the given signature is from the given message and RSA key, pkey. + static bool VerifyPSSSignature(EVP_PKEY* pkey, const uint8_t* message, + size_t message_length, + const uint8_t* signature, + size_t signature_length); + // Verify that the message was signed by the private key associated with + // |public_rsa_| using the specified padding scheme. + void VerifyRSASignature(const vector& message, + const uint8_t* signature, size_t signature_length, + RSA_Padding_Scheme padding_scheme); + // Encrypts a known session key with public_rsa_ for use in future calls to + // OEMCrypto_DeriveKeysFromSessionKey or OEMCrypto_RewrapDeviceRSAKey30. + // The unencrypted session key is stored in session_key. + bool GenerateRSASessionKey(vector* session_key, + vector* enc_session_key); + // Calls OEMCrypto_RewrapDeviceRSAKey30 with the given provisioning response + // message. If force is true, we assert that the key loads successfully. + void RewrapRSAKey30(const struct RSAPrivateKeyMessage& encrypted, + const std::vector& encrypted_message_key, + vector* wrapped_key, bool force); + // Loads the specified wrapped_rsa_key into OEMCrypto, and then runs + // GenerateDerivedKeysFromSessionKey to install known encryption and mac keys. + void InstallRSASessionTestKey(const vector& wrapped_rsa_key); + // Creates a new usage entry, and keeps track of the index. + // If status is null, we expect success, otherwise status is set to the + // return value. + void CreateNewUsageEntry(OEMCryptoResult* status = nullptr); + // Copy encrypted usage entry from other session, and then load it. + // This session must already be open. + void LoadUsageEntry(uint32_t index, const vector& buffer); + // Copy encrypted usage entry from other session. + // This session must already be open. + void LoadUsageEntry(const Session& other) { + LoadUsageEntry(other.usage_entry_number(), other.encrypted_usage_entry()); + } + // Reload previously used usage entry. + void ReloadUsageEntry() { LoadUsageEntry(*this); } + // Update the usage entry and save the header to the specified buffer. + void UpdateUsageEntry(std::vector* header_buffer); + // The usage entry number for this session's usage entry. + uint32_t usage_entry_number() const { return usage_entry_number_; } + void set_usage_entry_number(uint32_t v) { usage_entry_number_ = v; } + // The encrypted buffer holding the recently updated and saved usage entry. + const vector& encrypted_usage_entry() const { + return encrypted_usage_entry_; + } + // Generates a usage report for the specified pst. If there is success, + // the report's signature is verified, and several fields are given sanity + // checks. If |other| is not null, then the mac keys are copied from other in + // order to verify signatures. + void GenerateReport(const std::string& pst, + OEMCryptoResult expected_result = OEMCrypto_SUCCESS, + Session* other = 0); + // Move this usage entry to a new index. + void MoveUsageEntry(uint32_t new_index, std::vector* header_buffer, + OEMCryptoResult expect_result = OEMCrypto_SUCCESS); + // PST used in FillSimpleMesage. + string pst() const { return pst_; } + // Returns a pointer-like thing to the usage report generated by the previous + // call to GenerateReport. + wvutil::Unpacked_PST_Report pst_report() { + return wvutil::Unpacked_PST_Report(&pst_report_buffer_[0]); + } + // Verify the values in the PST report. The signature should have been + // verified in GenerateReport, above. + void VerifyPST(const Test_PST_Report& report); + // Verify the Usage Report. If any time is greater than 10 minutes, it is + // assumed to be an absolute time, and time_since will be computed relative to + // now. + void VerifyReport(Test_PST_Report report, + int64_t time_license_received = 0, + int64_t time_first_decrypt = 0, + int64_t time_last_decrypt = 0); + // Create an entry in the old usage table based on the given report. + void CreateOldEntry(const Test_PST_Report &report); + // Create a new entry and copy the old entry into it. Then very the report + // is right. + void CopyAndVerifyOldEntry(const Test_PST_Report &report, + std::vector* header_buffer); + + // The unencrypted license response or license renewal response. + MessageData& license() { return license_; } + + void set_license(const MessageData& license) { license_ = license; } + + const KeyDeriver& key_deriver() const { return key_deriver_; } + void set_mac_keys(const uint8_t* mac_keys) { + key_deriver_.set_mac_keys(mac_keys); + } + + private: + // This compares the actual result with the expected result. If OEMCrypto is + // an older version, we allow it to report an equivalent error code. + void TestDecryptResult(OEMCryptoResult expected_result, + OEMCryptoResult actual_select_result, + OEMCryptoResult actual_decryt_result); + + bool open_; + bool forced_session_id_; + OEMCrypto_SESSION session_id_; + KeyDeriver key_deriver_; + uint32_t nonce_; + RSA* public_rsa_; + vector pst_report_buffer_; + MessageData license_; + + vector encrypted_usage_entry_; + uint32_t usage_entry_number_; + string pst_; +}; +} // namespace wvoec + +#endif // CDM_OEC_SESSION_UTIL_H_ diff --git a/oemcrypto/test/oec_test_data.h b/oemcrypto/test/oec_test_data.h new file mode 100644 index 0000000..280eb4d --- /dev/null +++ b/oemcrypto/test/oec_test_data.h @@ -0,0 +1,688 @@ +// 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. +// +// Test data for OEMCrypto unit tests. +// +#ifndef CDM_OEC_TEST_DATA_H_ +#define CDM_OEC_TEST_DATA_H_ + +#include + +#include "OEMCryptoCAS.h" +#include "oemcrypto_types.h" + + +namespace wvoec { + +// This is a test keybox. It will not be accepted by production systems. By +// using a known keybox for these tests, the results for a given set of inputs +// to a test are predictable and can be compared to the actual results. +static const WidevineKeybox kTestKeybox = { + // Sample keybox used for test vectors + { + // deviceID = WidevineTestOnlyKeybox000 + 0x57, 0x69, 0x64, 0x65, 0x76, 0x69, 0x6e, 0x65, + 0x54, 0x65, 0x73, 0x74, 0x4f, 0x6e, 0x6c, 0x79, + 0x4b, 0x65, 0x79, 0x62, 0x6f, 0x78, 0x30, 0x30, + 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }, { + // key + 0xe4, 0xff, 0x57, 0x4c, 0x32, 0x2e, 0xf5, 0x34, + 0x26, 0x21, 0x2c, 0xb3, 0xed, 0x37, 0xf3, 0x5e, + }, { + // data (system ID 7912 = 1EE8). + 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x1e, 0xe8, + 0xca, 0x1e, 0x71, 0x7c, 0xfb, 0xe8, 0xa3, 0x94, + 0x52, 0x0a, 0x6b, 0x71, 0x37, 0xd2, 0x69, 0xfa, + 0x5a, 0xc6, 0xb5, 0x4c, 0x6b, 0x46, 0x63, 0x9b, + 0xbe, 0x80, 0x3d, 0xbb, 0x4f, 0xf7, 0x4c, 0x5f, + 0x6f, 0x55, 0x0e, 0x3d, 0x3d, 0x9a, 0xcf, 0x81, + 0x12, 0x5d, 0x52, 0xe0, 0x47, 0x8c, 0xda, 0x0b, + 0xf4, 0x31, 0x41, 0x13, 0xd0, 0xd5, 0x2d, 0xa0, + 0x5b, 0x20, 0x9a, 0xed, 0x51, 0x5d, 0x13, 0xd6, + }, { + // magic + 0x6b, 0x62, 0x6f, 0x78, + }, { + // Crc + 0x39, 0xf2, 0x94, 0xa7, + } +}; + +// A 2048 bit RSA key in PKCS#8 PrivateKeyInfo format +// Used to verify the functions that manipulate RSA keys. +static const uint8_t kTestRSAPKCS8PrivateKeyInfo2_2048[] = { + 0x30, 0x82, 0x04, 0xbc, 0x02, 0x01, 0x00, 0x30, + 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, + 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x04, 0x82, + 0x04, 0xa6, 0x30, 0x82, 0x04, 0xa2, 0x02, 0x01, + 0x00, 0x02, 0x82, 0x01, 0x01, 0x00, 0xa7, 0x00, + 0x36, 0x60, 0x65, 0xdc, 0xbd, 0x54, 0x5a, 0x2a, + 0x40, 0xb4, 0xe1, 0x15, 0x94, 0x58, 0x11, 0x4f, + 0x94, 0x58, 0xdd, 0xde, 0xa7, 0x1f, 0x3c, 0x2c, + 0xe0, 0x88, 0x09, 0x29, 0x61, 0x57, 0x67, 0x5e, + 0x56, 0x7e, 0xee, 0x27, 0x8f, 0x59, 0x34, 0x9a, + 0x2a, 0xaa, 0x9d, 0xb4, 0x4e, 0xfa, 0xa7, 0x6a, + 0xd4, 0xc9, 0x7a, 0x53, 0xc1, 0x4e, 0x9f, 0xe3, + 0x34, 0xf7, 0x3d, 0xb7, 0xc9, 0x10, 0x47, 0x4f, + 0x28, 0xda, 0x3f, 0xce, 0x31, 0x7b, 0xfd, 0x06, + 0x10, 0xeb, 0xf7, 0xbe, 0x92, 0xf9, 0xaf, 0xfb, + 0x3e, 0x68, 0xda, 0xee, 0x1a, 0x64, 0x4c, 0xf3, + 0x29, 0xf2, 0x73, 0x9e, 0x39, 0xd8, 0xf6, 0x6f, + 0xd8, 0xb2, 0x80, 0x82, 0x71, 0x8e, 0xb5, 0xa4, + 0xf2, 0xc2, 0x3e, 0xcd, 0x0a, 0xca, 0xb6, 0x04, + 0xcd, 0x9a, 0x13, 0x8b, 0x54, 0x73, 0x54, 0x25, + 0x54, 0x8c, 0xbe, 0x98, 0x7a, 0x67, 0xad, 0xda, + 0xb3, 0x4e, 0xb3, 0xfa, 0x82, 0xa8, 0x4a, 0x67, + 0x98, 0x56, 0x57, 0x54, 0x71, 0xcd, 0x12, 0x7f, + 0xed, 0xa3, 0x01, 0xc0, 0x6a, 0x8b, 0x24, 0x03, + 0x96, 0x88, 0xbe, 0x97, 0x66, 0x2a, 0xbc, 0x53, + 0xc9, 0x83, 0x06, 0x51, 0x5a, 0x88, 0x65, 0x13, + 0x18, 0xe4, 0x3a, 0xed, 0x6b, 0xf1, 0x61, 0x5b, + 0x4c, 0xc8, 0x1e, 0xf4, 0xc2, 0xae, 0x08, 0x5e, + 0x2d, 0x5f, 0xf8, 0x12, 0x7f, 0xa2, 0xfc, 0xbb, + 0x21, 0x18, 0x30, 0xda, 0xfe, 0x40, 0xfb, 0x01, + 0xca, 0x2e, 0x37, 0x0e, 0xce, 0xdd, 0x76, 0x87, + 0x82, 0x46, 0x0b, 0x3a, 0x77, 0x8f, 0xc0, 0x72, + 0x07, 0x2c, 0x7f, 0x9d, 0x1e, 0x86, 0x5b, 0xed, + 0x27, 0x29, 0xdf, 0x03, 0x97, 0x62, 0xef, 0x44, + 0xd3, 0x5b, 0x3d, 0xdb, 0x9c, 0x5e, 0x1b, 0x7b, + 0x39, 0xb4, 0x0b, 0x6d, 0x04, 0x6b, 0xbb, 0xbb, + 0x2c, 0x5f, 0xcf, 0xb3, 0x7a, 0x05, 0x02, 0x03, + 0x01, 0x00, 0x01, 0x02, 0x82, 0x01, 0x00, 0x5e, + 0x79, 0x65, 0x49, 0xa5, 0x76, 0x79, 0xf9, 0x05, + 0x45, 0x0f, 0xf4, 0x03, 0xbd, 0xa4, 0x7d, 0x29, + 0xd5, 0xde, 0x33, 0x63, 0xd8, 0xb8, 0xac, 0x97, + 0xeb, 0x3f, 0x5e, 0x55, 0xe8, 0x7d, 0xf3, 0xe7, + 0x3b, 0x5c, 0x2d, 0x54, 0x67, 0x36, 0xd6, 0x1d, + 0x46, 0xf5, 0xca, 0x2d, 0x8b, 0x3a, 0x7e, 0xdc, + 0x45, 0x38, 0x79, 0x7e, 0x65, 0x71, 0x5f, 0x1c, + 0x5e, 0x79, 0xb1, 0x40, 0xcd, 0xfe, 0xc5, 0xe1, + 0xc1, 0x6b, 0x78, 0x04, 0x4e, 0x8e, 0x79, 0xf9, + 0x0a, 0xfc, 0x79, 0xb1, 0x5e, 0xb3, 0x60, 0xe3, + 0x68, 0x7b, 0xc6, 0xef, 0xcb, 0x71, 0x4c, 0xba, + 0xa7, 0x79, 0x5c, 0x7a, 0x81, 0xd1, 0x71, 0xe7, + 0x00, 0x21, 0x13, 0xe2, 0x55, 0x69, 0x0e, 0x75, + 0xbe, 0x09, 0xc3, 0x4f, 0xa9, 0xc9, 0x68, 0x22, + 0x0e, 0x97, 0x8d, 0x89, 0x6e, 0xf1, 0xe8, 0x88, + 0x7a, 0xd1, 0xd9, 0x09, 0x5d, 0xd3, 0x28, 0x78, + 0x25, 0x0b, 0x1c, 0x47, 0x73, 0x25, 0xcc, 0x21, + 0xb6, 0xda, 0xc6, 0x24, 0x5a, 0xd0, 0x37, 0x14, + 0x46, 0xc7, 0x94, 0x69, 0xe4, 0x43, 0x6f, 0x47, + 0xde, 0x00, 0x33, 0x4d, 0x8f, 0x95, 0x72, 0xfa, + 0x68, 0x71, 0x17, 0x66, 0x12, 0x1a, 0x87, 0x27, + 0xf7, 0xef, 0x7e, 0xe0, 0x35, 0x58, 0xf2, 0x4d, + 0x6f, 0x35, 0x01, 0xaa, 0x96, 0xe2, 0x3d, 0x51, + 0x13, 0x86, 0x9c, 0x79, 0xd0, 0xb7, 0xb6, 0x64, + 0xe8, 0x86, 0x65, 0x50, 0xbf, 0xcc, 0x27, 0x53, + 0x1f, 0x51, 0xd4, 0xca, 0xbe, 0xf5, 0xdd, 0x77, + 0x70, 0x98, 0x0f, 0xee, 0xa8, 0x96, 0x07, 0x5f, + 0x45, 0x6a, 0x7a, 0x0d, 0x03, 0x9c, 0x4f, 0x29, + 0xf6, 0x06, 0xf3, 0x5d, 0x58, 0x6c, 0x47, 0xd0, + 0x96, 0xa9, 0x03, 0x17, 0xbb, 0x4e, 0xc9, 0x21, + 0xe0, 0xac, 0xcd, 0x78, 0x78, 0xb2, 0xfe, 0x81, + 0xb2, 0x51, 0x53, 0xa6, 0x1f, 0x98, 0x45, 0x02, + 0x81, 0x81, 0x00, 0xcf, 0x73, 0x8c, 0xbe, 0x6d, + 0x45, 0x2d, 0x0c, 0x0b, 0x5d, 0x5c, 0x6c, 0x75, + 0x78, 0xcc, 0x35, 0x48, 0xb6, 0x98, 0xf1, 0xb9, + 0x64, 0x60, 0x8c, 0x43, 0xeb, 0x85, 0xab, 0x04, + 0xb6, 0x7d, 0x1b, 0x71, 0x75, 0x06, 0xe2, 0xda, + 0x84, 0x68, 0x2e, 0x7f, 0x4c, 0xe3, 0x73, 0xb4, + 0xde, 0x51, 0x4b, 0xb6, 0x51, 0x86, 0x7b, 0xd0, + 0xe6, 0x4d, 0xf3, 0xd1, 0xcf, 0x1a, 0xfe, 0x7f, + 0x3a, 0x83, 0xba, 0xb3, 0xe1, 0xff, 0x54, 0x13, + 0x93, 0xd7, 0x9c, 0x27, 0x80, 0xb7, 0x1e, 0x64, + 0x9e, 0xf7, 0x32, 0x2b, 0x46, 0x29, 0xf7, 0xf8, + 0x18, 0x6c, 0xf7, 0x4a, 0xbe, 0x4b, 0xee, 0x96, + 0x90, 0x8f, 0xa2, 0x16, 0x22, 0x6a, 0xcc, 0x48, + 0x06, 0x74, 0x63, 0x43, 0x7f, 0x27, 0x22, 0x44, + 0x3c, 0x2d, 0x3b, 0x62, 0xf1, 0x1c, 0xb4, 0x27, + 0x33, 0x85, 0x26, 0x60, 0x48, 0x16, 0xcb, 0xef, + 0xf8, 0xcd, 0x37, 0x02, 0x81, 0x81, 0x00, 0xce, + 0x15, 0x43, 0x6e, 0x4b, 0x0f, 0xf9, 0x3f, 0x87, + 0xc3, 0x41, 0x45, 0x97, 0xb1, 0x49, 0xc2, 0x19, + 0x23, 0x87, 0xe4, 0x24, 0x1c, 0x64, 0xe5, 0x28, + 0xcb, 0x43, 0x10, 0x14, 0x14, 0x0e, 0x19, 0xcb, + 0xbb, 0xdb, 0xfd, 0x11, 0x9d, 0x17, 0x68, 0x78, + 0x6d, 0x61, 0x70, 0x63, 0x3a, 0xa1, 0xb3, 0xf3, + 0xa7, 0x5b, 0x0e, 0xff, 0xb7, 0x61, 0x11, 0x54, + 0x91, 0x99, 0xe5, 0x91, 0x32, 0x2d, 0xeb, 0x3f, + 0xd8, 0x3e, 0xf7, 0xd4, 0xcb, 0xd2, 0xa3, 0x41, + 0xc1, 0xee, 0xc6, 0x92, 0x13, 0xeb, 0x7f, 0x42, + 0x58, 0xf4, 0xd0, 0xb2, 0x74, 0x1d, 0x8e, 0x87, + 0x46, 0xcd, 0x14, 0xb8, 0x16, 0xad, 0xb5, 0xbd, + 0x0d, 0x6c, 0x95, 0x5a, 0x16, 0xbf, 0xe9, 0x53, + 0xda, 0xfb, 0xed, 0x83, 0x51, 0x67, 0xa9, 0x55, + 0xab, 0x54, 0x02, 0x95, 0x20, 0xa6, 0x68, 0x17, + 0x53, 0xa8, 0xea, 0x43, 0xe5, 0xb0, 0xa3, 0x02, + 0x81, 0x80, 0x67, 0x9c, 0x32, 0x83, 0x39, 0x57, + 0xff, 0x73, 0xb0, 0x89, 0x64, 0x8b, 0xd6, 0xf0, + 0x0a, 0x2d, 0xe2, 0xaf, 0x30, 0x1c, 0x2a, 0x97, + 0xf3, 0x90, 0x9a, 0xab, 0x9b, 0x0b, 0x1b, 0x43, + 0x79, 0xa0, 0xa7, 0x3d, 0xe7, 0xbe, 0x8d, 0x9c, + 0xeb, 0xdb, 0xad, 0x40, 0xdd, 0xa9, 0x00, 0x80, + 0xb8, 0xe1, 0xb3, 0xa1, 0x6c, 0x25, 0x92, 0xe4, + 0x33, 0xb2, 0xbe, 0xeb, 0x4d, 0x74, 0x26, 0x5f, + 0x37, 0x43, 0x9c, 0x6c, 0x17, 0x76, 0x0a, 0x81, + 0x20, 0x82, 0xa1, 0x48, 0x2c, 0x2d, 0x45, 0xdc, + 0x0f, 0x62, 0x43, 0x32, 0xbb, 0xeb, 0x59, 0x41, + 0xf9, 0xca, 0x58, 0xce, 0x4a, 0x66, 0x53, 0x54, + 0xc8, 0x28, 0x10, 0x1e, 0x08, 0x71, 0x16, 0xd8, + 0x02, 0x71, 0x41, 0x58, 0xd4, 0x56, 0xcc, 0xf5, + 0xb1, 0x31, 0xa3, 0xed, 0x00, 0x85, 0x09, 0xbf, + 0x35, 0x95, 0x41, 0x29, 0x40, 0x19, 0x83, 0x35, + 0x24, 0x69, 0x02, 0x81, 0x80, 0x55, 0x10, 0x0b, + 0xcc, 0x3b, 0xa9, 0x75, 0x3d, 0x16, 0xe1, 0xae, + 0x50, 0x76, 0x63, 0x94, 0x49, 0x4c, 0xad, 0x10, + 0xcb, 0x47, 0x68, 0x7c, 0xf0, 0xe5, 0xdc, 0xb8, + 0x6a, 0xab, 0x8e, 0xf7, 0x9f, 0x08, 0x2c, 0x1b, + 0x8a, 0xa2, 0xb9, 0x8f, 0xce, 0xec, 0x5e, 0x61, + 0xa8, 0xcd, 0x1c, 0x87, 0x60, 0x4a, 0xc3, 0x1a, + 0x5f, 0xdf, 0x87, 0x26, 0xc6, 0xcb, 0x7c, 0x69, + 0xe4, 0x8b, 0x01, 0x06, 0x59, 0x22, 0xfa, 0x34, + 0x4b, 0x81, 0x87, 0x3c, 0x03, 0x6d, 0x02, 0x0a, + 0x77, 0xe6, 0x15, 0xd8, 0xcf, 0xa7, 0x68, 0x26, + 0x6c, 0xfa, 0x2b, 0xd9, 0x83, 0x5a, 0x2d, 0x0c, + 0x3b, 0x70, 0x1c, 0xd4, 0x48, 0xbe, 0xa7, 0x0a, + 0xd9, 0xbe, 0xdc, 0xc3, 0x0c, 0x21, 0x33, 0xb3, + 0x66, 0xff, 0x1c, 0x1b, 0xc8, 0x96, 0x76, 0xe8, + 0x6f, 0x44, 0x74, 0xbc, 0x9b, 0x1c, 0x7d, 0xc8, + 0xac, 0x21, 0xa8, 0x6e, 0x37, 0x02, 0x81, 0x80, + 0x2c, 0x7c, 0xad, 0x1e, 0x75, 0xf6, 0x69, 0x1d, + 0xe7, 0xa6, 0xca, 0x74, 0x7d, 0x67, 0xc8, 0x65, + 0x28, 0x66, 0xc4, 0x43, 0xa6, 0xbd, 0x40, 0x57, + 0xae, 0xb7, 0x65, 0x2c, 0x52, 0xf9, 0xe4, 0xc7, + 0x81, 0x7b, 0x56, 0xa3, 0xd2, 0x0d, 0xe8, 0x33, + 0x70, 0xcf, 0x06, 0x84, 0xb3, 0x4e, 0x44, 0x50, + 0x75, 0x61, 0x96, 0x86, 0x4b, 0xb6, 0x2b, 0xad, + 0xf0, 0xad, 0x57, 0xd0, 0x37, 0x0d, 0x1d, 0x35, + 0x50, 0xcb, 0x69, 0x22, 0x39, 0x29, 0xb9, 0x3a, + 0xd3, 0x29, 0x23, 0x02, 0x60, 0xf7, 0xab, 0x30, + 0x40, 0xda, 0x8e, 0x4d, 0x45, 0x70, 0x26, 0xf4, + 0xa2, 0x0d, 0xd0, 0x64, 0x5d, 0x47, 0x3c, 0x18, + 0xf4, 0xd4, 0x52, 0x95, 0x00, 0xae, 0x84, 0x6b, + 0x47, 0xb2, 0x3c, 0x82, 0xd3, 0x72, 0x53, 0xde, + 0x72, 0x2c, 0xf7, 0xc1, 0x22, 0x36, 0xd9, 0x18, + 0x56, 0xfe, 0x39, 0x28, 0x33, 0xe0, 0xdb, 0x03 }; + +// A 3072 bit RSA key in PKCS#8 PrivateKeyInfo format +// Used to verify the functions that manipulate RSA keys. +static const uint8_t kTestRSAPKCS8PrivateKeyInfo3_3072[] = { + 0x30, 0x82, 0x06, 0xfe, 0x02, 0x01, 0x00, 0x30, 0x0d, 0x06, 0x09, 0x2a, + 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x04, 0x82, + 0x06, 0xe8, 0x30, 0x82, 0x06, 0xe4, 0x02, 0x01, 0x00, 0x02, 0x82, 0x01, + 0x81, 0x00, 0xe3, 0x32, 0x2f, 0x0a, 0x94, 0x06, 0x46, 0x99, 0x0a, 0x58, + 0xda, 0xd0, 0x9b, 0x2b, 0xe4, 0x2a, 0x35, 0xdf, 0xb7, 0x9b, 0x5e, 0xbf, + 0xfb, 0xe5, 0x24, 0x47, 0x5a, 0x94, 0x06, 0x04, 0xe5, 0x43, 0xed, 0x37, + 0x33, 0x94, 0x09, 0xd0, 0xae, 0xad, 0x86, 0xb4, 0xc7, 0xc3, 0x56, 0x6f, + 0x88, 0x74, 0xfb, 0xab, 0xf7, 0xcf, 0xcb, 0xa6, 0x89, 0x48, 0x4a, 0x02, + 0x89, 0xcd, 0xfd, 0x83, 0x3d, 0x2a, 0x27, 0xc1, 0xa2, 0x99, 0x8e, 0xef, + 0xcf, 0x91, 0xd3, 0xb2, 0x96, 0xe7, 0x5f, 0x0c, 0xb3, 0x44, 0x6a, 0xcf, + 0xc1, 0x22, 0xb9, 0xe4, 0xd4, 0xc0, 0xf2, 0xc3, 0x8d, 0xe1, 0x43, 0x38, + 0x31, 0x9c, 0x56, 0x04, 0xd4, 0x9d, 0x41, 0x02, 0x31, 0xce, 0x7e, 0xc0, + 0x11, 0x24, 0x54, 0xb1, 0xa2, 0x99, 0x0e, 0xe2, 0x0c, 0x5b, 0x24, 0x94, + 0x85, 0xe8, 0x8c, 0x30, 0xbb, 0x12, 0x94, 0x74, 0x0f, 0x67, 0xe5, 0x69, + 0xa4, 0xc4, 0x59, 0xd6, 0x77, 0x96, 0xae, 0xc6, 0x00, 0xbe, 0xf5, 0xe6, + 0x1f, 0x71, 0x90, 0x6d, 0xdd, 0xfb, 0x7b, 0x42, 0xd0, 0xdf, 0x4b, 0x58, + 0xaf, 0x9c, 0xba, 0xcb, 0x35, 0x4b, 0xf3, 0x06, 0x3a, 0x20, 0x42, 0x97, + 0x96, 0x95, 0x47, 0xbe, 0x2d, 0xeb, 0x9a, 0xb6, 0xea, 0xe0, 0xc1, 0x1d, + 0x80, 0x61, 0x3e, 0x8e, 0x18, 0x66, 0xf4, 0x26, 0x77, 0xcf, 0x56, 0x27, + 0x8b, 0xde, 0x93, 0x94, 0x3e, 0x1d, 0xe4, 0x5f, 0x6d, 0xf2, 0x39, 0x03, + 0x15, 0x4f, 0x2e, 0x58, 0x59, 0x75, 0x19, 0xb9, 0x24, 0x87, 0xd4, 0xff, + 0x64, 0x82, 0x11, 0x10, 0x34, 0x30, 0x09, 0x39, 0x43, 0x9c, 0xd2, 0x3b, + 0x45, 0xdc, 0x85, 0x4f, 0x6d, 0xb7, 0xbb, 0x49, 0xda, 0x3b, 0x07, 0xa2, + 0x76, 0x56, 0xa0, 0xee, 0xa9, 0xa9, 0x52, 0xb7, 0xf1, 0xfd, 0xde, 0xa1, + 0x6f, 0x0e, 0x7f, 0x82, 0x3f, 0x9e, 0x3d, 0x46, 0xcd, 0x48, 0x55, 0xe8, + 0x59, 0x65, 0xd8, 0xc7, 0xe4, 0x6b, 0xe6, 0xc0, 0xdd, 0x6e, 0x5c, 0xb7, + 0x0c, 0xdb, 0x29, 0xad, 0x8e, 0xa4, 0x86, 0xe9, 0x4d, 0xad, 0x54, 0xf9, + 0x56, 0x06, 0x0e, 0xc4, 0x2b, 0x01, 0xd9, 0x86, 0x1f, 0x65, 0xbe, 0x0d, + 0x77, 0x8d, 0x9d, 0xff, 0x37, 0x97, 0x57, 0xc3, 0x06, 0x8a, 0x05, 0x80, + 0x78, 0xd3, 0xbd, 0x91, 0xa5, 0xc1, 0x11, 0x4d, 0x99, 0x1a, 0x83, 0xd7, + 0x30, 0x1c, 0x24, 0xac, 0xdf, 0x6c, 0xc1, 0x23, 0x60, 0x76, 0x54, 0xbf, + 0x2b, 0xac, 0x34, 0xf0, 0x35, 0x92, 0x0d, 0x36, 0x29, 0x09, 0x24, 0xd5, + 0x54, 0xe9, 0x68, 0x9c, 0x90, 0x07, 0x16, 0x86, 0xb1, 0xd0, 0x9b, 0xa5, + 0x86, 0x4e, 0xce, 0xbf, 0x30, 0x9d, 0x91, 0xd7, 0xd2, 0xa6, 0x4f, 0xbb, + 0xbb, 0x9d, 0x7c, 0x0f, 0x58, 0xaa, 0xf1, 0xd0, 0x90, 0x66, 0x20, 0x48, + 0x8f, 0x29, 0x02, 0x03, 0x01, 0x00, 0x01, 0x02, 0x82, 0x01, 0x81, 0x00, + 0x88, 0x41, 0x28, 0x85, 0x27, 0x91, 0x3b, 0xf5, 0xbc, 0x86, 0xdd, 0x74, + 0x0e, 0x1b, 0x9c, 0x92, 0xd4, 0x0c, 0x7f, 0x88, 0xe6, 0xa2, 0x2c, 0xe7, + 0x97, 0x82, 0x53, 0x88, 0x42, 0xb3, 0xdc, 0xeb, 0x87, 0xf0, 0x7b, 0x36, + 0x65, 0x4c, 0x89, 0xf5, 0xf7, 0xbb, 0xa3, 0xb2, 0x3a, 0xbc, 0x59, 0x12, + 0x0f, 0x7d, 0x6a, 0xf9, 0x6c, 0x21, 0x4c, 0x63, 0xd6, 0x3e, 0xff, 0x76, + 0x52, 0x7b, 0xca, 0xca, 0xe5, 0x5c, 0xf3, 0xaf, 0x34, 0x52, 0x0e, 0x22, + 0x5e, 0xdb, 0xd4, 0x34, 0x9e, 0x84, 0x77, 0x5e, 0xa8, 0xd0, 0x3f, 0xfc, + 0x1b, 0x90, 0x69, 0x27, 0xee, 0x6f, 0xe9, 0x3f, 0x17, 0x99, 0x33, 0xe7, + 0x96, 0x8e, 0xff, 0x13, 0xf0, 0x50, 0xe8, 0x9d, 0xf6, 0xd6, 0x29, 0x71, + 0xa8, 0x79, 0x80, 0x12, 0x5b, 0x22, 0xa6, 0x56, 0x62, 0xf1, 0xcf, 0xfd, + 0x4f, 0x56, 0x4a, 0x5b, 0x32, 0x3d, 0x08, 0xa0, 0x3e, 0xad, 0xc4, 0xeb, + 0x1d, 0x15, 0xca, 0x52, 0xcc, 0x2e, 0x63, 0x74, 0x22, 0xf5, 0x08, 0x16, + 0x8b, 0x8f, 0xd8, 0x79, 0x61, 0xcb, 0x08, 0x89, 0x62, 0x1e, 0xa5, 0xf3, + 0x50, 0xf3, 0x5d, 0xdb, 0x56, 0xbc, 0x7d, 0x4f, 0xab, 0xa0, 0x4d, 0xe6, + 0xe9, 0x47, 0xdd, 0x32, 0x57, 0x6f, 0x2c, 0x1d, 0xee, 0xb3, 0x4a, 0xb3, + 0x07, 0x59, 0x20, 0xb9, 0x5d, 0xe3, 0x54, 0x27, 0x3c, 0x7c, 0x2b, 0x1d, + 0x07, 0xff, 0x49, 0x93, 0xe2, 0xe3, 0xb2, 0x65, 0xf3, 0x69, 0xc1, 0x1c, + 0x2a, 0x75, 0x80, 0x16, 0x37, 0xe6, 0x00, 0x5b, 0xd3, 0x1b, 0xac, 0xca, + 0x8b, 0x8b, 0x98, 0x77, 0x81, 0x67, 0xe3, 0xdc, 0xbb, 0xc4, 0x3a, 0x45, + 0x15, 0xec, 0xd9, 0xad, 0xdb, 0x60, 0xcf, 0xe5, 0xd8, 0xd9, 0xfc, 0xcf, + 0xbe, 0x76, 0x2f, 0x5b, 0x60, 0xdb, 0x06, 0x62, 0x5b, 0x80, 0x7e, 0x53, + 0xde, 0x74, 0xb1, 0xa3, 0xb6, 0x9b, 0x14, 0xd7, 0x09, 0x65, 0x21, 0x1d, + 0xd5, 0xd3, 0x34, 0xca, 0x89, 0xe7, 0xbc, 0xf4, 0x48, 0x81, 0x6a, 0xcf, + 0x28, 0xbe, 0x74, 0x8b, 0x40, 0xad, 0x86, 0xcd, 0xa5, 0xd6, 0xfa, 0x64, + 0x9b, 0xd2, 0xd4, 0x17, 0x20, 0xd6, 0x0d, 0xbe, 0x95, 0xd4, 0xaf, 0xa5, + 0xde, 0x31, 0x0d, 0x6a, 0x90, 0xc6, 0xd0, 0x59, 0xd4, 0x8c, 0x81, 0x2d, + 0x9d, 0x09, 0xf1, 0x22, 0xf5, 0x30, 0x2d, 0xdf, 0x85, 0x54, 0x34, 0x8a, + 0xde, 0x3c, 0xce, 0xdb, 0x36, 0x9f, 0xcf, 0x12, 0x61, 0x0e, 0x99, 0x87, + 0x70, 0x51, 0x04, 0x91, 0x74, 0xc6, 0x88, 0x22, 0x75, 0x02, 0x8f, 0x7e, + 0xb5, 0x79, 0x48, 0x2f, 0xf3, 0x3b, 0xb8, 0x82, 0x3e, 0x7e, 0x45, 0xe5, + 0xb2, 0xc8, 0x4c, 0x12, 0x73, 0xb8, 0x92, 0x04, 0xd1, 0x9a, 0xae, 0xaa, + 0x08, 0xd9, 0x23, 0x54, 0x19, 0x46, 0xc8, 0x56, 0x5f, 0x5e, 0x10, 0xa1, + 0x02, 0x81, 0xc1, 0x00, 0xf6, 0x38, 0x88, 0x31, 0x06, 0x85, 0xd9, 0x00, + 0xf0, 0x6b, 0xd8, 0x7d, 0x76, 0x08, 0xc0, 0x69, 0x6a, 0xfb, 0xa4, 0xc8, + 0xdc, 0x6b, 0x00, 0xaf, 0xae, 0x52, 0x82, 0xe6, 0xba, 0xc9, 0x5e, 0xc9, + 0xb7, 0x7f, 0xa1, 0xc4, 0xcb, 0xa0, 0xbc, 0x66, 0x3c, 0x55, 0x6a, 0xea, + 0x6e, 0x42, 0xf1, 0x6b, 0xbd, 0xc4, 0xf2, 0x6b, 0x91, 0x11, 0x82, 0x20, + 0xc2, 0xe6, 0x9e, 0x96, 0x5c, 0x9a, 0x7e, 0xb3, 0x57, 0x45, 0x9c, 0x42, + 0x60, 0x4c, 0x04, 0x4f, 0x47, 0xfb, 0xa7, 0x68, 0x4e, 0x15, 0x43, 0x5a, + 0x97, 0xb3, 0xfc, 0xd2, 0x91, 0x3c, 0x11, 0x5e, 0xaf, 0x57, 0x2a, 0xa1, + 0x45, 0xa5, 0x60, 0xf0, 0xbe, 0x31, 0xe8, 0xc4, 0x0b, 0x35, 0xe3, 0x42, + 0x9b, 0x22, 0x6b, 0xa3, 0x6c, 0x49, 0x71, 0x20, 0x34, 0x3f, 0x46, 0x0b, + 0x79, 0xc9, 0xb8, 0xb4, 0xbd, 0x9c, 0xad, 0xd3, 0xd8, 0x7e, 0x95, 0x9f, + 0x9a, 0xd4, 0x03, 0xe9, 0x5a, 0x54, 0x46, 0x94, 0x39, 0x55, 0xf1, 0x28, + 0x0d, 0xd1, 0xaa, 0xc9, 0xf8, 0x28, 0x58, 0xef, 0xb0, 0x62, 0xb6, 0x2d, + 0xc7, 0xd2, 0x09, 0x3a, 0x21, 0x0f, 0x7d, 0xa1, 0xb9, 0x59, 0xd5, 0xa7, + 0x43, 0xa9, 0x51, 0xb7, 0xbf, 0x9d, 0xf3, 0x85, 0xec, 0xb3, 0xfb, 0x51, + 0x61, 0xca, 0x81, 0x4d, 0xfa, 0xf1, 0xc3, 0x94, 0x37, 0x45, 0x91, 0xf0, + 0x4b, 0xfc, 0x8e, 0xff, 0x02, 0x81, 0xc1, 0x00, 0xec, 0x38, 0x37, 0x3b, + 0xba, 0x1b, 0x83, 0xaf, 0x3a, 0x00, 0xb9, 0x5e, 0x1f, 0xc8, 0xad, 0x57, + 0xcf, 0x7c, 0xe2, 0x94, 0x95, 0xf1, 0xec, 0x0a, 0x4b, 0x40, 0xc4, 0x48, + 0xfb, 0x47, 0x5f, 0x66, 0xc6, 0xf0, 0x70, 0x14, 0xe9, 0x08, 0xe4, 0x50, + 0x29, 0x0a, 0x24, 0x57, 0x93, 0x97, 0x21, 0xd9, 0xfb, 0xc5, 0x52, 0x0a, + 0x38, 0xb9, 0x68, 0xa3, 0x4f, 0x4b, 0xf8, 0xb8, 0x24, 0xef, 0x0c, 0x42, + 0xda, 0x57, 0x32, 0x77, 0xed, 0x9c, 0x78, 0xeb, 0x10, 0x3e, 0x70, 0x67, + 0xe9, 0x01, 0x03, 0x19, 0x19, 0xdb, 0x48, 0x9e, 0x1e, 0x52, 0x23, 0x88, + 0xb6, 0x87, 0xb8, 0x0d, 0x2d, 0x0c, 0xfc, 0x90, 0x31, 0x9f, 0xa6, 0x96, + 0x0a, 0xe1, 0x34, 0x72, 0x86, 0x0e, 0x49, 0x7c, 0xfe, 0x21, 0xaa, 0x25, + 0xdd, 0x36, 0xbb, 0x1f, 0x85, 0xfe, 0x34, 0x18, 0xc2, 0x36, 0xa2, 0x7d, + 0xee, 0xd9, 0x4f, 0x8e, 0xcb, 0x49, 0x8e, 0x7a, 0x43, 0x3c, 0x52, 0x73, + 0x18, 0x60, 0xf6, 0xb7, 0x7a, 0xc4, 0x7a, 0x8a, 0x1c, 0xf0, 0xc9, 0x2e, + 0xad, 0x54, 0xb1, 0x7b, 0x8e, 0xcb, 0x4d, 0xc2, 0xbc, 0x2a, 0x72, 0xfe, + 0x61, 0x01, 0xd8, 0xff, 0x0a, 0x22, 0x6c, 0x51, 0x7e, 0x06, 0x9e, 0x9e, + 0x3c, 0xe8, 0x31, 0x98, 0xf5, 0x08, 0x34, 0x7e, 0xfa, 0x08, 0xd1, 0x14, + 0xdf, 0xfd, 0x26, 0x2f, 0x1f, 0x5a, 0x89, 0xd7, 0x02, 0x81, 0xc0, 0x76, + 0xdd, 0xed, 0xe9, 0xf5, 0x23, 0x33, 0x13, 0x3f, 0xfe, 0x60, 0xa2, 0x99, + 0x14, 0x3a, 0x87, 0xea, 0x0d, 0x18, 0x8d, 0x9b, 0xd3, 0xd0, 0x9d, 0xff, + 0xc3, 0x77, 0xcc, 0x9a, 0x0a, 0x53, 0x47, 0x80, 0xde, 0x0e, 0x23, 0xea, + 0xc6, 0x6b, 0x8d, 0xd3, 0xbc, 0xcd, 0x03, 0xe6, 0x3d, 0x4d, 0x3d, 0xdd, + 0x7c, 0xb2, 0x27, 0xf9, 0xfe, 0x00, 0xdb, 0x7e, 0x1c, 0x46, 0x1d, 0x83, + 0x11, 0x56, 0xef, 0x8f, 0xc7, 0x5c, 0x5b, 0xb3, 0x0f, 0x9f, 0xd9, 0x02, + 0x80, 0x5c, 0x5e, 0x7f, 0xab, 0xc6, 0x3b, 0x7b, 0x17, 0x7a, 0x8b, 0xd1, + 0x6f, 0xb5, 0x57, 0x07, 0xc1, 0x46, 0x24, 0x5b, 0x72, 0x2e, 0xad, 0xaa, + 0xb4, 0x7f, 0x91, 0xfd, 0x73, 0x83, 0x86, 0x89, 0x4c, 0x81, 0xb8, 0x80, + 0xb3, 0xa7, 0xf8, 0x8b, 0x20, 0xac, 0xd9, 0x27, 0x6f, 0x9a, 0x4b, 0x2f, + 0x6a, 0xef, 0x84, 0x61, 0x75, 0x23, 0x18, 0xcd, 0x6f, 0x63, 0x80, 0x09, + 0x8a, 0xbc, 0x14, 0x1c, 0xe5, 0xff, 0xa9, 0x7d, 0x9a, 0x66, 0x20, 0x61, + 0x3c, 0x61, 0x4b, 0x3d, 0xd5, 0x39, 0xec, 0x3a, 0x16, 0x8d, 0x3b, 0xd1, + 0xf0, 0x1f, 0x8f, 0xae, 0xe2, 0xce, 0xc1, 0x94, 0x69, 0xae, 0xb8, 0xcd, + 0xba, 0x1c, 0x71, 0xe0, 0x47, 0x37, 0xa2, 0x1f, 0x5a, 0xdb, 0x37, 0xe1, + 0x59, 0x4c, 0x39, 0x46, 0xc1, 0xc0, 0x65, 0xc8, 0xd9, 0x61, 0xd3, 0x02, + 0x81, 0xc0, 0x2f, 0x63, 0xe7, 0xd0, 0xd7, 0xb9, 0x85, 0x65, 0xb6, 0x21, + 0x47, 0x0f, 0x17, 0x19, 0x4f, 0x8d, 0x7a, 0x56, 0xf7, 0xae, 0x0f, 0x97, + 0x05, 0x5f, 0xdb, 0x51, 0x17, 0x0f, 0xfd, 0x39, 0x88, 0x6e, 0x3a, 0x23, + 0x2a, 0x99, 0x47, 0x57, 0x3d, 0x56, 0xc7, 0xa4, 0xfd, 0x3d, 0x84, 0xa2, + 0xa1, 0x6b, 0xf6, 0x12, 0xd4, 0x2e, 0xb0, 0xca, 0xa1, 0xaf, 0x81, 0xcd, + 0x20, 0x0c, 0xf1, 0x7b, 0xf3, 0xdd, 0xc5, 0xa8, 0x10, 0xbb, 0xf6, 0xb3, + 0x99, 0x9e, 0xaf, 0x17, 0x97, 0xbd, 0x81, 0x05, 0x6e, 0xf5, 0xae, 0x36, + 0x4c, 0x0f, 0x4c, 0xcd, 0xf5, 0xcb, 0x0b, 0xb3, 0x96, 0xbd, 0x2d, 0xf8, + 0x99, 0x02, 0xe4, 0xb1, 0xbe, 0xde, 0x03, 0x38, 0xc3, 0x28, 0xe6, 0xb4, + 0x1f, 0x12, 0x30, 0x79, 0xd8, 0x84, 0xd8, 0x28, 0x8e, 0xc9, 0xf8, 0x3b, + 0xd3, 0x7f, 0xd4, 0x16, 0xd9, 0xea, 0xa1, 0xec, 0x7f, 0x05, 0x8a, 0xcb, + 0x2b, 0x06, 0x64, 0x4e, 0xc9, 0xcb, 0xc5, 0x6c, 0x4e, 0x92, 0xe8, 0xd2, + 0x5a, 0x33, 0x33, 0x33, 0x2b, 0x69, 0x6d, 0xe4, 0xbb, 0xe6, 0xa9, 0xf3, + 0x27, 0x9a, 0x95, 0xdd, 0x7e, 0x4c, 0x82, 0x71, 0xb8, 0x73, 0x12, 0x39, + 0x6d, 0xb9, 0xbb, 0xaa, 0xe0, 0x4f, 0xa6, 0xb0, 0x7e, 0xa2, 0xcd, 0x25, + 0xe4, 0x42, 0x45, 0x2f, 0x57, 0xa2, 0xf4, 0x7c, 0xf9, 0x18, 0x23, 0x16, + 0x2a, 0xe9, 0x02, 0x81, 0xc1, 0x00, 0xab, 0x35, 0x0d, 0x35, 0x94, 0x9d, + 0x96, 0xb2, 0xb7, 0x45, 0x16, 0xef, 0xb7, 0xea, 0xba, 0xa4, 0x32, 0xec, + 0x43, 0x05, 0xb0, 0x14, 0xbd, 0x9e, 0xd2, 0xbe, 0x0a, 0x0c, 0x4f, 0xca, + 0x4f, 0xf3, 0x11, 0xb3, 0x1f, 0xdc, 0x04, 0x18, 0x38, 0x9d, 0xb0, 0x09, + 0xb8, 0xf1, 0xcf, 0x7a, 0x89, 0x03, 0xd8, 0xed, 0x28, 0x30, 0xe8, 0xe6, + 0xbc, 0x7c, 0x1c, 0x59, 0x12, 0xf8, 0x95, 0x9b, 0x36, 0xad, 0xf2, 0xea, + 0x4a, 0x34, 0x00, 0xcf, 0x94, 0x3e, 0xeb, 0xff, 0xe2, 0x5b, 0x6c, 0x72, + 0xe3, 0x04, 0xd1, 0x10, 0x2e, 0xdd, 0x18, 0x8d, 0x9a, 0x84, 0x93, 0x55, + 0x4a, 0x80, 0x6c, 0xb5, 0x82, 0xc4, 0x16, 0x19, 0xc4, 0xba, 0xad, 0x2e, + 0x40, 0x76, 0xb3, 0xc9, 0xd4, 0x26, 0x5d, 0xc9, 0xb1, 0x05, 0x0f, 0x1f, + 0x7d, 0x59, 0x8c, 0x7b, 0xbe, 0x34, 0x09, 0x3e, 0x71, 0x0b, 0xc8, 0xf9, + 0xb3, 0x77, 0x4e, 0x4b, 0xfb, 0xbf, 0x81, 0x55, 0xa4, 0x5e, 0xc6, 0xe9, + 0xa1, 0xc3, 0x16, 0xff, 0xc8, 0x37, 0x88, 0xd5, 0x2d, 0xfb, 0x06, 0x98, + 0xe9, 0x82, 0x1b, 0x5e, 0x1e, 0xdd, 0x48, 0x5d, 0x6c, 0x59, 0xee, 0x7a, + 0xa6, 0xa4, 0x29, 0x41, 0x20, 0xb4, 0xcd, 0xf4, 0x58, 0x95, 0xfd, 0x7d, + 0xbf, 0xfc, 0x83, 0xf5, 0xe1, 0x5a, 0x5d, 0xa8, 0x08, 0x66, 0xd8, 0xa0, + 0x7f, 0xad, 0x7d, 0xcd, 0x22, 0x06 +}; + +// A 2048 bit RSA key in PKCS#8 PrivateKeyInfo format. +// This is a different key from the one above. +static const uint8_t kTestRSAPKCS8PrivateKeyInfo4_2048[] = { + 0x30, 0x82, 0x04, 0xbd, 0x02, 0x01, 0x00, 0x30, 0x0d, 0x06, 0x09, 0x2a, + 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x04, 0x82, + 0x04, 0xa7, 0x30, 0x82, 0x04, 0xa3, 0x02, 0x01, 0x00, 0x02, 0x82, 0x01, + 0x01, 0x00, 0xc7, 0xfd, 0xa7, 0xca, 0x67, 0x15, 0xd1, 0x29, 0xea, 0xbc, + 0x42, 0x9b, 0x02, 0x37, 0xbb, 0xfb, 0xb4, 0x72, 0x86, 0x76, 0x05, 0x20, + 0x05, 0xcb, 0x94, 0x58, 0x79, 0x4a, 0x79, 0x68, 0xfe, 0xb4, 0x1d, 0x27, + 0x87, 0x97, 0x22, 0xeb, 0x13, 0xb2, 0xb6, 0xed, 0xda, 0xb3, 0xc0, 0x63, + 0x32, 0xd9, 0x31, 0x29, 0xb1, 0xb9, 0x97, 0x17, 0x67, 0x65, 0xd3, 0x9b, + 0x8d, 0xf2, 0x73, 0x1c, 0x70, 0x08, 0xe5, 0x64, 0x9f, 0xcf, 0x5b, 0x52, + 0x9e, 0x47, 0x51, 0xb0, 0xeb, 0x21, 0x81, 0xb4, 0x24, 0xc8, 0xd0, 0x42, + 0xf3, 0xd4, 0x77, 0x1e, 0x96, 0x12, 0x2f, 0x74, 0xab, 0x06, 0xb2, 0x2e, + 0xfa, 0xcd, 0x83, 0xcc, 0xbf, 0x02, 0x14, 0x34, 0x3c, 0x2c, 0x79, 0x7f, + 0x60, 0x60, 0xd0, 0x68, 0x7e, 0x70, 0x32, 0xd4, 0x34, 0x73, 0x0a, 0x42, + 0x7b, 0xbc, 0x69, 0xc0, 0xf8, 0xa6, 0xdc, 0x8f, 0x10, 0xda, 0x1c, 0xd4, + 0x9d, 0x59, 0x23, 0xb4, 0x05, 0x32, 0xac, 0xcc, 0x54, 0xff, 0x35, 0xb3, + 0x3f, 0x22, 0x1b, 0x8d, 0x9b, 0x6d, 0x76, 0xd7, 0xe5, 0xd1, 0xaa, 0x54, + 0x01, 0xd9, 0x53, 0x88, 0x40, 0xb4, 0x7a, 0x3c, 0x2b, 0x47, 0x09, 0xcc, + 0x50, 0xfa, 0x7a, 0x87, 0x56, 0x44, 0x28, 0x4b, 0x47, 0xc4, 0xee, 0x2e, + 0x2a, 0xaa, 0xe7, 0x7a, 0xd6, 0x7f, 0xd3, 0xc5, 0xf8, 0x70, 0x14, 0x97, + 0x64, 0x1c, 0x62, 0xdc, 0x42, 0xed, 0x90, 0x7d, 0x18, 0x36, 0xc2, 0xfa, + 0xd2, 0xdf, 0xd5, 0x23, 0x5c, 0x5a, 0xfb, 0x84, 0xe1, 0xe3, 0x5d, 0x3a, + 0x6a, 0x4e, 0x6a, 0x4b, 0xd9, 0x2d, 0xbd, 0xea, 0x88, 0x95, 0xc0, 0xb5, + 0xf2, 0xd3, 0x37, 0x1f, 0xfe, 0xc7, 0x0a, 0x62, 0x33, 0x5b, 0x98, 0x36, + 0x9d, 0x8d, 0x1a, 0x8e, 0x3a, 0xc5, 0x69, 0xe3, 0x2b, 0x95, 0x6e, 0xa6, + 0xf6, 0x00, 0xc0, 0x9d, 0xa7, 0x39, 0x02, 0x03, 0x01, 0x00, 0x01, 0x02, + 0x82, 0x01, 0x00, 0x3e, 0x5b, 0x16, 0x80, 0x03, 0x58, 0x48, 0x19, 0x3b, + 0xb9, 0x6b, 0x43, 0x0d, 0x93, 0xf7, 0x26, 0xea, 0x02, 0x73, 0x6e, 0x44, + 0xf6, 0xd1, 0x17, 0xc2, 0xaf, 0x3b, 0x52, 0x3f, 0x3c, 0xbf, 0x7d, 0xc5, + 0x41, 0x58, 0x03, 0x85, 0xbd, 0xaa, 0x2c, 0xa4, 0xe0, 0xd0, 0xba, 0x0f, + 0x18, 0x5e, 0xf8, 0x5d, 0x5d, 0xd4, 0xae, 0x3b, 0x2f, 0xee, 0xeb, 0x4e, + 0xe9, 0x47, 0x7e, 0xfa, 0xe9, 0x38, 0xfd, 0x6d, 0x18, 0xcc, 0xc3, 0x48, + 0x84, 0x20, 0x19, 0xb8, 0x9c, 0xe3, 0x13, 0x55, 0x6f, 0xa7, 0xb0, 0x7d, + 0x4f, 0x02, 0xdb, 0x92, 0x30, 0x95, 0x36, 0x22, 0x59, 0xcc, 0x21, 0x90, + 0x59, 0x19, 0x96, 0x0e, 0xac, 0x3e, 0x3a, 0x15, 0xb6, 0x9b, 0xb4, 0x7a, + 0x45, 0x7e, 0xf5, 0x70, 0xc5, 0xc3, 0xf6, 0x93, 0xc2, 0x9a, 0x7f, 0xec, + 0x09, 0x02, 0xaf, 0xa2, 0x81, 0xaa, 0x85, 0xcc, 0x6b, 0x47, 0x23, 0x4d, + 0x65, 0x7e, 0xd7, 0xa9, 0x0c, 0x37, 0x48, 0x83, 0xc8, 0x40, 0x60, 0x88, + 0xfd, 0xeb, 0x31, 0xea, 0x65, 0xe3, 0xf6, 0xc5, 0x1d, 0x7b, 0xbd, 0xbc, + 0x4b, 0x60, 0x27, 0xcb, 0xf1, 0xe2, 0xd9, 0x09, 0x03, 0xf3, 0xfe, 0xd8, + 0x5c, 0xc1, 0x70, 0xdb, 0x0e, 0xdc, 0xc3, 0x7a, 0xb2, 0x41, 0x07, 0xf6, + 0x85, 0x26, 0x31, 0xed, 0xa3, 0x36, 0x80, 0x08, 0x4f, 0xf7, 0x02, 0x37, + 0x6e, 0x06, 0x9b, 0x1a, 0x76, 0x47, 0x62, 0x86, 0xdc, 0x33, 0xab, 0xcc, + 0x88, 0x77, 0x1c, 0x15, 0x43, 0xac, 0x72, 0x46, 0x73, 0x6e, 0x77, 0x28, + 0x82, 0x75, 0xa0, 0x2f, 0xd4, 0x96, 0x1b, 0x1e, 0x2e, 0x63, 0x66, 0x6c, + 0x75, 0x4f, 0xaa, 0xd0, 0x2c, 0xcc, 0xd3, 0x65, 0x85, 0xbe, 0x87, 0x6a, + 0x8e, 0x72, 0x65, 0x57, 0x64, 0xb5, 0x79, 0x71, 0x92, 0x7f, 0xe9, 0x80, + 0x08, 0xa3, 0xa3, 0x2d, 0xc0, 0x49, 0x89, 0x02, 0x81, 0x81, 0x00, 0xee, + 0x13, 0x86, 0xc3, 0x48, 0xb1, 0x4c, 0x7d, 0xe2, 0x31, 0x6c, 0x16, 0x37, + 0x72, 0x6f, 0x33, 0x9d, 0x17, 0xe4, 0x20, 0x91, 0xa3, 0x75, 0x42, 0x19, + 0xc1, 0xdb, 0x60, 0x90, 0x00, 0x4f, 0x04, 0x33, 0x88, 0xe1, 0xb8, 0x6f, + 0xdf, 0xde, 0x71, 0x34, 0xb1, 0xc8, 0x45, 0x16, 0x20, 0xac, 0x83, 0xd2, + 0xfe, 0x4c, 0x87, 0xb8, 0xdb, 0xfa, 0x42, 0x8e, 0xf5, 0x08, 0x03, 0x77, + 0xb3, 0x07, 0x17, 0xb2, 0x9b, 0xd1, 0x8c, 0xe8, 0xdc, 0x61, 0xb8, 0x82, + 0xf1, 0xe2, 0xa5, 0x46, 0xb1, 0xc1, 0x62, 0x1a, 0x9d, 0xf7, 0x44, 0x65, + 0xda, 0xe3, 0xa0, 0xe5, 0xd5, 0x30, 0xca, 0x28, 0x07, 0x2f, 0xd7, 0xba, + 0x99, 0x9f, 0x2a, 0x16, 0xc7, 0xe8, 0x98, 0x00, 0x7e, 0xd5, 0x53, 0x3c, + 0x29, 0x8a, 0x52, 0xd0, 0x93, 0xbe, 0xf7, 0xa9, 0x3a, 0x6e, 0x07, 0x28, + 0x44, 0x15, 0x6f, 0x20, 0x22, 0x52, 0xe3, 0x02, 0x81, 0x81, 0x00, 0xd7, + 0x0c, 0x1a, 0x8e, 0x0a, 0x11, 0x60, 0x12, 0xb0, 0x62, 0xfb, 0x71, 0x21, + 0xc0, 0x92, 0x65, 0x3d, 0xa4, 0x44, 0x1b, 0xbf, 0x17, 0x50, 0x45, 0xe6, + 0x19, 0x5f, 0x95, 0xda, 0x56, 0x12, 0x79, 0xc7, 0xcd, 0x8f, 0x07, 0x31, + 0x51, 0x93, 0xb7, 0x31, 0xd7, 0xc3, 0x09, 0x46, 0x8f, 0xfc, 0xd2, 0x1c, + 0xcd, 0x3c, 0xbc, 0x87, 0x54, 0x9d, 0xe8, 0xa4, 0xc1, 0xb6, 0xa4, 0x68, + 0x5e, 0xfd, 0x03, 0x78, 0xef, 0x69, 0xac, 0xd9, 0x0e, 0x00, 0x6f, 0x29, + 0xe8, 0x2b, 0x54, 0x62, 0x7f, 0x83, 0xe5, 0x5f, 0x9d, 0x83, 0xe2, 0x01, + 0x33, 0x33, 0x28, 0x39, 0x8f, 0x83, 0xaf, 0x6d, 0x60, 0xd5, 0x6f, 0xec, + 0xdb, 0x74, 0x5b, 0x87, 0x6e, 0x32, 0xe5, 0x51, 0x95, 0xbe, 0x08, 0xa5, + 0x8a, 0xf8, 0x92, 0x09, 0xe8, 0xae, 0xca, 0x95, 0x16, 0x44, 0x95, 0x84, + 0x52, 0xd8, 0x67, 0x1d, 0x31, 0x8c, 0x33, 0x02, 0x81, 0x81, 0x00, 0xe7, + 0x34, 0x05, 0x52, 0xce, 0xdc, 0x87, 0xf9, 0x54, 0x5a, 0x44, 0xaa, 0x8a, + 0xac, 0x52, 0x3b, 0xe0, 0x9a, 0x9c, 0x0b, 0xd9, 0x03, 0xd1, 0xd3, 0x20, + 0xaa, 0x77, 0xbe, 0x9d, 0xf9, 0xfe, 0xc2, 0x88, 0xbd, 0xac, 0xdf, 0x6d, + 0x9e, 0x0e, 0x2b, 0x08, 0x4e, 0x82, 0xbc, 0x3d, 0x11, 0xfc, 0x17, 0x85, + 0x27, 0x3a, 0xf1, 0x2f, 0x60, 0x49, 0xf8, 0xab, 0x56, 0x60, 0xb2, 0xe5, + 0x62, 0xf2, 0x47, 0x5c, 0x48, 0x20, 0x47, 0xe4, 0xb8, 0x69, 0x1a, 0x43, + 0xe8, 0x5d, 0xff, 0xbd, 0xd8, 0xd0, 0x81, 0xd2, 0x71, 0x87, 0xf9, 0x5f, + 0xa5, 0x24, 0x1c, 0x54, 0x37, 0x26, 0xcb, 0x9b, 0xf5, 0xe6, 0xcb, 0x6d, + 0x46, 0xd5, 0x20, 0x8d, 0xc1, 0x14, 0x2b, 0xd1, 0x87, 0x5a, 0xea, 0x1b, + 0x6a, 0x08, 0x10, 0x06, 0xc9, 0x04, 0xd3, 0x05, 0x00, 0x5e, 0x6c, 0x0a, + 0xf9, 0x9b, 0x8b, 0x30, 0xbc, 0xc4, 0x9b, 0x02, 0x81, 0x80, 0x20, 0x16, + 0x83, 0x43, 0xdc, 0x86, 0xb9, 0x32, 0x30, 0xb5, 0xa4, 0x55, 0x3e, 0x37, + 0x10, 0xe1, 0x20, 0x70, 0x89, 0x65, 0x26, 0x7f, 0x61, 0x0f, 0xfd, 0x04, + 0x9e, 0x39, 0xab, 0x09, 0x9a, 0xda, 0x27, 0x11, 0x2a, 0x1d, 0x65, 0xa3, + 0x89, 0x8a, 0x15, 0x5a, 0xdf, 0x94, 0x5b, 0xbe, 0x12, 0x78, 0x7b, 0xa9, + 0xb8, 0x3d, 0x5d, 0x68, 0x27, 0xe3, 0xd5, 0xe9, 0xbd, 0xca, 0x0c, 0x74, + 0xd8, 0x06, 0x13, 0x9e, 0x8d, 0xc3, 0x27, 0xe4, 0x29, 0xf6, 0x6c, 0x67, + 0x63, 0x47, 0xc5, 0x26, 0xc7, 0xd4, 0x8b, 0x0e, 0xe2, 0xe5, 0xef, 0x4d, + 0xe7, 0x7d, 0x42, 0x11, 0x83, 0x11, 0xc3, 0x1d, 0x66, 0xdb, 0xbb, 0x99, + 0x07, 0x64, 0xbc, 0xf1, 0x99, 0x90, 0x94, 0x3a, 0xe6, 0xcf, 0x3d, 0x7c, + 0x5e, 0x18, 0xd4, 0x77, 0xae, 0xd5, 0x35, 0xa7, 0xcb, 0xf6, 0x0f, 0x77, + 0x99, 0x28, 0xd1, 0xb7, 0x59, 0xf7, 0x02, 0x81, 0x80, 0x43, 0x4d, 0xf6, + 0xfe, 0x87, 0x20, 0xfb, 0x31, 0xc4, 0x00, 0x83, 0xa2, 0x66, 0x63, 0xa9, + 0xed, 0x4c, 0xff, 0x79, 0xd3, 0xdc, 0x5d, 0x9d, 0xad, 0xa2, 0x03, 0x66, + 0x1b, 0x97, 0xe0, 0x44, 0xd0, 0xc0, 0x5e, 0xf4, 0x5d, 0xe4, 0xc9, 0x2a, + 0xa0, 0x6e, 0x1d, 0x2e, 0xb0, 0x23, 0xf4, 0x7b, 0x1c, 0xaa, 0x5d, 0x86, + 0xb9, 0xf1, 0x93, 0xf8, 0x32, 0x09, 0x74, 0x53, 0xfe, 0x90, 0x24, 0xfc, + 0x35, 0x1e, 0x7a, 0x0e, 0x4a, 0x3b, 0x1e, 0x58, 0x49, 0x3e, 0x7e, 0xc8, + 0x8a, 0xb1, 0x1c, 0xb0, 0xe1, 0x35, 0x1b, 0x9e, 0xc5, 0x59, 0x7c, 0x32, + 0x3a, 0xab, 0x24, 0x5a, 0xb7, 0x70, 0x27, 0xf9, 0x62, 0x4b, 0x89, 0x35, + 0x20, 0x0d, 0x10, 0xea, 0x76, 0x24, 0x91, 0xe6, 0x64, 0x02, 0xbc, 0xba, + 0xf7, 0x59, 0x16, 0xc7, 0x41, 0xc0, 0xb7, 0xa4, 0x6d, 0xa4, 0xb9, 0xe6, + 0x61, 0x66, 0x42, 0xbf, 0xf0}; + +// This is an RSA key where e*d != 1 mod phi. Instead, it uses the carmicahel +// totient. That means e*d = 1 mod lambda, where lambda = lcm(p-1, q-1). +static const uint8_t kTestKeyRSACarmichael_2048[] = { + 0x30, 0x82, 0x04, 0xbc, 0x02, 0x01, 0x00, 0x30, 0x0d, 0x06, 0x09, 0x2a, + 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x04, 0x82, + 0x04, 0xa6, 0x30, 0x82, 0x04, 0xa2, 0x02, 0x01, 0x00, 0x02, 0x82, 0x01, + 0x01, 0x00, 0xa7, 0x00, 0x36, 0x60, 0x65, 0xdc, 0xbd, 0x54, 0x5a, 0x2a, + 0x40, 0xb4, 0xe1, 0x15, 0x94, 0x58, 0x11, 0x4f, 0x94, 0x58, 0xdd, 0xde, + 0xa7, 0x1f, 0x3c, 0x2c, 0xe0, 0x88, 0x09, 0x29, 0x61, 0x57, 0x67, 0x5e, + 0x56, 0x7e, 0xee, 0x27, 0x8f, 0x59, 0x34, 0x9a, 0x2a, 0xaa, 0x9d, 0xb4, + 0x4e, 0xfa, 0xa7, 0x6a, 0xd4, 0xc9, 0x7a, 0x53, 0xc1, 0x4e, 0x9f, 0xe3, + 0x34, 0xf7, 0x3d, 0xb7, 0xc9, 0x10, 0x47, 0x4f, 0x28, 0xda, 0x3f, 0xce, + 0x31, 0x7b, 0xfd, 0x06, 0x10, 0xeb, 0xf7, 0xbe, 0x92, 0xf9, 0xaf, 0xfb, + 0x3e, 0x68, 0xda, 0xee, 0x1a, 0x64, 0x4c, 0xf3, 0x29, 0xf2, 0x73, 0x9e, + 0x39, 0xd8, 0xf6, 0x6f, 0xd8, 0xb2, 0x80, 0x82, 0x71, 0x8e, 0xb5, 0xa4, + 0xf2, 0xc2, 0x3e, 0xcd, 0x0a, 0xca, 0xb6, 0x04, 0xcd, 0x9a, 0x13, 0x8b, + 0x54, 0x73, 0x54, 0x25, 0x54, 0x8c, 0xbe, 0x98, 0x7a, 0x67, 0xad, 0xda, + 0xb3, 0x4e, 0xb3, 0xfa, 0x82, 0xa8, 0x4a, 0x67, 0x98, 0x56, 0x57, 0x54, + 0x71, 0xcd, 0x12, 0x7f, 0xed, 0xa3, 0x01, 0xc0, 0x6a, 0x8b, 0x24, 0x03, + 0x96, 0x88, 0xbe, 0x97, 0x66, 0x2a, 0xbc, 0x53, 0xc9, 0x83, 0x06, 0x51, + 0x5a, 0x88, 0x65, 0x13, 0x18, 0xe4, 0x3a, 0xed, 0x6b, 0xf1, 0x61, 0x5b, + 0x4c, 0xc8, 0x1e, 0xf4, 0xc2, 0xae, 0x08, 0x5e, 0x2d, 0x5f, 0xf8, 0x12, + 0x7f, 0xa2, 0xfc, 0xbb, 0x21, 0x18, 0x30, 0xda, 0xfe, 0x40, 0xfb, 0x01, + 0xca, 0x2e, 0x37, 0x0e, 0xce, 0xdd, 0x76, 0x87, 0x82, 0x46, 0x0b, 0x3a, + 0x77, 0x8f, 0xc0, 0x72, 0x07, 0x2c, 0x7f, 0x9d, 0x1e, 0x86, 0x5b, 0xed, + 0x27, 0x29, 0xdf, 0x03, 0x97, 0x62, 0xef, 0x44, 0xd3, 0x5b, 0x3d, 0xdb, + 0x9c, 0x5e, 0x1b, 0x7b, 0x39, 0xb4, 0x0b, 0x6d, 0x04, 0x6b, 0xbb, 0xbb, + 0x2c, 0x5f, 0xcf, 0xb3, 0x7a, 0x05, 0x02, 0x03, 0x01, 0x00, 0x01, 0x02, + 0x82, 0x01, 0x00, 0x0a, 0xf9, 0x4a, 0x19, 0x72, 0x88, 0x1b, 0x4e, 0xd8, + 0x2f, 0xef, 0x99, 0x93, 0x32, 0xda, 0x51, 0x21, 0x2e, 0x14, 0x06, 0xf4, + 0xe9, 0x65, 0x1c, 0xf9, 0xd4, 0xcf, 0x1a, 0x51, 0x53, 0xcd, 0x48, 0x33, + 0x8c, 0x30, 0xed, 0xdd, 0x53, 0x6f, 0x29, 0x82, 0xf9, 0xe0, 0x74, 0xde, + 0xb1, 0x13, 0x01, 0x88, 0x8f, 0xce, 0x14, 0xc1, 0x3b, 0x90, 0xb7, 0xcc, + 0x6c, 0xdf, 0x35, 0xa1, 0xf2, 0x1a, 0x3d, 0xbe, 0x19, 0xd7, 0x0a, 0xe4, + 0x67, 0x75, 0xbb, 0xfa, 0x87, 0xf4, 0x03, 0xb5, 0x7f, 0x69, 0xe4, 0x0b, + 0x6a, 0xdc, 0x92, 0x82, 0x54, 0x64, 0x1a, 0x94, 0x2d, 0xe4, 0x63, 0x40, + 0xb2, 0xb4, 0x85, 0x6b, 0xc8, 0x34, 0xba, 0xa2, 0x14, 0x30, 0x47, 0x1a, + 0xeb, 0x90, 0x62, 0x30, 0x43, 0x44, 0x02, 0xc7, 0x0c, 0x30, 0xc0, 0x7f, + 0xa9, 0x47, 0xae, 0xde, 0x68, 0x27, 0x92, 0xaa, 0x11, 0x95, 0xf5, 0x6f, + 0xfc, 0x19, 0x8b, 0x49, 0xa0, 0x77, 0x9d, 0xc6, 0x13, 0x5d, 0x73, 0xff, + 0x45, 0xa2, 0x4c, 0x3b, 0xf3, 0xe1, 0x2d, 0xd7, 0xc4, 0x70, 0xe2, 0x6c, + 0x37, 0x99, 0x4c, 0x7a, 0xa9, 0x27, 0xf8, 0x3a, 0xd6, 0xfd, 0xc5, 0xd8, + 0xfa, 0x2d, 0x0e, 0x71, 0x4b, 0x85, 0x7e, 0xce, 0xcb, 0x1c, 0x79, 0x71, + 0xbd, 0xff, 0x63, 0x03, 0x6b, 0x58, 0x68, 0xe0, 0x14, 0xca, 0x5e, 0x85, + 0xfd, 0xd0, 0xb7, 0xe0, 0x68, 0x14, 0xff, 0x2c, 0x82, 0x22, 0x26, 0x8a, + 0x3f, 0xbf, 0xb0, 0x2a, 0x90, 0xff, 0xc7, 0x72, 0xfc, 0x66, 0x51, 0x3e, + 0x51, 0x9f, 0x82, 0x68, 0x0e, 0xf3, 0x65, 0x74, 0x88, 0xab, 0xb7, 0xe5, + 0x97, 0x5f, 0x0f, 0x3e, 0xe5, 0x3a, 0xbc, 0xa4, 0xa1, 0x50, 0xdd, 0x5c, + 0x94, 0x4b, 0x0c, 0x70, 0x71, 0x48, 0x4e, 0xd0, 0xec, 0x46, 0x8f, 0xdf, + 0xa2, 0x9a, 0xfe, 0xd8, 0x35, 0x1a, 0x2f, 0x02, 0x81, 0x81, 0x00, 0xcf, + 0x73, 0x8c, 0xbe, 0x6d, 0x45, 0x2d, 0x0c, 0x0b, 0x5d, 0x5c, 0x6c, 0x75, + 0x78, 0xcc, 0x35, 0x48, 0xb6, 0x98, 0xf1, 0xb9, 0x64, 0x60, 0x8c, 0x43, + 0xeb, 0x85, 0xab, 0x04, 0xb6, 0x7d, 0x1b, 0x71, 0x75, 0x06, 0xe2, 0xda, + 0x84, 0x68, 0x2e, 0x7f, 0x4c, 0xe3, 0x73, 0xb4, 0xde, 0x51, 0x4b, 0xb6, + 0x51, 0x86, 0x7b, 0xd0, 0xe6, 0x4d, 0xf3, 0xd1, 0xcf, 0x1a, 0xfe, 0x7f, + 0x3a, 0x83, 0xba, 0xb3, 0xe1, 0xff, 0x54, 0x13, 0x93, 0xd7, 0x9c, 0x27, + 0x80, 0xb7, 0x1e, 0x64, 0x9e, 0xf7, 0x32, 0x2b, 0x46, 0x29, 0xf7, 0xf8, + 0x18, 0x6c, 0xf7, 0x4a, 0xbe, 0x4b, 0xee, 0x96, 0x90, 0x8f, 0xa2, 0x16, + 0x22, 0x6a, 0xcc, 0x48, 0x06, 0x74, 0x63, 0x43, 0x7f, 0x27, 0x22, 0x44, + 0x3c, 0x2d, 0x3b, 0x62, 0xf1, 0x1c, 0xb4, 0x27, 0x33, 0x85, 0x26, 0x60, + 0x48, 0x16, 0xcb, 0xef, 0xf8, 0xcd, 0x37, 0x02, 0x81, 0x81, 0x00, 0xce, + 0x15, 0x43, 0x6e, 0x4b, 0x0f, 0xf9, 0x3f, 0x87, 0xc3, 0x41, 0x45, 0x97, + 0xb1, 0x49, 0xc2, 0x19, 0x23, 0x87, 0xe4, 0x24, 0x1c, 0x64, 0xe5, 0x28, + 0xcb, 0x43, 0x10, 0x14, 0x14, 0x0e, 0x19, 0xcb, 0xbb, 0xdb, 0xfd, 0x11, + 0x9d, 0x17, 0x68, 0x78, 0x6d, 0x61, 0x70, 0x63, 0x3a, 0xa1, 0xb3, 0xf3, + 0xa7, 0x5b, 0x0e, 0xff, 0xb7, 0x61, 0x11, 0x54, 0x91, 0x99, 0xe5, 0x91, + 0x32, 0x2d, 0xeb, 0x3f, 0xd8, 0x3e, 0xf7, 0xd4, 0xcb, 0xd2, 0xa3, 0x41, + 0xc1, 0xee, 0xc6, 0x92, 0x13, 0xeb, 0x7f, 0x42, 0x58, 0xf4, 0xd0, 0xb2, + 0x74, 0x1d, 0x8e, 0x87, 0x46, 0xcd, 0x14, 0xb8, 0x16, 0xad, 0xb5, 0xbd, + 0x0d, 0x6c, 0x95, 0x5a, 0x16, 0xbf, 0xe9, 0x53, 0xda, 0xfb, 0xed, 0x83, + 0x51, 0x67, 0xa9, 0x55, 0xab, 0x54, 0x02, 0x95, 0x20, 0xa6, 0x68, 0x17, + 0x53, 0xa8, 0xea, 0x43, 0xe5, 0xb0, 0xa3, 0x02, 0x81, 0x80, 0x67, 0x9c, + 0x32, 0x83, 0x39, 0x57, 0xff, 0x73, 0xb0, 0x89, 0x64, 0x8b, 0xd6, 0xf0, + 0x0a, 0x2d, 0xe2, 0xaf, 0x30, 0x1c, 0x2a, 0x97, 0xf3, 0x90, 0x9a, 0xab, + 0x9b, 0x0b, 0x1b, 0x43, 0x79, 0xa0, 0xa7, 0x3d, 0xe7, 0xbe, 0x8d, 0x9c, + 0xeb, 0xdb, 0xad, 0x40, 0xdd, 0xa9, 0x00, 0x80, 0xb8, 0xe1, 0xb3, 0xa1, + 0x6c, 0x25, 0x92, 0xe4, 0x33, 0xb2, 0xbe, 0xeb, 0x4d, 0x74, 0x26, 0x5f, + 0x37, 0x43, 0x9c, 0x6c, 0x17, 0x76, 0x0a, 0x81, 0x20, 0x82, 0xa1, 0x48, + 0x2c, 0x2d, 0x45, 0xdc, 0x0f, 0x62, 0x43, 0x32, 0xbb, 0xeb, 0x59, 0x41, + 0xf9, 0xca, 0x58, 0xce, 0x4a, 0x66, 0x53, 0x54, 0xc8, 0x28, 0x10, 0x1e, + 0x08, 0x71, 0x16, 0xd8, 0x02, 0x71, 0x41, 0x58, 0xd4, 0x56, 0xcc, 0xf5, + 0xb1, 0x31, 0xa3, 0xed, 0x00, 0x85, 0x09, 0xbf, 0x35, 0x95, 0x41, 0x29, + 0x40, 0x19, 0x83, 0x35, 0x24, 0x69, 0x02, 0x81, 0x80, 0x55, 0x10, 0x0b, + 0xcc, 0x3b, 0xa9, 0x75, 0x3d, 0x16, 0xe1, 0xae, 0x50, 0x76, 0x63, 0x94, + 0x49, 0x4c, 0xad, 0x10, 0xcb, 0x47, 0x68, 0x7c, 0xf0, 0xe5, 0xdc, 0xb8, + 0x6a, 0xab, 0x8e, 0xf7, 0x9f, 0x08, 0x2c, 0x1b, 0x8a, 0xa2, 0xb9, 0x8f, + 0xce, 0xec, 0x5e, 0x61, 0xa8, 0xcd, 0x1c, 0x87, 0x60, 0x4a, 0xc3, 0x1a, + 0x5f, 0xdf, 0x87, 0x26, 0xc6, 0xcb, 0x7c, 0x69, 0xe4, 0x8b, 0x01, 0x06, + 0x59, 0x22, 0xfa, 0x34, 0x4b, 0x81, 0x87, 0x3c, 0x03, 0x6d, 0x02, 0x0a, + 0x77, 0xe6, 0x15, 0xd8, 0xcf, 0xa7, 0x68, 0x26, 0x6c, 0xfa, 0x2b, 0xd9, + 0x83, 0x5a, 0x2d, 0x0c, 0x3b, 0x70, 0x1c, 0xd4, 0x48, 0xbe, 0xa7, 0x0a, + 0xd9, 0xbe, 0xdc, 0xc3, 0x0c, 0x21, 0x33, 0xb3, 0x66, 0xff, 0x1c, 0x1b, + 0xc8, 0x96, 0x76, 0xe8, 0x6f, 0x44, 0x74, 0xbc, 0x9b, 0x1c, 0x7d, 0xc8, + 0xac, 0x21, 0xa8, 0x6e, 0x37, 0x02, 0x81, 0x80, 0x2c, 0x7c, 0xad, 0x1e, + 0x75, 0xf6, 0x69, 0x1d, 0xe7, 0xa6, 0xca, 0x74, 0x7d, 0x67, 0xc8, 0x65, + 0x28, 0x66, 0xc4, 0x43, 0xa6, 0xbd, 0x40, 0x57, 0xae, 0xb7, 0x65, 0x2c, + 0x52, 0xf9, 0xe4, 0xc7, 0x81, 0x7b, 0x56, 0xa3, 0xd2, 0x0d, 0xe8, 0x33, + 0x70, 0xcf, 0x06, 0x84, 0xb3, 0x4e, 0x44, 0x50, 0x75, 0x61, 0x96, 0x86, + 0x4b, 0xb6, 0x2b, 0xad, 0xf0, 0xad, 0x57, 0xd0, 0x37, 0x0d, 0x1d, 0x35, + 0x50, 0xcb, 0x69, 0x22, 0x39, 0x29, 0xb9, 0x3a, 0xd3, 0x29, 0x23, 0x02, + 0x60, 0xf7, 0xab, 0x30, 0x40, 0xda, 0x8e, 0x4d, 0x45, 0x70, 0x26, 0xf4, + 0xa2, 0x0d, 0xd0, 0x64, 0x5d, 0x47, 0x3c, 0x18, 0xf4, 0xd4, 0x52, 0x95, + 0x00, 0xae, 0x84, 0x6b, 0x47, 0xb2, 0x3c, 0x82, 0xd3, 0x72, 0x53, 0xde, + 0x72, 0x2c, 0xf7, 0xc1, 0x22, 0x36, 0xd9, 0x18, 0x56, 0xfe, 0x39, 0x28, + 0x33, 0xe0, 0xdb, 0x03, +}; + +// This is the same key as above, except the private key was chosen using the +// Euler totient. +static const uint8_t kTestKeyRSAEuler_2048[] = { + 0x30, 0x82, 0x04, 0xbc, 0x02, 0x01, 0x00, 0x30, 0x0d, 0x06, 0x09, 0x2a, + 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x04, 0x82, + 0x04, 0xa6, 0x30, 0x82, 0x04, 0xa2, 0x02, 0x01, 0x00, 0x02, 0x82, 0x01, + 0x01, 0x00, 0xa7, 0x00, 0x36, 0x60, 0x65, 0xdc, 0xbd, 0x54, 0x5a, 0x2a, + 0x40, 0xb4, 0xe1, 0x15, 0x94, 0x58, 0x11, 0x4f, 0x94, 0x58, 0xdd, 0xde, + 0xa7, 0x1f, 0x3c, 0x2c, 0xe0, 0x88, 0x09, 0x29, 0x61, 0x57, 0x67, 0x5e, + 0x56, 0x7e, 0xee, 0x27, 0x8f, 0x59, 0x34, 0x9a, 0x2a, 0xaa, 0x9d, 0xb4, + 0x4e, 0xfa, 0xa7, 0x6a, 0xd4, 0xc9, 0x7a, 0x53, 0xc1, 0x4e, 0x9f, 0xe3, + 0x34, 0xf7, 0x3d, 0xb7, 0xc9, 0x10, 0x47, 0x4f, 0x28, 0xda, 0x3f, 0xce, + 0x31, 0x7b, 0xfd, 0x06, 0x10, 0xeb, 0xf7, 0xbe, 0x92, 0xf9, 0xaf, 0xfb, + 0x3e, 0x68, 0xda, 0xee, 0x1a, 0x64, 0x4c, 0xf3, 0x29, 0xf2, 0x73, 0x9e, + 0x39, 0xd8, 0xf6, 0x6f, 0xd8, 0xb2, 0x80, 0x82, 0x71, 0x8e, 0xb5, 0xa4, + 0xf2, 0xc2, 0x3e, 0xcd, 0x0a, 0xca, 0xb6, 0x04, 0xcd, 0x9a, 0x13, 0x8b, + 0x54, 0x73, 0x54, 0x25, 0x54, 0x8c, 0xbe, 0x98, 0x7a, 0x67, 0xad, 0xda, + 0xb3, 0x4e, 0xb3, 0xfa, 0x82, 0xa8, 0x4a, 0x67, 0x98, 0x56, 0x57, 0x54, + 0x71, 0xcd, 0x12, 0x7f, 0xed, 0xa3, 0x01, 0xc0, 0x6a, 0x8b, 0x24, 0x03, + 0x96, 0x88, 0xbe, 0x97, 0x66, 0x2a, 0xbc, 0x53, 0xc9, 0x83, 0x06, 0x51, + 0x5a, 0x88, 0x65, 0x13, 0x18, 0xe4, 0x3a, 0xed, 0x6b, 0xf1, 0x61, 0x5b, + 0x4c, 0xc8, 0x1e, 0xf4, 0xc2, 0xae, 0x08, 0x5e, 0x2d, 0x5f, 0xf8, 0x12, + 0x7f, 0xa2, 0xfc, 0xbb, 0x21, 0x18, 0x30, 0xda, 0xfe, 0x40, 0xfb, 0x01, + 0xca, 0x2e, 0x37, 0x0e, 0xce, 0xdd, 0x76, 0x87, 0x82, 0x46, 0x0b, 0x3a, + 0x77, 0x8f, 0xc0, 0x72, 0x07, 0x2c, 0x7f, 0x9d, 0x1e, 0x86, 0x5b, 0xed, + 0x27, 0x29, 0xdf, 0x03, 0x97, 0x62, 0xef, 0x44, 0xd3, 0x5b, 0x3d, 0xdb, + 0x9c, 0x5e, 0x1b, 0x7b, 0x39, 0xb4, 0x0b, 0x6d, 0x04, 0x6b, 0xbb, 0xbb, + 0x2c, 0x5f, 0xcf, 0xb3, 0x7a, 0x05, 0x02, 0x03, 0x01, 0x00, 0x01, 0x02, + 0x82, 0x01, 0x00, 0x5e, 0x79, 0x65, 0x49, 0xa5, 0x76, 0x79, 0xf9, 0x05, + 0x45, 0x0f, 0xf4, 0x03, 0xbd, 0xa4, 0x7d, 0x29, 0xd5, 0xde, 0x33, 0x63, + 0xd8, 0xb8, 0xac, 0x97, 0xeb, 0x3f, 0x5e, 0x55, 0xe8, 0x7d, 0xf3, 0xe7, + 0x3b, 0x5c, 0x2d, 0x54, 0x67, 0x36, 0xd6, 0x1d, 0x46, 0xf5, 0xca, 0x2d, + 0x8b, 0x3a, 0x7e, 0xdc, 0x45, 0x38, 0x79, 0x7e, 0x65, 0x71, 0x5f, 0x1c, + 0x5e, 0x79, 0xb1, 0x40, 0xcd, 0xfe, 0xc5, 0xe1, 0xc1, 0x6b, 0x78, 0x04, + 0x4e, 0x8e, 0x79, 0xf9, 0x0a, 0xfc, 0x79, 0xb1, 0x5e, 0xb3, 0x60, 0xe3, + 0x68, 0x7b, 0xc6, 0xef, 0xcb, 0x71, 0x4c, 0xba, 0xa7, 0x79, 0x5c, 0x7a, + 0x81, 0xd1, 0x71, 0xe7, 0x00, 0x21, 0x13, 0xe2, 0x55, 0x69, 0x0e, 0x75, + 0xbe, 0x09, 0xc3, 0x4f, 0xa9, 0xc9, 0x68, 0x22, 0x0e, 0x97, 0x8d, 0x89, + 0x6e, 0xf1, 0xe8, 0x88, 0x7a, 0xd1, 0xd9, 0x09, 0x5d, 0xd3, 0x28, 0x78, + 0x25, 0x0b, 0x1c, 0x47, 0x73, 0x25, 0xcc, 0x21, 0xb6, 0xda, 0xc6, 0x24, + 0x5a, 0xd0, 0x37, 0x14, 0x46, 0xc7, 0x94, 0x69, 0xe4, 0x43, 0x6f, 0x47, + 0xde, 0x00, 0x33, 0x4d, 0x8f, 0x95, 0x72, 0xfa, 0x68, 0x71, 0x17, 0x66, + 0x12, 0x1a, 0x87, 0x27, 0xf7, 0xef, 0x7e, 0xe0, 0x35, 0x58, 0xf2, 0x4d, + 0x6f, 0x35, 0x01, 0xaa, 0x96, 0xe2, 0x3d, 0x51, 0x13, 0x86, 0x9c, 0x79, + 0xd0, 0xb7, 0xb6, 0x64, 0xe8, 0x86, 0x65, 0x50, 0xbf, 0xcc, 0x27, 0x53, + 0x1f, 0x51, 0xd4, 0xca, 0xbe, 0xf5, 0xdd, 0x77, 0x70, 0x98, 0x0f, 0xee, + 0xa8, 0x96, 0x07, 0x5f, 0x45, 0x6a, 0x7a, 0x0d, 0x03, 0x9c, 0x4f, 0x29, + 0xf6, 0x06, 0xf3, 0x5d, 0x58, 0x6c, 0x47, 0xd0, 0x96, 0xa9, 0x03, 0x17, + 0xbb, 0x4e, 0xc9, 0x21, 0xe0, 0xac, 0xcd, 0x78, 0x78, 0xb2, 0xfe, 0x81, + 0xb2, 0x51, 0x53, 0xa6, 0x1f, 0x98, 0x45, 0x02, 0x81, 0x81, 0x00, 0xcf, + 0x73, 0x8c, 0xbe, 0x6d, 0x45, 0x2d, 0x0c, 0x0b, 0x5d, 0x5c, 0x6c, 0x75, + 0x78, 0xcc, 0x35, 0x48, 0xb6, 0x98, 0xf1, 0xb9, 0x64, 0x60, 0x8c, 0x43, + 0xeb, 0x85, 0xab, 0x04, 0xb6, 0x7d, 0x1b, 0x71, 0x75, 0x06, 0xe2, 0xda, + 0x84, 0x68, 0x2e, 0x7f, 0x4c, 0xe3, 0x73, 0xb4, 0xde, 0x51, 0x4b, 0xb6, + 0x51, 0x86, 0x7b, 0xd0, 0xe6, 0x4d, 0xf3, 0xd1, 0xcf, 0x1a, 0xfe, 0x7f, + 0x3a, 0x83, 0xba, 0xb3, 0xe1, 0xff, 0x54, 0x13, 0x93, 0xd7, 0x9c, 0x27, + 0x80, 0xb7, 0x1e, 0x64, 0x9e, 0xf7, 0x32, 0x2b, 0x46, 0x29, 0xf7, 0xf8, + 0x18, 0x6c, 0xf7, 0x4a, 0xbe, 0x4b, 0xee, 0x96, 0x90, 0x8f, 0xa2, 0x16, + 0x22, 0x6a, 0xcc, 0x48, 0x06, 0x74, 0x63, 0x43, 0x7f, 0x27, 0x22, 0x44, + 0x3c, 0x2d, 0x3b, 0x62, 0xf1, 0x1c, 0xb4, 0x27, 0x33, 0x85, 0x26, 0x60, + 0x48, 0x16, 0xcb, 0xef, 0xf8, 0xcd, 0x37, 0x02, 0x81, 0x81, 0x00, 0xce, + 0x15, 0x43, 0x6e, 0x4b, 0x0f, 0xf9, 0x3f, 0x87, 0xc3, 0x41, 0x45, 0x97, + 0xb1, 0x49, 0xc2, 0x19, 0x23, 0x87, 0xe4, 0x24, 0x1c, 0x64, 0xe5, 0x28, + 0xcb, 0x43, 0x10, 0x14, 0x14, 0x0e, 0x19, 0xcb, 0xbb, 0xdb, 0xfd, 0x11, + 0x9d, 0x17, 0x68, 0x78, 0x6d, 0x61, 0x70, 0x63, 0x3a, 0xa1, 0xb3, 0xf3, + 0xa7, 0x5b, 0x0e, 0xff, 0xb7, 0x61, 0x11, 0x54, 0x91, 0x99, 0xe5, 0x91, + 0x32, 0x2d, 0xeb, 0x3f, 0xd8, 0x3e, 0xf7, 0xd4, 0xcb, 0xd2, 0xa3, 0x41, + 0xc1, 0xee, 0xc6, 0x92, 0x13, 0xeb, 0x7f, 0x42, 0x58, 0xf4, 0xd0, 0xb2, + 0x74, 0x1d, 0x8e, 0x87, 0x46, 0xcd, 0x14, 0xb8, 0x16, 0xad, 0xb5, 0xbd, + 0x0d, 0x6c, 0x95, 0x5a, 0x16, 0xbf, 0xe9, 0x53, 0xda, 0xfb, 0xed, 0x83, + 0x51, 0x67, 0xa9, 0x55, 0xab, 0x54, 0x02, 0x95, 0x20, 0xa6, 0x68, 0x17, + 0x53, 0xa8, 0xea, 0x43, 0xe5, 0xb0, 0xa3, 0x02, 0x81, 0x80, 0x67, 0x9c, + 0x32, 0x83, 0x39, 0x57, 0xff, 0x73, 0xb0, 0x89, 0x64, 0x8b, 0xd6, 0xf0, + 0x0a, 0x2d, 0xe2, 0xaf, 0x30, 0x1c, 0x2a, 0x97, 0xf3, 0x90, 0x9a, 0xab, + 0x9b, 0x0b, 0x1b, 0x43, 0x79, 0xa0, 0xa7, 0x3d, 0xe7, 0xbe, 0x8d, 0x9c, + 0xeb, 0xdb, 0xad, 0x40, 0xdd, 0xa9, 0x00, 0x80, 0xb8, 0xe1, 0xb3, 0xa1, + 0x6c, 0x25, 0x92, 0xe4, 0x33, 0xb2, 0xbe, 0xeb, 0x4d, 0x74, 0x26, 0x5f, + 0x37, 0x43, 0x9c, 0x6c, 0x17, 0x76, 0x0a, 0x81, 0x20, 0x82, 0xa1, 0x48, + 0x2c, 0x2d, 0x45, 0xdc, 0x0f, 0x62, 0x43, 0x32, 0xbb, 0xeb, 0x59, 0x41, + 0xf9, 0xca, 0x58, 0xce, 0x4a, 0x66, 0x53, 0x54, 0xc8, 0x28, 0x10, 0x1e, + 0x08, 0x71, 0x16, 0xd8, 0x02, 0x71, 0x41, 0x58, 0xd4, 0x56, 0xcc, 0xf5, + 0xb1, 0x31, 0xa3, 0xed, 0x00, 0x85, 0x09, 0xbf, 0x35, 0x95, 0x41, 0x29, + 0x40, 0x19, 0x83, 0x35, 0x24, 0x69, 0x02, 0x81, 0x80, 0x55, 0x10, 0x0b, + 0xcc, 0x3b, 0xa9, 0x75, 0x3d, 0x16, 0xe1, 0xae, 0x50, 0x76, 0x63, 0x94, + 0x49, 0x4c, 0xad, 0x10, 0xcb, 0x47, 0x68, 0x7c, 0xf0, 0xe5, 0xdc, 0xb8, + 0x6a, 0xab, 0x8e, 0xf7, 0x9f, 0x08, 0x2c, 0x1b, 0x8a, 0xa2, 0xb9, 0x8f, + 0xce, 0xec, 0x5e, 0x61, 0xa8, 0xcd, 0x1c, 0x87, 0x60, 0x4a, 0xc3, 0x1a, + 0x5f, 0xdf, 0x87, 0x26, 0xc6, 0xcb, 0x7c, 0x69, 0xe4, 0x8b, 0x01, 0x06, + 0x59, 0x22, 0xfa, 0x34, 0x4b, 0x81, 0x87, 0x3c, 0x03, 0x6d, 0x02, 0x0a, + 0x77, 0xe6, 0x15, 0xd8, 0xcf, 0xa7, 0x68, 0x26, 0x6c, 0xfa, 0x2b, 0xd9, + 0x83, 0x5a, 0x2d, 0x0c, 0x3b, 0x70, 0x1c, 0xd4, 0x48, 0xbe, 0xa7, 0x0a, + 0xd9, 0xbe, 0xdc, 0xc3, 0x0c, 0x21, 0x33, 0xb3, 0x66, 0xff, 0x1c, 0x1b, + 0xc8, 0x96, 0x76, 0xe8, 0x6f, 0x44, 0x74, 0xbc, 0x9b, 0x1c, 0x7d, 0xc8, + 0xac, 0x21, 0xa8, 0x6e, 0x37, 0x02, 0x81, 0x80, 0x2c, 0x7c, 0xad, 0x1e, + 0x75, 0xf6, 0x69, 0x1d, 0xe7, 0xa6, 0xca, 0x74, 0x7d, 0x67, 0xc8, 0x65, + 0x28, 0x66, 0xc4, 0x43, 0xa6, 0xbd, 0x40, 0x57, 0xae, 0xb7, 0x65, 0x2c, + 0x52, 0xf9, 0xe4, 0xc7, 0x81, 0x7b, 0x56, 0xa3, 0xd2, 0x0d, 0xe8, 0x33, + 0x70, 0xcf, 0x06, 0x84, 0xb3, 0x4e, 0x44, 0x50, 0x75, 0x61, 0x96, 0x86, + 0x4b, 0xb6, 0x2b, 0xad, 0xf0, 0xad, 0x57, 0xd0, 0x37, 0x0d, 0x1d, 0x35, + 0x50, 0xcb, 0x69, 0x22, 0x39, 0x29, 0xb9, 0x3a, 0xd3, 0x29, 0x23, 0x02, + 0x60, 0xf7, 0xab, 0x30, 0x40, 0xda, 0x8e, 0x4d, 0x45, 0x70, 0x26, 0xf4, + 0xa2, 0x0d, 0xd0, 0x64, 0x5d, 0x47, 0x3c, 0x18, 0xf4, 0xd4, 0x52, 0x95, + 0x00, 0xae, 0x84, 0x6b, 0x47, 0xb2, 0x3c, 0x82, 0xd3, 0x72, 0x53, 0xde, + 0x72, 0x2c, 0xf7, 0xc1, 0x22, 0x36, 0xd9, 0x18, 0x56, 0xfe, 0x39, 0x28, + 0x33, 0xe0, 0xdb, 0x03, +}; + +static const uint8_t kTestSandbox[] = { 0x01, 0x02, 0x03 }; + +} // namespace wvoec + +#endif // CDM_OEC_TEST_DATA_H_ diff --git a/oemcrypto/test/oemcrypto_corpus_generator_helper.cpp b/oemcrypto/test/oemcrypto_corpus_generator_helper.cpp new file mode 100644 index 0000000..33c0f7a --- /dev/null +++ b/oemcrypto/test/oemcrypto_corpus_generator_helper.cpp @@ -0,0 +1,34 @@ +/* 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 "oemcrypto_corpus_generator_helper.h" + +#include +#include + +namespace wvoec { +bool g_generate_corpus; + +void AppendToFile(const std::string& file_name, const char* message, + const size_t message_size) { + std::ofstream filebuf(file_name.c_str(), std::ios::app | std::ios::binary); + if (!filebuf) { + std::cout << "Cannot open file " << file_name.c_str() << std::endl; + } + filebuf.write(message, message_size); + filebuf.close(); +} + +std::string GetFileName(const char* directory) { + std::string file_name(PATH_TO_CORPUS); + file_name += directory; + file_name += "/"; + file_name += std::to_string(rand()); + return file_name; +} + +void SetGenerateCorpus(bool should_generate_corpus) { + g_generate_corpus = should_generate_corpus; +} +bool ShouldGenerateCorpus() { return g_generate_corpus; } +} // namespace wvoec diff --git a/oemcrypto/test/oemcrypto_corpus_generator_helper.h b/oemcrypto/test/oemcrypto_corpus_generator_helper.h new file mode 100644 index 0000000..e145fe6 --- /dev/null +++ b/oemcrypto/test/oemcrypto_corpus_generator_helper.h @@ -0,0 +1,25 @@ +/* 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 CDM_OEMCRYPTO_CORPUS_GENERATOR_HELPER_H_ +#define CDM_OEMCRYPTO_CORPUS_GENERATOR_HELPER_H_ + +#define PATH_TO_CORPUS "./oemcrypto/test/fuzz_tests/corpus/" + +#include +#include +#include + +namespace wvoec { +void AppendToFile(const std::string& file_name, const char* message, + const size_t message_size); + +std::string GetFileName(const char* directory); + +void SetGenerateCorpus(bool should_generate_corpus); +// Output of this function decides if binary data needs to be written +// to corpus files or not. Controlled by --generate_corpus flag. +bool ShouldGenerateCorpus(); +} // namespace wvoec + +#endif // CDM_OEMCRYPTO_CORPUS_GENERATOR_HELPER_H_ diff --git a/oemcrypto/test/oemcrypto_session_tests_helper.cpp b/oemcrypto/test/oemcrypto_session_tests_helper.cpp new file mode 100644 index 0000000..da8d5ec --- /dev/null +++ b/oemcrypto/test/oemcrypto_session_tests_helper.cpp @@ -0,0 +1,86 @@ +#include "oemcrypto_session_tests_helper.h" + +#include +#include "oec_test_data.h" + +using namespace std; +using namespace wvoec; + +namespace wvoec { + +// Make this function available when in Fuzz mode because we are not inheriting +// from OEMCryptoClientTest. +const uint8_t* find(const vector& message, + const vector& substring) { + vector::const_iterator pos = search( + message.begin(), message.end(), substring.begin(), substring.end()); + if (pos == message.end()) { + return nullptr; + } + return &(*pos); +} + +// This creates a wrapped RSA key. +void SessionUtil::CreateWrappedRSAKey() { + Session s; + ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_); + provisioning_messages.PrepareSession(keybox_); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, provisioning_messages.LoadResponse()); + wrapped_rsa_key_ = provisioning_messages.wrapped_rsa_key(); +} + +void SessionUtil::InstallKeybox(const wvoec::WidevineKeybox& keybox, + bool good) { + uint8_t wrapped[sizeof(wvoec::WidevineKeybox)]; + size_t length = sizeof(wvoec::WidevineKeybox); + keybox_ = keybox; + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_WrapKeybox(reinterpret_cast(&keybox), + sizeof(keybox), wrapped, &length, nullptr, 0)); + OEMCryptoResult sts = OEMCrypto_InstallKeybox(wrapped, sizeof(keybox)); + if (good) { + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + } else { + // Can return error now, or return error on IsKeyboxValid. + } +} + +void SessionUtil::EnsureTestKeys() { + switch (global_features.derive_key_method) { + case DeviceFeatures::LOAD_TEST_KEYBOX: + keybox_ = kTestKeybox; + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_LoadTestKeybox(reinterpret_cast(&keybox_), + sizeof(keybox_))); + break; + case DeviceFeatures::LOAD_TEST_RSA_KEY: + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_LoadTestRSAKey()); + break; + case DeviceFeatures::TEST_PROVISION_30: + // Can use oem certificate to install test rsa key. + break; + default: + FAIL() << "Cannot run test without test keybox or RSA key installed."; + } +} + +// This makes sure that the derived keys (encryption key and two mac keys) +// are installed in OEMCrypto and in the test session. +void SessionUtil::InstallTestRSAKey(Session* s) { + if (global_features.loads_certificate) { + if (wrapped_rsa_key_.size() == 0) { + // If we don't have a wrapped key yet, create one. + // This wrapped key will be shared by all sessions in the test. + ASSERT_NO_FATAL_FAILURE(CreateWrappedRSAKey()); + } + // Load the wrapped rsa test key. + ASSERT_NO_FATAL_FAILURE(s->InstallRSASessionTestKey(wrapped_rsa_key_)); + } + // Test RSA key should be loaded. + ASSERT_NO_FATAL_FAILURE(s->PreparePublicKey()); +} +} // namespace wvoec diff --git a/oemcrypto/test/oemcrypto_session_tests_helper.h b/oemcrypto/test/oemcrypto_session_tests_helper.h new file mode 100644 index 0000000..a16ba5a --- /dev/null +++ b/oemcrypto/test/oemcrypto_session_tests_helper.h @@ -0,0 +1,38 @@ +#include +#include +#include +#include +#include + +#include "oec_session_util.h" +#include "oec_test_data.h" +#include "OEMCryptoCAS.h" + +namespace wvoec { + +class SessionUtil { +public: + SessionUtil() + : encoded_rsa_key_(kTestRSAPKCS8PrivateKeyInfo2_2048, + kTestRSAPKCS8PrivateKeyInfo2_2048 + + sizeof(kTestRSAPKCS8PrivateKeyInfo2_2048)) {} + + // Create a new wrapped DRM Certificate. + void CreateWrappedRSAKey(); + + // This is used to force installation of a keybox. This overwrites the + // production keybox -- it does NOT use OEMCrypto_LoadTestKeybox. + void InstallKeybox(const wvoec::WidevineKeybox& keybox, bool good); + + // This loads the test keybox or the test RSA key, using LoadTestKeybox or + // LoadTestRSAKey as needed. + void EnsureTestKeys(); + + void InstallTestRSAKey(Session* s); + + std::vector encoded_rsa_key_; + std::vector wrapped_rsa_key_; + wvoec::WidevineKeybox keybox_; +}; + +} // namespace wvoec diff --git a/oemcrypto/test/oemcrypto_test.cpp b/oemcrypto/test/oemcrypto_test.cpp new file mode 100644 index 0000000..1ad58ad --- /dev/null +++ b/oemcrypto/test/oemcrypto_test.cpp @@ -0,0 +1,6645 @@ +// 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. +// +// OEMCrypto unit tests +// +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "OEMCryptoCAS.h" +#include "clock.h" +#include "log.h" +#include "oec_decrypt_fallback_chain.h" +#include "oec_device_features.h" +#include "oec_session_util.h" +#include "oec_test_data.h" +#include "oemcrypto_session_tests_helper.h" +#include "oemcrypto_types.h" +#include "platform.h" +#include "string_conversions.h" +#include "test_sleep.h" +#include "wvcrc32.h" + +using ::testing::Bool; +using ::testing::Combine; +using ::testing::Range; +using ::testing::tuple; +using ::testing::Values; +using ::testing::WithParamInterface; +using namespace std; + +namespace std { // GTest wants PrintTo to be in the std namespace. +void PrintTo(const tuple& param, + ostream* os) { + OEMCrypto_CENCEncryptPatternDesc pattern = ::testing::get<0>(param); + OEMCryptoCipherMode mode = ::testing::get<1>(param); + wvoec::OutputType output = ::testing::get<2>(param); + bool decrypt_inplace = output.decrypt_inplace; + OEMCryptoBufferType type = output.type; + *os << ((mode == OEMCrypto_CipherMode_CTR) ? "CTR mode" : "CBC mode") + << ", pattern=(encrypt:" << pattern.encrypt << ", skip:" << pattern.skip + << ")"; + switch (type) { + case OEMCrypto_BufferType_Clear: + *os << ", BufferType = Clear"; + break; + case OEMCrypto_BufferType_Secure: + *os << ", BufferType = Secure"; + break; + case OEMCrypto_BufferType_Direct: + *os << ", BufferType = Direct"; + break; + default: + *os << ", type = "; + break; + } + if (decrypt_inplace) *os << " (in place)"; +} +} // namespace std + +namespace wvoec { +namespace { +constexpr size_t kBufferOverrunPadding = 16; + +// Resource tiers: +constexpr size_t KiB = 1024; +constexpr size_t MiB = 1024 * 1024; +// With OEMCrypto v15 and above, we have different resource requirements +// depending on the resource rating reported by OEMCrypto. This function looks +// up the required value for the specified resource for the target OEMCrypto +// library. +template +T GetResourceValue(T (&resource_values)[N]) { + if (global_features.resource_rating < 1) return resource_values[0]; + if (global_features.resource_rating > N) return resource_values[N - 1]; + return resource_values[global_features.resource_rating - 1]; +} + +// After API 16, we require 300 entries in the usage table. Before API 16, we +// required 200. +size_t RequiredUsageSize() { + return global_features.api_version < 16 ? 200 : 300; +} + +// These are the maximum sizes we test. That means it is the minimum size that +// OEMCrypto must support. +// clang-format off +const size_t kMaxSampleSize[] = { 1*MiB, 2*MiB, 4*MiB, 16*MiB}; +const size_t kMaxNumberSubsamples[] = { 10, 16, 32, 64}; +const size_t kMaxSubsampleSize[] = { 100*KiB, 500*KiB, 1*MiB, 4*MiB}; +const size_t kMaxGenericBuffer[] = { 10*KiB, 100*KiB, 500*KiB, 1*MiB}; +const size_t kMaxConcurrentSession[] = { 10, 20, 20, 30}; +const size_t kMaxKeysPerSession[] = { 4, 20, 20, 30}; +const size_t kMaxTotalKeys[] = { 16, 40, 80, 90}; +const size_t kLargeMessageSize[] = { 8*KiB, 8*KiB, 16*KiB, 32*KiB}; +// Note: Frame rate and simultaneous playback are specified by resource rating, +// but are tested at the system level, so there are no unit tests for frame +// rate. Similarly, number of subsamples for AV1 +// const size_t kAV1NumberSubsamples[] = { 72, 144, 288, 576}; +// clang-format on + +} // namespace + +class OEMCryptoClientTest : public ::testing::Test, public SessionUtil { + protected: + OEMCryptoClientTest() {} + + void SetUp() override { + ::testing::Test::SetUp(); + wvutil::TestSleep::SyncFakeClock(); + const ::testing::TestInfo* const test_info = + ::testing::UnitTest::GetInstance()->current_test_info(); + LOGD("Running test %s.%s", test_info->test_case_name(), test_info->name()); + OEMCrypto_SetSandbox(kTestSandbox, sizeof(kTestSandbox)); + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_Initialize()); + } + + void TearDown() override { + OEMCrypto_Terminate(); + ::testing::Test::TearDown(); + } + + const uint8_t* find(const vector& message, + const vector& substring) { + vector::const_iterator pos = search( + message.begin(), message.end(), substring.begin(), substring.end()); + if (pos == message.end()) { + return nullptr; + } + return &(*pos); + } +}; + +// +// General tests. +// This test is first, becuase it might give an idea why other +// tests are failing when the device has the wrong keybox installed. +TEST_F(OEMCryptoClientTest, VersionNumber) { + const std::string log_message = + "OEMCrypto unit tests for API 16.3. Tests last updated 2020-06-01"; + cout << " " << log_message << "\n"; + LOGI("%s", log_message.c_str()); + // If any of the following fail, then it is time to update the log message + // above. + EXPECT_EQ(ODK_MAJOR_VERSION, 16); + EXPECT_EQ(ODK_MINOR_VERSION, 3); + EXPECT_EQ(kCurrentAPI, 16u); + const char* level = OEMCrypto_SecurityLevel(); + ASSERT_NE(nullptr, level); + ASSERT_EQ('L', level[0]); + cout << " OEMCrypto Security Level is " << level << endl; + uint32_t version = OEMCrypto_APIVersion(); + cout << " OEMCrypto API version is " << version << endl; + if (OEMCrypto_SupportsUsageTable()) { + cout << " OEMCrypto supports usage tables" << endl; + } else { + cout << " OEMCrypto does not support usage tables" << endl; + } + if (version >= 15) { + const uint32_t tier = OEMCrypto_ResourceRatingTier(); + cout << " Resource Rating Tier: " << tier << endl; + const char* build_info = OEMCrypto_BuildInformation(); + ASSERT_NE(nullptr, build_info); + ASSERT_TRUE(strnlen(build_info, 256) <= 256) + << "BuildInformation should be a short printable string."; + cout << " BuildInformation: " << build_info << endl; + } + ASSERT_GE(version, 8u); + ASSERT_LE(version, kCurrentAPI); +} + +// The resource rating is a number from 1 to 4. The first three levels were +// initially defined in API 15 and they were expanded in API 16. +TEST_F(OEMCryptoClientTest, ResourceRatingAPI15) { + ASSERT_GE(OEMCrypto_ResourceRatingTier(), 1u); + ASSERT_LE(OEMCrypto_ResourceRatingTier(), 4u); +} + +// OEMCrypto must declare what type of provisioning scheme it uses. +TEST_F(OEMCryptoClientTest, ProvisioningDeclaredAPI12) { + OEMCrypto_ProvisioningMethod provisioning_method = + OEMCrypto_GetProvisioningMethod(); + cout << " Provisioning method = " + << ProvisioningMethodName(provisioning_method) << endl; + ASSERT_NE(OEMCrypto_ProvisioningError, provisioning_method); +} + +const char* HDCPCapabilityAsString(OEMCrypto_HDCP_Capability value) { + switch (value) { + case HDCP_NONE: + return "No HDCP supported, no secure data path"; + case HDCP_V1: + return "HDCP version 1.0"; + case HDCP_V2: + return "HDCP version 2.0"; + case HDCP_V2_1: + return "HDCP version 2.1"; + case HDCP_V2_2: + return "HDCP version 2.2"; + case HDCP_V2_3: + return "HDCP version 2.3"; + case HDCP_NO_DIGITAL_OUTPUT: + return "No HDCP device attached/using local display with secure path"; + default: + return ""; + } +} + +TEST_F(OEMCryptoClientTest, CheckHDCPCapabilityAPI09) { + OEMCryptoResult sts; + OEMCrypto_HDCP_Capability current, maximum; + sts = OEMCrypto_GetHDCPCapability(¤t, &maximum); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + printf(" Current HDCP Capability: 0x%02x = %s.\n", current, + HDCPCapabilityAsString(current)); + printf(" Maximum HDCP Capability: 0x%02x = %s.\n", maximum, + HDCPCapabilityAsString(maximum)); +} + +TEST_F(OEMCryptoClientTest, CheckSRMCapabilityV13) { + // This just tests some trivial functionality of the SRM update functions. + bool supported = OEMCrypto_IsSRMUpdateSupported(); + printf(" Update SRM Supported: %s.\n", + supported ? "true" : "false"); + uint16_t version = 0; + OEMCryptoResult current_result = OEMCrypto_GetCurrentSRMVersion(&version); + if (current_result == OEMCrypto_SUCCESS) { + printf(" Current SRM Version: %d.\n", version); + EXPECT_NE(OEMCrypto_SUCCESS, OEMCrypto_GetCurrentSRMVersion(nullptr)); + } else if (current_result == OEMCrypto_LOCAL_DISPLAY_ONLY) { + printf(" Current SRM Status: Local Display Only.\n"); + } else { + EXPECT_EQ(OEMCrypto_ERROR_NOT_IMPLEMENTED, current_result); + } + vector bad_srm(42); + GetRandBytes(bad_srm.data(), bad_srm.size()); + EXPECT_NE(OEMCrypto_SUCCESS, + OEMCrypto_LoadSRM(bad_srm.data(), bad_srm.size())); +} + +TEST_F(OEMCryptoClientTest, CheckMaxNumberOfSessionsAPI10) { + size_t sessions_count; + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GetNumberOfOpenSessions(&sessions_count)); + ASSERT_EQ(0u, sessions_count); + size_t maximum; + OEMCryptoResult sts = OEMCrypto_GetMaxNumberOfSessions(&maximum); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + printf(" Max Number of Sessions: %zu.\n", maximum); + size_t required_max = GetResourceValue(kMaxConcurrentSession); + ASSERT_GE(maximum, required_max); +} + +TEST_F(OEMCryptoClientTest, CheckUsageTableSizeAPI16) { + const size_t maximum = OEMCrypto_MaximumUsageTableHeaderSize(); + printf(" Max Usage Table Size: %zu.\n", maximum); + // A maximum of 0 means the table is constrained by dynamic memory allocation. + if (maximum > 0) ASSERT_GE(maximum, RequiredUsageSize()); +} + +// +// initialization tests +// +TEST_F(OEMCryptoClientTest, NormalInitTermination) { + // Should be able to terminate OEMCrypto, and then restart it. + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_Terminate()); + OEMCrypto_SetSandbox(kTestSandbox, sizeof(kTestSandbox)); + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_Initialize()); +} + +// +// Session Tests +// +TEST_F(OEMCryptoClientTest, NormalSessionOpenClose) { + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.close()); +} + +TEST_F(OEMCryptoClientTest, TwoSessionsOpenClose) { + Session s1; + Session s2; + ASSERT_NO_FATAL_FAILURE(s1.open()); + ASSERT_NO_FATAL_FAILURE(s2.open()); + ASSERT_NO_FATAL_FAILURE(s1.close()); + ASSERT_NO_FATAL_FAILURE(s2.close()); +} + +// This test verifies that OEMCrypto can open approximately as many sessions as +// it claims. +TEST_F(OEMCryptoClientTest, MaxSessionsOpenCloseAPI10) { + size_t sessions_count; + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GetNumberOfOpenSessions(&sessions_count)); + ASSERT_EQ(0u, sessions_count); + size_t max_sessions; + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_GetMaxNumberOfSessions(&max_sessions)); + // We expect OEMCrypto implementations support at least this many sessions. + size_t required_number = GetResourceValue(kMaxConcurrentSession); + ASSERT_GE(max_sessions, required_number); + // We allow GetMaxNumberOfSessions to return an estimate. This tests with a + // pad of 5%. Even if it's just an estimate, we still require 8 sessions. + size_t max_sessions_with_pad = max(max_sessions * 19 / 20, required_number); + vector sessions; + // Limit the number of sessions for testing. + const size_t kMaxNumberOfSessionsForTesting = 0x100u; + for (size_t i = 0; i < kMaxNumberOfSessionsForTesting; i++) { + OEMCrypto_SESSION session_id; + OEMCryptoResult sts = OEMCrypto_OpenSession(&session_id); + // GetMaxNumberOfSessions might be an estimate. We allow OEMCrypto to report + // a max that is less than what is actually supported. Assume the number + // returned is |max|. OpenSessions shall not fail if number of active + // sessions is less than |max|; OpenSessions should fail with + // OEMCrypto_ERROR_TOO_MANY_SESSIONS if too many sessions are open. + if (sts != OEMCrypto_SUCCESS) { + ASSERT_EQ(OEMCrypto_ERROR_TOO_MANY_SESSIONS, sts); + ASSERT_GE(i, max_sessions_with_pad); + break; + } + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GetNumberOfOpenSessions(&sessions_count)); + ASSERT_EQ(i + 1, sessions_count); + sessions.push_back(session_id); + } + for (size_t i = 0; i < sessions.size(); i++) { + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CloseSession(sessions[i])); + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GetNumberOfOpenSessions(&sessions_count)); + ASSERT_EQ(sessions.size() - i - 1, sessions_count); + } + if (sessions.size() == kMaxNumberOfSessionsForTesting) { + printf( + " MaxSessionsOpenClose: reaches " + "kMaxNumberOfSessionsForTesting(%zu). GetMaxNumberOfSessions = %zu. " + "ERROR_TOO_MANY_SESSIONS not tested.", + kMaxNumberOfSessionsForTesting, max_sessions); + } +} + +// Verify that GetRandom does work, and does some sanity checks on how random +// the data is. Basically, we say that calling GetRandom twice should not +// generate much overlap. +TEST_F(OEMCryptoClientTest, GetRandomLargeBuffer) { + // 32 bytes. Not very large, but that's all we really need in one call. + const size_t size = 32; + uint8_t data1[size]; + uint8_t data2[size]; + memset(data1, 0, size); + memset(data2, 0, size); + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_GetRandom(data1, size)); + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_GetRandom(data2, size)); + // We don't have enough data to see that the data is really random, + // so we'll just do a spot check that two calls don't return the same values. + int count = 0; + for (size_t i = 0; i < size; i++) { + if (data1[i] == data2[i]) count++; + } + ASSERT_LE(count, 3); // P(count > 3) = 1/256^3 = 6e-8. +} + +TEST_F(OEMCryptoClientTest, GenerateNonce) { + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + s.GenerateNonce(); +} + +// Prevent a nonce flood even if each nonce is in a different session. +TEST_F(OEMCryptoClientTest, PreventNonceFlood2API16) { + int error_counter = 0; + const int64_t test_start = wvutil::Clock().GetCurrentTime(); + // More than 200 nonces per second should generate an error. + // To allow for some slop, we actually test for more. + const int flood_cutoff = 200; + const int loop_count = flood_cutoff * 2; + for (int i = 0; i < loop_count; i++) { + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + s.GenerateNonce(&error_counter); + } + const int64_t test_end = wvutil::Clock().GetCurrentTime(); + int valid_counter = loop_count - error_counter; + // Either oemcrypto should enforce a delay, or it should return an error from + // GenerateNonce -- in either case the number of valid nonces is rate + // limited. We add two seconds to allow for round off error in both + // test_start and test_end. + EXPECT_LE(valid_counter, flood_cutoff * (test_end - test_start + 2)); + error_counter = 0; + // After a pause, we should be able to regenerate nonces. + wvutil::TestSleep::Sleep(2); + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + s.GenerateNonce(&error_counter); + EXPECT_EQ(0, error_counter); +} + +// Prevent a nonce flood even if some nonces are in a different session. This +// is different from the test above because there are several session open at +// the same time. We want to make sure you can't get a flood of nonces by +// opening a flood of sessions. +TEST_F(OEMCryptoClientTest, PreventNonceFlood3API16) { + int request_counter = 0; + int error_counter = 0; + const int64_t test_start = wvutil::Clock().GetCurrentTime(); + // More than 200 nonces per second should generate an error. + // To allow for some slop, we actually test for more. + const int flood_cutoff = 200; + const size_t session_count = GetResourceValue(kMaxConcurrentSession); + const size_t loop_count = 2 * flood_cutoff / session_count + 1; + for (size_t i = 0; i < loop_count; i++) { + std::vector s(session_count); + for (size_t j = 0; j < session_count; j++) { + ASSERT_NO_FATAL_FAILURE(s[j].open()); + request_counter++; + s[j].GenerateNonce(&error_counter); + } + } + const int64_t test_end = wvutil::Clock().GetCurrentTime(); + int valid_counter = request_counter - error_counter; + // Either oemcrypto should enforce a delay, or it should return an error from + // GenerateNonce -- in either case the number of valid nonces is rate + // limited. We add two seconds to allow for round off error in both + // test_start and test_end. + EXPECT_LE(valid_counter, flood_cutoff * (test_end - test_start + 2)); + error_counter = 0; + // After a pause, we should be able to regenerate nonces. + wvutil::TestSleep::Sleep(2); + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + s.GenerateNonce(&error_counter); + EXPECT_EQ(0, error_counter); +} + +// This verifies that CopyBuffer works, even before a license has been loaded. +TEST_F(OEMCryptoClientTest, ClearCopyTestAPI10) { + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + const int kDataSize = 256; + vector input_buffer(kDataSize); + GetRandBytes(input_buffer.data(), input_buffer.size()); + vector output_buffer(kDataSize); + OEMCrypto_DestBufferDesc dest_buffer_descriptor; + dest_buffer_descriptor.type = OEMCrypto_BufferType_Clear; + dest_buffer_descriptor.buffer.clear.address = output_buffer.data(); + dest_buffer_descriptor.buffer.clear.address_length = output_buffer.size(); + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_CopyBuffer(s.session_id(), input_buffer.data(), + input_buffer.size(), &dest_buffer_descriptor, + OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample)); + ASSERT_EQ(input_buffer, output_buffer); + ASSERT_EQ( + OEMCrypto_ERROR_INVALID_CONTEXT, + OEMCrypto_CopyBuffer(s.session_id(), nullptr, input_buffer.size(), + &dest_buffer_descriptor, + OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample)); + ASSERT_EQ(OEMCrypto_ERROR_INVALID_CONTEXT, + OEMCrypto_CopyBuffer( + s.session_id(), input_buffer.data(), input_buffer.size(), + nullptr, OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample)); + dest_buffer_descriptor.buffer.clear.address = nullptr; + ASSERT_EQ( + OEMCrypto_ERROR_INVALID_CONTEXT, + OEMCrypto_CopyBuffer(s.session_id(), input_buffer.data(), + input_buffer.size(), &dest_buffer_descriptor, + OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample)); + dest_buffer_descriptor.buffer.clear.address = output_buffer.data(); + dest_buffer_descriptor.buffer.clear.address_length = output_buffer.size() - 1; + ASSERT_EQ( + OEMCrypto_ERROR_SHORT_BUFFER, + OEMCrypto_CopyBuffer(s.session_id(), input_buffer.data(), + input_buffer.size(), &dest_buffer_descriptor, + OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample)); +} + +// This verifies that CopyBuffer works on the maximum required buffer size. +TEST_F(OEMCryptoClientTest, ClearCopyTestLargeSubsample) { + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + size_t max_size = GetResourceValue(kMaxSubsampleSize); + vector input_buffer(max_size); + GetRandBytes(input_buffer.data(), input_buffer.size()); + vector output_buffer(max_size); + OEMCrypto_DestBufferDesc dest_buffer_descriptor; + dest_buffer_descriptor.type = OEMCrypto_BufferType_Clear; + dest_buffer_descriptor.buffer.clear.address = output_buffer.data(); + dest_buffer_descriptor.buffer.clear.address_length = output_buffer.size(); + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_CopyBuffer(s.session_id(), input_buffer.data(), + input_buffer.size(), &dest_buffer_descriptor, + OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample)); + ASSERT_EQ(input_buffer, output_buffer); +} + +TEST_F(OEMCryptoClientTest, CanLoadTestKeys) { + ASSERT_NE(DeviceFeatures::NO_METHOD, global_features.derive_key_method) + << "Session tests cannot run with out a test keybox or RSA cert."; +} + +// Tests using this class are only used for devices with a keybox. They are not +// run for devices with an OEM Certificate. +class OEMCryptoKeyboxTest : public OEMCryptoClientTest { + void SetUp() override { + OEMCryptoClientTest::SetUp(); + OEMCryptoResult sts = OEMCrypto_IsKeyboxValid(); + // If the production keybox is valid, use it for these tests. Most of the + // other tests will use a test keybox anyway, but it's nice to check the + // device ID for the real keybox if we can. + if (sts == OEMCrypto_SUCCESS) return; + printf("Production keybox is NOT valid. All tests use test keybox.\n"); + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_LoadTestKeybox(reinterpret_cast(&kTestKeybox), + sizeof(kTestKeybox))); + } +}; + +// This test is used to print the device ID to stdout. +TEST_F(OEMCryptoKeyboxTest, NormalGetDeviceId) { + OEMCryptoResult sts; + uint8_t dev_id[128] = {0}; + size_t dev_id_len = 128; + sts = OEMCrypto_GetDeviceID(dev_id, &dev_id_len); + cout << " NormalGetDeviceId: dev_id = " << dev_id + << " len = " << dev_id_len << endl; + ASSERT_EQ(OEMCrypto_SUCCESS, sts); +} + +TEST_F(OEMCryptoKeyboxTest, GetDeviceIdShortBuffer) { + OEMCryptoResult sts; + uint8_t dev_id[128]; + for (int i = 0; i < 128; ++i) { + dev_id[i] = 0x55; + } + dev_id[127] = '\0'; + size_t dev_id_len = 0; + sts = OEMCrypto_GetDeviceID(dev_id, &dev_id_len); + ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts); + // On short buffer error, function should return minimum buffer length + ASSERT_GT(dev_id_len, 0u); + // Should also return short buffer if passed a zero length and a null buffer. + dev_id_len = 0; + sts = OEMCrypto_GetDeviceID(nullptr, &dev_id_len); + ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts); + // On short buffer error, function should return minimum buffer length + ASSERT_GT(dev_id_len, 0u); +} + +TEST_F(OEMCryptoKeyboxTest, NormalGetKeyData) { + OEMCryptoResult sts; + uint8_t key_data[256]; + size_t key_data_len = sizeof(key_data); + sts = OEMCrypto_GetKeyData(key_data, &key_data_len); + + uint32_t* data = reinterpret_cast(key_data); + printf(" NormalGetKeyData: system_id = %d = 0x%04X, version=%d\n", + htonl(data[1]), htonl(data[1]), htonl(data[0])); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); +} + +TEST_F(OEMCryptoKeyboxTest, GetKeyDataNullPointer) { + OEMCryptoResult sts; + uint8_t key_data[256]; + sts = OEMCrypto_GetKeyData(key_data, nullptr); + ASSERT_NE(OEMCrypto_SUCCESS, sts); +} + +// This test makes sure the installed keybox is valid. It doesn't really check +// that it is a production keybox. That must be done by an integration test. +TEST_F(OEMCryptoKeyboxTest, ProductionKeyboxValid) { + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_IsKeyboxValid()); +} + +// This tests GenerateDerivedKeys with an 8k context. +TEST_F(OEMCryptoKeyboxTest, GenerateDerivedKeysFromKeyboxLargeBuffer) { + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + const size_t max_size = GetResourceValue(kLargeMessageSize); + vector mac_context(max_size); + vector enc_context(max_size); + // Stripe the data so the two vectors are not identical, and not all zeroes. + for (size_t i = 0; i < max_size; i++) { + mac_context[i] = i % 0x100; + enc_context[i] = (3 * i) % 0x100; + } + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GenerateDerivedKeys( + s.session_id(), mac_context.data(), mac_context.size(), + enc_context.data(), enc_context.size())); +} + +// This class is for tests that have an OEM Certificate instead of a keybox. +class OEMCryptoProv30Test : public OEMCryptoClientTest {}; + +// This verifies that the device really does claim to have a certificate. +// It should be filtered out for devices that have a keybox. +TEST_F(OEMCryptoProv30Test, DeviceClaimsOEMCertificate) { + ASSERT_EQ(OEMCrypto_OEMCertificate, OEMCrypto_GetProvisioningMethod()); +} + +TEST_F(OEMCryptoProv30Test, GetDeviceId) { + OEMCryptoResult sts; + std::vector dev_id(128, 0); + size_t dev_id_len = dev_id.size(); + sts = OEMCrypto_GetDeviceID(dev_id.data(), &dev_id_len); + if (sts == OEMCrypto_ERROR_SHORT_BUFFER) { + ASSERT_GT(dev_id_len, 0u); + dev_id.resize(dev_id_len); + sts = OEMCrypto_GetDeviceID(dev_id.data(), &dev_id_len); + } + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + dev_id.resize(dev_id_len); + cout << " NormalGetDeviceId: dev_id = " << dev_id.data() + << " len = " << dev_id_len << endl; + ASSERT_EQ(OEMCrypto_SUCCESS, sts); +} + +// The OEM certificate must be valid. +TEST_F(OEMCryptoProv30Test, CertValidAPI15) { + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_IsKeyboxOrOEMCertValid()); +} + +TEST_F(OEMCryptoProv30Test, OEMCertValid) { + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + bool kVerify = true; + ASSERT_NO_FATAL_FAILURE(s.LoadOEMCert(kVerify)); // Load and verify. +} + +// This verifies that the OEM Certificate cannot be used for other RSA padding +// schemes. Those schemes should only be used by cast receiver certificates. +TEST_F(OEMCryptoProv30Test, OEMCertForbiddenPaddingScheme) { + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.LoadOEMCert()); + OEMCryptoResult sts; + // Sign a Message + vector data(500); + GetRandBytes(data.data(), data.size()); + size_t signature_length = 0; + // We need a size one vector to pass as a pointer. + vector signature(1, 0); + vector zero(1, 0); + + sts = OEMCrypto_GenerateRSASignature(s.session_id(), data.data(), data.size(), + signature.data(), &signature_length, + kSign_PKCS1_Block1); + if (OEMCrypto_ERROR_SHORT_BUFFER == sts) { + // The OEMCrypto could complain about buffer length first, so let's + // resize and check if it's writing to the signature again. + signature.resize(signature_length, 0); + zero.resize(signature_length, 0); + sts = OEMCrypto_GenerateRSASignature(s.session_id(), data.data(), + data.size(), signature.data(), + &signature_length, kSign_PKCS1_Block1); + } + EXPECT_NE(OEMCrypto_SUCCESS, sts) + << "OEM Cert Signed with forbidden kSign_PKCS1_Block1."; + ASSERT_EQ(zero, signature); // signature should not be computed. +} + +// Calling OEMCrypto_GetOEMPublicCertificate should not change the session's +// private key. +TEST_F(OEMCryptoProv30Test, GetCertOnlyAPI16) { + if (wrapped_rsa_key_.size() == 0) { + // If we don't have a wrapped key yet, create one. + // This wrapped key will be shared by all sessions in the test. + ASSERT_NO_FATAL_FAILURE(CreateWrappedRSAKey()); + } + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + // Install the DRM Cert's RSA key. + ASSERT_NO_FATAL_FAILURE(s.InstallRSASessionTestKey(wrapped_rsa_key_)); + ASSERT_NO_FATAL_FAILURE(s.PreparePublicKey()); + // Request the OEM Cert. -- This should NOT load the OEM Private key. + vector public_cert; + size_t public_cert_length = 0; + ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, + OEMCrypto_GetOEMPublicCertificate(nullptr, &public_cert_length)); + ASSERT_LT(0u, public_cert_length); + public_cert.resize(public_cert_length); + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_GetOEMPublicCertificate( + public_cert.data(), &public_cert_length)); + // Derive keys from the session key -- this should use the DRM Cert's key. It + // should NOT use the OEM Private key because that key should not have been + // loaded. + ASSERT_NO_FATAL_FAILURE(s.GenerateDerivedKeysFromSessionKey()); + // Now fill a message and try to load it. + LicenseRoundTrip license_messages(&s); + license_messages.set_control(0); + ASSERT_NO_FATAL_FAILURE(license_messages.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages.LoadResponse()); +} + +// +// AddKey Tests +// +// These tests will use either a test keybox or a test certificate to derive +// session keys. +class OEMCryptoSessionTests : public OEMCryptoClientTest { + public: + vector encrypted_usage_header_; + + protected: + OEMCryptoSessionTests() {} + + void SetUp() override { + OEMCryptoClientTest::SetUp(); + EnsureTestKeys(); + if (global_features.usage_table) { + CreateUsageTableHeader(); + } + } + + void CreateUsageTableHeader(bool expect_success = true) { + size_t header_buffer_length = 0; + OEMCryptoResult sts = + OEMCrypto_CreateUsageTableHeader(nullptr, &header_buffer_length); + if (expect_success) { + ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts); + } else { + ASSERT_NE(OEMCrypto_SUCCESS, sts); + if (sts != OEMCrypto_ERROR_SHORT_BUFFER) return; + } + encrypted_usage_header_.resize(header_buffer_length); + sts = OEMCrypto_CreateUsageTableHeader(encrypted_usage_header_.data(), + &header_buffer_length); + if (expect_success) { + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + } else { + ASSERT_NE(OEMCrypto_SUCCESS, sts); + } + } +}; + +class OEMCryptoSessionTestKeyboxTest : public OEMCryptoSessionTests {}; + +TEST_F(OEMCryptoSessionTestKeyboxTest, TestKeyboxIsValid) { + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_IsKeyboxValid()); +} + +// This class is for testing a single license with the default API version +// of 16. +class OEMCryptoLicenseTestAPI16 : public OEMCryptoSessionTests { + public: + OEMCryptoLicenseTestAPI16() + : license_api_version_(kCurrentAPI), license_messages_(&session_) {} + + void SetUp() override { + OEMCryptoSessionTests::SetUp(); + ASSERT_NO_FATAL_FAILURE(session_.open()); + ASSERT_NO_FATAL_FAILURE(InstallTestRSAKey(&session_)); + } + + void TearDown() override { + ASSERT_NO_FATAL_FAILURE(session_.close()); + OEMCryptoSessionTests::TearDown(); + } + + protected: + Session session_; + uint32_t license_api_version_; + LicenseRoundTrip license_messages_; +}; + +// This class is used to test a license that is from a server either that is +// current or one version old. +class OEMCryptoLicenseTest : public OEMCryptoLicenseTestAPI16, + public WithParamInterface { + protected: + void SetUp() override { + // The only difference between this class and its parent is that we use a + // different license api: + license_api_version_ = GetParam(); + license_messages_.set_api_version(license_api_version_); + OEMCryptoLicenseTestAPI16::SetUp(); + } +}; + +// This class is used to test a license that is only for v15 license. +class OEMCryptoLicenseTestAPI15 : public OEMCryptoLicenseTestAPI16 { + void SetUp() override { + // The only difference between this class and its parent is that we use a + // different license api: + license_api_version_ = 15; + license_messages_.set_api_version(license_api_version_); + OEMCryptoLicenseTestAPI16::SetUp(); + } +}; + +// This class is used to test each key control block verification string in the +// range kc09-kc1?. This test is parameterized by the API number in the key +// control lock. +class OEMCryptoLicenseTestRangeAPI : public OEMCryptoLicenseTest {}; + +// Verify that a license may be signed. +TEST_P(OEMCryptoLicenseTest, SignLicenseRequest) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); +} + +TEST_P(OEMCryptoLicenseTest, SignLicenseRequestNoNonce) { + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); +} + +// Verify that a large license request may be signed. +TEST_P(OEMCryptoLicenseTest, SignLargeLicenseRequest) { + const size_t max_size = GetResourceValue(kLargeMessageSize); + license_messages_.set_message_size(max_size); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); +} + +// Verify that a license may be loaded without a nonce. +TEST_P(OEMCryptoLicenseTest, LoadKeyNoNonce) { + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + license_messages_.set_control(0); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); +} + +// Verify that a preloaded license may be loaded without first signing the +// request. This test is important for the preloaded licenses used by ATSC and +// CAS. +TEST_P(OEMCryptoLicenseTest, LoadKeyWithNoRequest) { + if (license_api_version_ > global_features.api_version) { + // We should not attempt to preload a license with an API higher than that + // of OEMCrypto. + license_api_version_ = global_features.api_version; + license_messages_.set_api_version(license_api_version_); + } + license_messages_.set_control(0); + // The test code uses the core request to create the core response. + license_messages_.core_request().api_major_version = ODK_MAJOR_VERSION; + license_messages_.core_request().api_minor_version = ODK_MINOR_VERSION; + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + + // Load license in a different session, which did not create the request. + Session session2; + ASSERT_NO_FATAL_FAILURE(session2.open()); + ASSERT_NO_FATAL_FAILURE(InstallTestRSAKey(&session2)); + ASSERT_NO_FATAL_FAILURE(session2.GenerateDerivedKeysFromSessionKey()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse(&session2)); +} + +// Verify that a license may be loaded with a nonce. +TEST_P(OEMCryptoLicenseTest, LoadKeyWithNonce) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); +} + +// Verify that a second license may not be loaded in a session. +TEST_P(OEMCryptoLicenseTest, LoadKeyNoNonceTwiceAPI16) { + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + license_messages_.set_control(0); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + // A second load, should NOT succeed. + ASSERT_EQ(OEMCrypto_ERROR_LICENSE_RELOAD, license_messages_.LoadResponse()); +} + +// Verify that a second license may not be loaded in a session. +TEST_P(OEMCryptoLicenseTest, LoadKeyWithNonceTwiceAPI16) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + // A second load, should NOT succeed. + ASSERT_EQ(OEMCrypto_ERROR_LICENSE_RELOAD, license_messages_.LoadResponse()); +} + +// This verifies that entitlement keys and entitled content keys can be loaded. +TEST_P(OEMCryptoLicenseTest, LoadEntitlementKeysAPI17) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + license_messages_.set_license_type(OEMCrypto_EntitlementLicense); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + uint32_t key_session_id = 0; + OEMCryptoResult sts = OEMCrypto_CreateEntitledKeySession( + session_.session_id(), &key_session_id); + if (sts == OEMCrypto_ERROR_NOT_IMPLEMENTED) { + return; + } + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + ASSERT_NE(key_session_id, 0u); + + EntitledMessage entitled_message_1(&license_messages_); + entitled_message_1.FillKeyArray(); + entitled_message_1.SetEntitledKeySession(key_session_id); + ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadKeys(OEMCrypto_SUCCESS)); + EntitledMessage entitled_message_2(&license_messages_); + entitled_message_2.FillKeyArray(); + entitled_message_2.SetEntitledKeySession(key_session_id); + ASSERT_NO_FATAL_FAILURE(entitled_message_2.LoadKeys(OEMCrypto_SUCCESS)); +} + +// This verifies that entitled content keys cannot be loaded if we have not yet +// loaded the entitlement keys. +TEST_P(OEMCryptoLicenseTest, LoadEntitlementKeysNoEntitlementKeysAPI17) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + license_messages_.set_license_type(OEMCrypto_EntitlementLicense); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + uint32_t key_session_id = 0; + OEMCryptoResult sts = OEMCrypto_CreateEntitledKeySession( + session_.session_id(), &key_session_id); + if (sts == OEMCrypto_ERROR_NOT_IMPLEMENTED) { + return; + } + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + ASSERT_NE(key_session_id, 0u); + + EntitledMessage entitled_message_1(&license_messages_); + entitled_message_1.FillKeyArray(); + entitled_message_1.SetEntitledKeySession(key_session_id); + ASSERT_NO_FATAL_FAILURE( + entitled_message_1.LoadKeys(OEMCrypto_ERROR_INVALID_CONTEXT)); +} + +// This verifies that entitled content keys cannot be loaded if we have loaded +// the wrong entitlement keys. +TEST_P(OEMCryptoLicenseTest, LoadEntitlementKeysWrongEntitlementKeysAPI17) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + license_messages_.set_license_type(OEMCrypto_EntitlementLicense); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + uint32_t key_session_id = 0; + OEMCryptoResult sts = OEMCrypto_CreateEntitledKeySession( + session_.session_id(), &key_session_id); + if (sts == OEMCrypto_ERROR_NOT_IMPLEMENTED) { + return; + } + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + ASSERT_NE(key_session_id, 0u); + + EntitledMessage entitled_message_1(&license_messages_); + entitled_message_1.FillKeyArray(); + const std::string key_id = "no_key"; + entitled_message_1.SetEntitlementKeyId(0, key_id); + entitled_message_1.SetEntitledKeySession(key_session_id); + ASSERT_NO_FATAL_FAILURE( + entitled_message_1.LoadKeys(OEMCrypto_KEY_NOT_ENTITLED)); +} + +// This verifies that entitled content keys cannot be loaded if we specify an +// entitled key session that has not been created. +TEST_P(OEMCryptoLicenseTest, LoadEntitlementKeysWrongEntitledKeySessionAPI17) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + license_messages_.set_license_type(OEMCrypto_EntitlementLicense); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + uint32_t key_session_id = 0; + OEMCryptoResult sts = OEMCrypto_CreateEntitledKeySession( + session_.session_id(), &key_session_id); + if (sts == OEMCrypto_ERROR_NOT_IMPLEMENTED) { + return; + } + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + ASSERT_NE(key_session_id, 0u); + + EntitledMessage entitled_message_1(&license_messages_); + entitled_message_1.FillKeyArray(); + entitled_message_1.SetEntitledKeySession(0); + ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadKeys( + OEMCrypto_ERROR_INVALID_ENTITLED_KEY_SESSION)); +} + +// This verifies that entitled content keys cannot be loaded if we specify an +// entitled key session that is actually an oemcrypto session. +TEST_P(OEMCryptoLicenseTest, LoadEntitlementKeysOemcryptoSessionAPI17) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + license_messages_.set_license_type(OEMCrypto_EntitlementLicense); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + uint32_t key_session_id = 0; + OEMCryptoResult sts = OEMCrypto_CreateEntitledKeySession( + session_.session_id(), &key_session_id); + if (sts == OEMCrypto_ERROR_NOT_IMPLEMENTED) { + return; + } + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + ASSERT_NE(key_session_id, 0u); + + EntitledMessage entitled_message_1(&license_messages_); + entitled_message_1.FillKeyArray(); + entitled_message_1.SetEntitledKeySession(session_.session_id()); + ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadKeys( + OEMCrypto_ERROR_INVALID_ENTITLED_KEY_SESSION)); +} + +// This tests load license with an 8k license response. +TEST_P(OEMCryptoLicenseTest, LoadKeyLargeBuffer) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + const size_t max_size = GetResourceValue(kLargeMessageSize); + license_messages_.set_message_size(max_size); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); +} + +// Verify that you can't use LoadKeys on a v16 license. +TEST_F(OEMCryptoLicenseTestAPI16, UseWrongLoadAPI16) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + // After the license round trip was create for v16, we now tell it to use v15 + // so it call LoadKeys instead of LoadLicense. This means the license request + // was made from a v16 device, and the response was created and signed by a + // v16 server. So OEMCrypto should only accept it if we load it using + // LoadLicense. A call to LoadKeys should fail. + license_messages_.set_api_version(kCoreMessagesAPI - 1); + ASSERT_NE(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); +} + +//---------------------------------------------------------------------------// +//---------------------------------------------------------------------------// +// Each of the following LoadKeyWithBadRange_* tests is similar. They verify +// that OEMCrypto_LoadLicense checks the range of all the pointers. It should +// reject a message if the pointer does not point into the message buffer. +//---------------------------------------------------------------------------// +//---------------------------------------------------------------------------// +TEST_P(OEMCryptoLicenseTest, LoadKeyWithBadRange_enc_mac_keys) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + // See the comment in LicenseRoundTrip::LoadResponse for why we increment by + // the message size. + license_messages_.core_response().enc_mac_keys.offset += + sizeof(license_messages_.response_data()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_NE(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); +} + +TEST_P(OEMCryptoLicenseTest, LoadKeyWithBadRange_enc_mac_keys_iv) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + // See the comment in LicenseRoundTrip::LoadResponse for why we increment by + // the message size. + license_messages_.core_response().enc_mac_keys_iv.offset += + sizeof(license_messages_.response_data()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_NE(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); +} + +TEST_P(OEMCryptoLicenseTest, LoadKeyWithBadRange_key_id) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + // See the comment in LicenseRoundTrip::LoadResponse for why we increment by + // the message size. + license_messages_.core_response().key_array[0].key_id.offset += + sizeof(license_messages_.response_data()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_NE(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); +} + +TEST_P(OEMCryptoLicenseTest, LoadKeyWithBadRange_key_data) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + // See the comment in LicenseRoundTrip::LoadResponse for why we increment by + // the message size. + license_messages_.core_response().key_array[1].key_data.offset += + sizeof(license_messages_.response_data()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_NE(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); +} + +TEST_P(OEMCryptoLicenseTest, LoadKeyWithBadRange_key_data_iv) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + // See the comment in LicenseRoundTrip::LoadResponse for why we increment by + // the message size. + license_messages_.core_response().key_array[1].key_data_iv.offset += + sizeof(license_messages_.response_data()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_NE(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); +} + +TEST_P(OEMCryptoLicenseTest, LoadKeyWithBadRange_key_control) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + // See the comment in LicenseRoundTrip::LoadResponse for why we increment by + // the message size. + license_messages_.core_response().key_array[2].key_control.offset += + sizeof(license_messages_.response_data()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_NE(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); +} + +TEST_P(OEMCryptoLicenseTest, LoadKeyWithBadRange_key_control_iv) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + // See the comment in LicenseRoundTrip::LoadResponse for why we increment by + // the message size. + license_messages_.core_response().key_array[2].key_control_iv.offset += + sizeof(license_messages_.response_data()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_NE(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); +} + +TEST_P(OEMCryptoLicenseTest, LoadKeyWithBadRange_pst) { + license_messages_.set_control(wvoec::kControlNonceOrEntry); + license_messages_.set_pst("my_pst"); + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + // See the comment in LicenseRoundTrip::LoadResponse for why we increment by + // the message size. + license_messages_.core_response().pst.offset += + sizeof(license_messages_.response_data()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + // If we have a pst, then we need a usage entry. + ASSERT_NO_FATAL_FAILURE(session_.CreateNewUsageEntry()); + ASSERT_NE(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); +} +//---------------------------------------------------------------------------// +//---------------------------------------------------------------------------// + +// The IV should not be identical to the data right before the encrypted mac +// keys. This requirement was added in 15.2, so it frequently fails on +// production devices. +// This test is being restricted to v16 devices on rvc-dev branch because we +// only required v15.1 on Android for Q. +TEST_F(OEMCryptoLicenseTestAPI15, LoadKeyWithSuspiciousIVAPI16) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + + // This is suspicious: the data right before the mac keys is identical to the + // iv. + memcpy(license_messages_.response_data().padding, + license_messages_.response_data().mac_key_iv, + sizeof(license_messages_.response_data().padding)); + + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_NE(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); +} + +// Test that LoadKeys fails when a key is loaded with no key control block. +TEST_P(OEMCryptoLicenseTest, LoadKeyWithNullKeyControl) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + license_messages_.core_response().key_array[2].key_control.offset = 0; + license_messages_.core_response().key_array[2].key_control.length = 0; + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_NE(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); +} + +// Test that LoadKeys fails when the key control block encryption has a null IV. +TEST_P(OEMCryptoLicenseTest, LoadKeyWithNullKeyControlIv) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + license_messages_.core_response().key_array[2].key_control_iv.offset = 0; + license_messages_.core_response().key_array[2].key_control_iv.length = 0; + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_NE(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); +} + +// Verify that LoadKeys fails when a key's nonce is wrong. +TEST_P(OEMCryptoLicenseTest, LoadKeyWithBadNonce) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + for (unsigned int i = 0; i < license_messages_.num_keys(); i++) + license_messages_.response_data().keys[i].control.nonce ^= 42; + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_ERROR_INVALID_NONCE, license_messages_.LoadResponse()); +} + +// Verify that LoadKeys fails when the core message's nonce is wrong. +TEST_F(OEMCryptoLicenseTestAPI16, LoadKeyWithBadNonce2) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + license_messages_.core_request().nonce ^= 42; + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_ERROR_INVALID_NONCE, license_messages_.LoadResponse()); +} + +// Verify that LoadKeys fails when the core message's session is wrong. +TEST_F(OEMCryptoLicenseTestAPI16, LoadKeyWithBadNonce3) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + license_messages_.core_request().session_id++; + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_ERROR_INVALID_NONCE, license_messages_.LoadResponse()); +} + +// Verify that LoadKeys fails when an attempt is made to use a nonce twice. +TEST_P(OEMCryptoLicenseTest, LoadKeyWithRepeatNonce) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + const uint32_t nonce = session_.nonce(); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + // This is the first attempt. It should succeed. + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + + // Now, open a new session and try to load a license with the same nonce. + session_.close(); + ASSERT_NO_FATAL_FAILURE(session_.open()); + ASSERT_NO_FATAL_FAILURE(InstallTestRSAKey(&session_)); + license_messages_.skip_nonce_check(); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + // Repeat the nonce. + license_messages_.core_request().nonce = nonce; + for (unsigned int i = 0; i < license_messages_.num_keys(); i++) + license_messages_.response_data().keys[i].control.nonce = htonl(nonce); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_ERROR_INVALID_NONCE, license_messages_.LoadResponse()); +} + +// This tests that a nonce cannot be used in new session. This is similar to +// the previous test, but does not use the nonce in the first session. The nonce +// should be tied to a session, so generating a nonce in the first session and +// then using it in the second session should fail. +TEST_P(OEMCryptoLicenseTest, LoadKeyNonceReopenSession) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + uint32_t nonce = session_.nonce(); + // Do not use the nonce now. Close session and use it after re-opening. + ASSERT_NO_FATAL_FAILURE(session_.close()); + + // Actually, this isn't the same session. OEMCrypto opens a new session, but + // we are guarding against the possiblity that it re-uses the session data + // and might not clear out the nonce correctly. + ASSERT_NO_FATAL_FAILURE(session_.open()); + ASSERT_NO_FATAL_FAILURE(InstallTestRSAKey(&session_)); + license_messages_.skip_nonce_check(); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + license_messages_.core_request().nonce = nonce; + for (unsigned int i = 0; i < license_messages_.num_keys(); i++) + license_messages_.response_data().keys[i].control.nonce = htonl(nonce); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_ERROR_INVALID_NONCE, license_messages_.LoadResponse()); +} + +// This tests that a nonce cannot be used in wrong session. This is similar to +// the previous test, except we do not close session 1 before we open session 2. +TEST_P(OEMCryptoLicenseTest, LoadKeyNonceWrongSession) { + // First, open a session and generate a nonce. We don't use the nonce in this + // session. + Session s2; + ASSERT_NO_FATAL_FAILURE(s2.open()); + ASSERT_NO_FATAL_FAILURE(InstallTestRSAKey(&s2)); + ASSERT_NO_FATAL_FAILURE(s2.GenerateNonce()); + uint32_t nonce = s2.nonce(); + + // Do not use the nonce. Also, leave the session open. We want to make sure + // that session_ and s2 do NOT share a nonce. This is different from + // the LoadKeyNonceReopenSession in that we do not close s1. + + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + license_messages_.core_request().nonce = nonce; + for (unsigned int i = 0; i < license_messages_.num_keys(); i++) + license_messages_.response_data().keys[i].control.nonce = htonl(nonce); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_ERROR_INVALID_NONCE, license_messages_.LoadResponse()); +} + +// LoadKeys should fail if the key control block as a bad verification string. +TEST_P(OEMCryptoLicenseTest, LoadKeyWithBadVerification) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + license_messages_.response_data().keys[1].control.verification[2] = 'Z'; + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_NE(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); +} + +// This test verifies that LoadKeys still works when the message is not aligned +// in memory on a word (2 or 4 byte) boundary. +TEST_P(OEMCryptoLicenseTest, LoadKeyUnalignedMessageAPI16) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + + std::vector buffer(1, '0'); // A string of 1 byte long. + size_t offset = buffer.size(); + ASSERT_EQ(1u, offset); + // We assume that vectors are allocated on as a small chunk of data that is + // aligned on a word boundary. I.e. we assume buffer is word aligned. Next, + // we append the message to buffer after the single padding byte. + buffer.insert(buffer.end(), + license_messages_.encrypted_response_buffer().begin(), + license_messages_.encrypted_response_buffer().end()); + // Thus, buffer[offset] is NOT word aligned. + const uint8_t* unaligned_message = &buffer[offset]; + if (license_api_version_ < kCoreMessagesAPI) { + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_LoadKeys( + session_.session_id(), unaligned_message, + license_messages_.encrypted_response_buffer().size(), + license_messages_.response_signature().data(), + license_messages_.response_signature().size(), + license_messages_.core_response().enc_mac_keys_iv, + license_messages_.core_response().enc_mac_keys, + license_messages_.core_response().key_array_length, + license_messages_.core_response().key_array, + license_messages_.core_response().pst, + license_messages_.core_response().srm_restriction_data, + static_cast( + license_messages_.core_response().license_type))); + } else { + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_LoadLicense( + session_.session_id(), unaligned_message, + license_messages_.encrypted_response_buffer().size(), + license_messages_.serialized_core_message().size(), + license_messages_.response_signature().data(), + license_messages_.response_signature().size())); + } +} + +// Verifies that a session can't reload a license without being closed and +// reopened. +TEST_P(OEMCryptoLicenseTest, LoadLicenseAgainFailureAPI16) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + ASSERT_EQ(OEMCrypto_ERROR_LICENSE_RELOAD, license_messages_.LoadResponse()); +} + +TEST_P(OEMCryptoLicenseTestRangeAPI, LoadKeys) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + // Re-set the API version. The function VerifyRequestSignature sets the api to + // be a sane value. But in this test, we want to verify an unsupported version + // is rejected. + license_messages_.set_api_version(license_api_version_); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + + // If this is a future API, then LoadKeys should fail. + if (global_features.api_version < license_api_version_) { + ASSERT_NE(OEMCrypto_SUCCESS, license_messages_.LoadResponse()) + << "Load License succeeded for future api kc" << license_api_version_; + } else { + // Otherwise, LoadKeys should succeed. + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()) + << "Load License failed for key control block kc" + << license_api_version_; + } +} + +// Range of API versions to test. This should start several versions old, and +// go to the current API + 2. We use +2 because we want to test at least 1 +// future API, and the ::testing::Range is not inclusive. +INSTANTIATE_TEST_CASE_P(TestAll, OEMCryptoLicenseTestRangeAPI, + Range(10, kCurrentAPI + 2)); + +TEST_P(OEMCryptoLicenseTest, LoadKeysBadSignatureAPI16) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + license_messages_.response_signature()[0] ^= 42; + ASSERT_EQ(OEMCrypto_ERROR_SIGNATURE_FAILURE, + license_messages_.LoadResponse()); +} + +TEST_F(OEMCryptoLicenseTestAPI16, BadCoreHashAPI16) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + license_messages_.BreakRequestHash(); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_NE(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); +} + +// LoadKeys should fail if we try to load keys with no keys. +TEST_P(OEMCryptoLicenseTest, LoadKeyNoKeys) { + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + license_messages_.set_control(0); + license_messages_.set_num_keys(0); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_NE(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); +} + +// Like the previous test, except we ask for a nonce first. +TEST_P(OEMCryptoLicenseTest, LoadKeyNoKeyWithNonce) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + license_messages_.set_num_keys(0); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_NE(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); +} + +// SelectKey should fail if we attempt to select a key that has not been loaded. +// Also, the error should be NO_CONTENT_KEY. +// This test should pass for v15 devices, except that the exact error code was +// not specified until v16. +TEST_P(OEMCryptoLicenseTest, SelectKeyNotThereAPI16) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + + const char* key_id = "no_key"; + OEMCryptoResult sts = OEMCrypto_SelectKey( + session_.session_id(), reinterpret_cast(key_id), + strlen(key_id), OEMCrypto_CipherMode_CTR); + if (sts != OEMCrypto_SUCCESS) { + EXPECT_EQ(OEMCrypto_ERROR_NO_CONTENT_KEY, sts); + } else { + // Delayed error code. If select key was a success, then we should + // eventually see the error when we decrypt. + vector in_buffer(256); + vector out_buffer(in_buffer.size()); + OEMCrypto_SampleDescription sample_description; + OEMCrypto_SubSampleDescription subsample_description; + + ASSERT_NO_FATAL_FAILURE(GenerateSimpleSampleDescription( + in_buffer, out_buffer, &sample_description, &subsample_description)); + + // Generate test data + for (size_t i = 0; i < in_buffer.size(); i++) in_buffer[i] = i % 256; + + // Create the pattern description (always 0,0 for CTR) + OEMCrypto_CENCEncryptPatternDesc pattern = {0, 0}; + + // Try to decrypt the data + sts = OEMCrypto_DecryptCENC(session_.session_id(), &sample_description, 1, + &pattern); + EXPECT_EQ(sts, OEMCrypto_ERROR_NO_CONTENT_KEY); + } +} + +TEST_P(OEMCryptoLicenseTest, SelectKeyEntitledKeyAPI17) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + license_messages_.set_license_type(OEMCrypto_EntitlementLicense); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + + uint32_t key_session_id; + OEMCryptoResult sts = OEMCrypto_CreateEntitledKeySession( + session_.session_id(), &key_session_id); + if (sts == OEMCrypto_ERROR_NOT_IMPLEMENTED) { + return; + } + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + EntitledMessage entitled_message_1(&license_messages_); + entitled_message_1.FillKeyArray(); + entitled_message_1.SetEntitledKeySession(key_session_id); + const char* content_key_id = "content_key_id"; + entitled_message_1.SetContentKeyId(0, content_key_id); + ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadKeys(OEMCrypto_SUCCESS)); + + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_SelectKey(key_session_id, + reinterpret_cast(content_key_id), + strlen(content_key_id), OEMCrypto_CipherMode_CTR)); +} + +// SelectEntitledKey should fail if we attempt to select a key that has not been +// loaded. Also, the error should be NO_CONTENT_KEY. +TEST_P(OEMCryptoLicenseTest, SelectKeyEntitledKeyNotThereAPI17) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + license_messages_.set_license_type(OEMCrypto_EntitlementLicense); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + + uint32_t key_session_id; + OEMCryptoResult sts = OEMCrypto_CreateEntitledKeySession( + session_.session_id(), &key_session_id); + if (sts == OEMCrypto_ERROR_NOT_IMPLEMENTED) { + return; + } + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + EntitledMessage entitled_message_1(&license_messages_); + entitled_message_1.FillKeyArray(); + entitled_message_1.SetEntitledKeySession(key_session_id); + ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadKeys(OEMCrypto_SUCCESS)); + + const char* content_key_id = "no_key"; + ASSERT_EQ( + OEMCrypto_ERROR_NO_CONTENT_KEY, + OEMCrypto_SelectKey(key_session_id, + reinterpret_cast(content_key_id), + strlen(content_key_id), OEMCrypto_CipherMode_CTR)); +} + +// Select key with entitlement license fails if the key id is entitilement key +// id. +TEST_P(OEMCryptoLicenseTest, SelectKeyEntitlementKeyAPI17) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + license_messages_.set_license_type(OEMCrypto_EntitlementLicense); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + + uint32_t key_session_id; + OEMCryptoResult sts = OEMCrypto_CreateEntitledKeySession( + session_.session_id(), &key_session_id); + if (sts == OEMCrypto_ERROR_NOT_IMPLEMENTED) { + return; + } + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + EntitledMessage entitled_message_1(&license_messages_); + entitled_message_1.FillKeyArray(); + entitled_message_1.SetEntitledKeySession(key_session_id); + ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadKeys(OEMCrypto_SUCCESS)); + + ASSERT_EQ(OEMCrypto_ERROR_INVALID_CONTEXT, + OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[0].key_id, + session_.license().keys[0].key_id_length, + OEMCrypto_CipherMode_CTR)); +} + +// This verifies that entitled key sessions can be created and removed. +TEST_P(OEMCryptoLicenseTest, EntitledKeySessionsAPI17) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + license_messages_.set_license_type(OEMCrypto_EntitlementLicense); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + + uint32_t key_session_id_1; + OEMCryptoResult sts = OEMCrypto_CreateEntitledKeySession( + session_.session_id(), &key_session_id_1); + if (sts == OEMCrypto_ERROR_NOT_IMPLEMENTED) { + return; + } + ASSERT_NE(key_session_id_1, 0u); // 0 is a reserved id number. + + uint32_t key_session_id_2; + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession( + session_.session_id(), &key_session_id_2)); + ASSERT_NE(key_session_id_2, 0u); // 0 is a reserved id number. + // Entitled key sessions should have unique ids. + ASSERT_NE(key_session_id_1, key_session_id_2); + + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_RemoveEntitledKeySession(key_session_id_1)); + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_RemoveEntitledKeySession(key_session_id_2)); +} + +// This verifies that multiple entitled key sessions can be created. They can +// load and select keys independently. +TEST_P(OEMCryptoLicenseTest, EntitledKeySessionMultipleKeySessionsAPI17) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + license_messages_.set_license_type(OEMCrypto_EntitlementLicense); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + + uint32_t key_session_id_1; + OEMCryptoResult sts = OEMCrypto_CreateEntitledKeySession( + session_.session_id(), &key_session_id_1); + if (sts == OEMCrypto_ERROR_NOT_IMPLEMENTED) { + return; + } + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + EntitledMessage entitled_message_1(&license_messages_); + entitled_message_1.FillKeyArray(); + entitled_message_1.SetEntitledKeySession(key_session_id_1); + const char* content_key_id_1 = "content_key_id_1"; + entitled_message_1.SetContentKeyId(0, content_key_id_1); + ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadKeys(OEMCrypto_SUCCESS)); + // We can select content key 1 in entitled key session 1. + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_SelectKey(key_session_id_1, + reinterpret_cast(content_key_id_1), + strlen(content_key_id_1), OEMCrypto_CipherMode_CTR)); + // Create another entitled key session. + uint32_t key_session_id_2; + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession( + session_.session_id(), &key_session_id_2)); + // Entitled key sessions should have unique ids. + ASSERT_NE(key_session_id_1, key_session_id_2); + + EntitledMessage entitled_message_2(&license_messages_); + entitled_message_2.FillKeyArray(); + entitled_message_2.SetEntitledKeySession(key_session_id_2); + const char* content_key_id_2 = "content_key_id_2"; + entitled_message_2.SetContentKeyId(0, content_key_id_2); + ASSERT_NO_FATAL_FAILURE(entitled_message_2.LoadKeys(OEMCrypto_SUCCESS)); + // We can select content key 2 in entitled key session 2. + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_SelectKey(key_session_id_2, + reinterpret_cast(content_key_id_2), + strlen(content_key_id_2), OEMCrypto_CipherMode_CTR)); + // Content key id 1 is not in entitled key session 2. + ASSERT_EQ( + OEMCrypto_ERROR_NO_CONTENT_KEY, + OEMCrypto_SelectKey(key_session_id_2, + reinterpret_cast(content_key_id_1), + strlen(content_key_id_1), OEMCrypto_CipherMode_CTR)); + // Content key id 2 is not in entitled key session 1. + ASSERT_EQ( + OEMCrypto_ERROR_NO_CONTENT_KEY, + OEMCrypto_SelectKey(key_session_id_1, + reinterpret_cast(content_key_id_2), + strlen(content_key_id_2), OEMCrypto_CipherMode_CTR)); +} + +// This verifies that within an entitled key session, each entitlement key can +// corresponds to only one content key at most. +TEST_P(OEMCryptoLicenseTest, + EntitledKeySessionOneContentKeyPerEntitlementAPI17) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + license_messages_.set_license_type(OEMCrypto_EntitlementLicense); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + + uint32_t key_session_id; + OEMCryptoResult sts = OEMCrypto_CreateEntitledKeySession( + session_.session_id(), &key_session_id); + if (sts == OEMCrypto_ERROR_NOT_IMPLEMENTED) { + return; + } + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + // Construct and load content keys to entitled key session. + EntitledMessage entitled_message_1(&license_messages_); + entitled_message_1.FillKeyArray(); + entitled_message_1.SetEntitledKeySession(key_session_id); + const char* content_key_id_1 = "content_key_id_1"; + entitled_message_1.SetContentKeyId(0, content_key_id_1); + ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadKeys(OEMCrypto_SUCCESS)); + // We can select content key 1 in entitled key session. + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_SelectKey(key_session_id, + reinterpret_cast(content_key_id_1), + strlen(content_key_id_1), OEMCrypto_CipherMode_CTR)); + // Load content key with new content id. + const char* content_key_id_2 = "content_key_id_2"; + entitled_message_1.SetContentKeyId(0, content_key_id_2); + ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadKeys(OEMCrypto_SUCCESS)); + // We can select content key 2 in entitled key session. + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_SelectKey(key_session_id, + reinterpret_cast(content_key_id_2), + strlen(content_key_id_2), OEMCrypto_CipherMode_CTR)); + // Content key one is no longer in the entitled key session as they use the + // same entitlement key. + ASSERT_EQ( + OEMCrypto_ERROR_NO_CONTENT_KEY, + OEMCrypto_SelectKey(key_session_id, + reinterpret_cast(content_key_id_1), + strlen(content_key_id_1), OEMCrypto_CipherMode_CTR)); +} + +// 'cens' mode is no longer supported in v16 +TEST_P(OEMCryptoLicenseTest, RejectCensAPI16) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + + OEMCryptoResult sts; + sts = OEMCrypto_SelectKey( + session_.session_id(), session_.license().keys[0].key_id, + session_.license().keys[0].key_id_length, OEMCrypto_CipherMode_CTR); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + + vector in_buffer(256); + vector out_buffer(in_buffer.size()); + OEMCrypto_SampleDescription sample_description; + OEMCrypto_SubSampleDescription subsample_description; + + ASSERT_NO_FATAL_FAILURE(GenerateSimpleSampleDescription( + in_buffer, out_buffer, &sample_description, &subsample_description)); + + // Create a non-zero pattern to indicate this is 'cens' + OEMCrypto_CENCEncryptPatternDesc pattern = {1, 9}; + + // Try to decrypt the data + sts = OEMCrypto_DecryptCENC(session_.session_id(), &sample_description, 1, + &pattern); + EXPECT_EQ(OEMCrypto_ERROR_INVALID_CONTEXT, sts); +} + +// 'cbc1' mode is no longer supported in v16 +TEST_P(OEMCryptoLicenseTest, RejectCbc1API16) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + + OEMCryptoResult sts; + sts = OEMCrypto_SelectKey( + session_.session_id(), session_.license().keys[0].key_id, + session_.license().keys[0].key_id_length, OEMCrypto_CipherMode_CBC); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + + vector in_buffer(256); + vector out_buffer(in_buffer.size()); + OEMCrypto_SampleDescription sample_description; + OEMCrypto_SubSampleDescription subsample_description; + + ASSERT_NO_FATAL_FAILURE(GenerateSimpleSampleDescription( + in_buffer, out_buffer, &sample_description, &subsample_description)); + + // Create a zero pattern to indicate this is 'cbc1' + OEMCrypto_CENCEncryptPatternDesc pattern = {0, 0}; + + // Try to decrypt the data + sts = OEMCrypto_DecryptCENC(session_.session_id(), &sample_description, 1, + &pattern); + EXPECT_EQ(OEMCrypto_ERROR_INVALID_CONTEXT, sts); +} + +TEST_P(OEMCryptoLicenseTest, RejectCbcsWithBlockOffset) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + + OEMCryptoResult sts; + sts = OEMCrypto_SelectKey( + session_.session_id(), session_.license().keys[0].key_id, + session_.license().keys[0].key_id_length, OEMCrypto_CipherMode_CBC); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + + vector in_buffer(256); + vector out_buffer(in_buffer.size()); + OEMCrypto_SampleDescription sample_description; + OEMCrypto_SubSampleDescription subsample_description; + + ASSERT_NO_FATAL_FAILURE(GenerateSimpleSampleDescription( + in_buffer, out_buffer, &sample_description, &subsample_description)); + subsample_description.block_offset = 5; // Any value 1-15 will do. + + // Create a non-zero pattern to indicate this is 'cbcs'. + OEMCrypto_CENCEncryptPatternDesc pattern = {1, 9}; + + // Try to decrypt the data + sts = OEMCrypto_DecryptCENC(session_.session_id(), &sample_description, 1, + &pattern); + EXPECT_EQ(OEMCrypto_ERROR_INVALID_CONTEXT, sts); +} + +// After loading keys, we should be able to query the key control block. If we +// attempt to query a key that has not been loaded, the error should be +// NO_CONTENT_KEY. +TEST_P(OEMCryptoLicenseTest, QueryKeyControl) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + + // Note: successful cases are tested in VerifyTestKeys. + KeyControlBlock block; + size_t size = sizeof(block) - 1; + OEMCryptoResult sts = OEMCrypto_QueryKeyControl( + session_.session_id(), license_messages_.response_data().keys[0].key_id, + license_messages_.response_data().keys[0].key_id_length, + reinterpret_cast(&block), &size); + if (sts == OEMCrypto_ERROR_NOT_IMPLEMENTED) { + return; + } + ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts); + const char* key_id = "no_key"; + size = sizeof(block); + ASSERT_EQ(OEMCrypto_ERROR_NO_CONTENT_KEY, + OEMCrypto_QueryKeyControl( + session_.session_id(), reinterpret_cast(key_id), + strlen(key_id), reinterpret_cast(&block), &size)); +} + +// If the device says it supports anti-rollback in the hardware, then it should +// accept a key control block with the anti-rollback hardware bit set. +// Otherwise, it should reject that key control block. +TEST_P(OEMCryptoLicenseTest, AntiRollbackHardwareRequired) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + license_messages_.set_control(wvoec::kControlRequireAntiRollbackHardware); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + OEMCryptoResult sts = license_messages_.LoadResponse(); + if (OEMCrypto_IsAntiRollbackHwPresent()) { + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + } else { + ASSERT_EQ(OEMCrypto_ERROR_UNKNOWN_FAILURE, sts); + } +} + +// This test verifies that OEMCrypto can load the number of keys required for +// the reported resource level. +TEST_P(OEMCryptoLicenseTest, MinimumKeys) { + const size_t num_keys = GetResourceValue(kMaxKeysPerSession); + ASSERT_LE(num_keys, kMaxNumKeys) << "Test constants need updating."; + license_messages_.set_num_keys(num_keys); + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + + constexpr bool kSelectKeyFirst = true; + for (size_t key_index = 0; key_index < num_keys; key_index++) { + ASSERT_NO_FATAL_FAILURE( + session_.TestDecryptCTR(kSelectKeyFirst, OEMCrypto_SUCCESS, key_index)); + } +} + +// This test verifies that OEMCrypto can load the total number of keys required +// for the reported resource level. +void TestMaxKeys(SessionUtil* util, size_t num_keys_per_session) { + const size_t max_total_keys = GetResourceValue(kMaxTotalKeys); + ASSERT_LE(num_keys_per_session, kMaxNumKeys) << "Update test constants."; + std::vector> sessions; + std::vector> licenses; + size_t total_keys = 0; + for (size_t i = 0; total_keys < max_total_keys; i++) { + sessions.push_back(std::unique_ptr(new Session())); + licenses.push_back(std::unique_ptr( + new LicenseRoundTrip(sessions[i].get()))); + const size_t num_keys = + std::min(max_total_keys - total_keys, num_keys_per_session); + licenses[i]->set_num_keys(num_keys); + total_keys += num_keys; + ASSERT_NO_FATAL_FAILURE(sessions[i]->open()); + ASSERT_NO_FATAL_FAILURE(util->InstallTestRSAKey(sessions[i].get())); + ASSERT_NO_FATAL_FAILURE(sessions[i]->GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(licenses[i]->SignAndVerifyRequest()); + } + for (size_t i = 0; i < licenses.size(); i++) { + ASSERT_NO_FATAL_FAILURE(licenses[i]->CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(licenses[i]->EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, licenses[i]->LoadResponse()); + } + constexpr bool kSelectKeyFirst = true; + for (size_t i = 0; i < licenses.size(); i++) { + for (size_t key_index = 0; key_index < licenses[i]->num_keys(); + key_index++) { + ASSERT_NO_FATAL_FAILURE(sessions[i]->TestDecryptCTR( + kSelectKeyFirst, OEMCrypto_SUCCESS, key_index)); + } + } + // Second call to decrypt for each session. + for (size_t i = 0; i < licenses.size(); i++) { + for (size_t key_index = 0; key_index < licenses[i]->num_keys(); + key_index++) { + ASSERT_NO_FATAL_FAILURE(sessions[i]->TestDecryptCTR( + kSelectKeyFirst, OEMCrypto_SUCCESS, key_index)); + } + } +} + +// This test verifies that OEMCrypto can load the total number of keys required +// for the reported resource level. This maximizes keys per session. +TEST_P(OEMCryptoLicenseTest, MaxTotalKeysPerSession) { + const size_t max_num_keys = GetResourceValue(kMaxKeysPerSession); + TestMaxKeys(this, max_num_keys); +} + +// This test verifies that OEMCrypto can load the total number of keys required +// for the reported resource level. This maximizes number of sessions. +TEST_P(OEMCryptoLicenseTest, MaxTotalKeysManySessions) { + const size_t max_total_keys = GetResourceValue(kMaxTotalKeys); + const size_t max_sessions = GetResourceValue(kMaxConcurrentSession); + const size_t max_num_keys = max_total_keys / max_sessions + 1; + TestMaxKeys(this, max_num_keys); +} + +// This test verifies that the minimum patch level can be required. The device +// should accept a key control block with the current patch level, and it should +// reject any key control blocks with a future patch level. +TEST_F(OEMCryptoSessionTests, CheckMinimumPatchLevel) { + uint8_t patch_level = OEMCrypto_Security_Patch_Level(); + printf(" Current Patch Level: %u.\n", patch_level); + { + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(InstallTestRSAKey(&s)); + LicenseRoundTrip license_messages(&s); + license_messages.set_control(patch_level + << wvoec::kControlSecurityPatchLevelShift); + ASSERT_NO_FATAL_FAILURE(license_messages.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages.EncryptAndSignResponse()); + EXPECT_EQ(global_features.api_version, license_messages.api_version()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages.LoadResponse()); + } + // Reject any future patch levels. + if (patch_level < 0x3F) { + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(InstallTestRSAKey(&s)); + LicenseRoundTrip license_messages(&s); + license_messages.set_control((patch_level + 1) + << wvoec::kControlSecurityPatchLevelShift); + ASSERT_NO_FATAL_FAILURE(license_messages.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_ERROR_UNKNOWN_FAILURE, license_messages.LoadResponse()); + } + // Accept an old patch level. + if (patch_level > 0) { + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(InstallTestRSAKey(&s)); + LicenseRoundTrip license_messages(&s); + license_messages.set_control((patch_level - 1) + << wvoec::kControlSecurityPatchLevelShift); + ASSERT_NO_FATAL_FAILURE(license_messages.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages.LoadResponse()); + } +} + +// Used to test the different HDCP versions. This test is parameterized by the +// required HDCP version in the key control block. +class OEMCryptoSessionTestDecryptWithHDCP : public OEMCryptoSessionTests, + public WithParamInterface { + protected: + void DecryptWithHDCP(OEMCrypto_HDCP_Capability version) { + OEMCryptoResult sts; + OEMCrypto_HDCP_Capability current, maximum; + sts = OEMCrypto_GetHDCPCapability(¤t, &maximum); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(InstallTestRSAKey(&s)); + LicenseRoundTrip license_messages(&s); + license_messages.set_control((version << wvoec::kControlHDCPVersionShift) | + wvoec::kControlObserveHDCP | + wvoec::kControlHDCPRequired); + ASSERT_NO_FATAL_FAILURE(license_messages.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages.LoadResponse()); + + if (version > current) { + if (global_features.api_version >= 16) { + // Can provide either OEMCrypto_WARNING_MIXED_OUTPUT_PROTECTION or + // OEMCrypto_ERROR_INSUFFICIENT_HDCP. TestDecryptCTR allows either to be + // reported if OEMCrypto_WARNING_MIXED_OUTPUT_PROTECTION is expected. + ASSERT_NO_FATAL_FAILURE( + s.TestDecryptCTR(true, OEMCrypto_WARNING_MIXED_OUTPUT_PROTECTION)) + << "Failed when current HDCP = " << HDCPCapabilityAsString(current) + << ", maximum HDCP = " << HDCPCapabilityAsString(maximum) + << ", license HDCP = " << HDCPCapabilityAsString(version); + } else { + ASSERT_NO_FATAL_FAILURE( + s.TestDecryptCTR(true, OEMCrypto_ERROR_INSUFFICIENT_HDCP)) + << "Failed when current HDCP = " << HDCPCapabilityAsString(current) + << ", maximum HDCP = " << HDCPCapabilityAsString(maximum) + << ", license HDCP = " << HDCPCapabilityAsString(version); + } + } else { + ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR(true, OEMCrypto_SUCCESS)) + << "Failed when current HDCP = " << HDCPCapabilityAsString(current) + << ", maximum HDCP = " << HDCPCapabilityAsString(maximum) + << ", license HDCP = " << HDCPCapabilityAsString(version); + } + } +}; + +TEST_P(OEMCryptoSessionTestDecryptWithHDCP, DecryptAPI09) { + // Test parameterized by HDCP version. + DecryptWithHDCP(static_cast(GetParam())); +} +INSTANTIATE_TEST_CASE_P(TestHDCP, OEMCryptoSessionTestDecryptWithHDCP, + Range(1, 6)); + +// +// Load, Refresh Keys Test +// +class OEMCryptoRefreshTest : public OEMCryptoLicenseTest { + protected: + void SetUp() override { + OEMCryptoLicenseTest::SetUp(); + // These values allow us to run a few simple duration tests or just start + // playback right away. All times are in seconds since the license was + // signed. + // Soft expiry false means timers are strictly enforce. + timer_limits_.soft_enforce_rental_duration = true; + timer_limits_.soft_enforce_playback_duration = false; + // Playback may begin immediately. + timer_limits_.earliest_playback_start_seconds = 0; + // First playback may be within the first two seconds. + timer_limits_.rental_duration_seconds = kDuration; + // Once started, playback may last two seconds without a renewal. + timer_limits_.initial_renewal_duration_seconds = kDuration; + // Total playback is not limited. + timer_limits_.total_playback_duration_seconds = 0; + } + + void LoadLicense() { + // If we require a nonce, then generate one. + if (license_messages_.control() & + (wvoec::kControlNonceEnabled | wvoec::kControlNonceOrEntry | + wvoec::kControlNonceRequired)) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + } + license_messages_.core_response().timer_limits = timer_limits_; + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + } + + void MakeRenewalRequest(RenewalRoundTrip* renewal_messages) { + ASSERT_NO_FATAL_FAILURE(renewal_messages->SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(renewal_messages->CreateDefaultResponse()); + } + + void LoadRenewal(RenewalRoundTrip* renewal_messages, + OEMCryptoResult expected_result) { + ASSERT_NO_FATAL_FAILURE(renewal_messages->EncryptAndSignResponse()); + ASSERT_EQ(expected_result, renewal_messages->LoadResponse()); + } + + ODK_TimerLimits timer_limits_; +}; + +// This class is for the refresh tests that should only be run on licenses with +// a core message. +class OEMCryptoRefreshTestAPI16 : public OEMCryptoRefreshTest {}; + +// Refresh keys should work if the license uses a nonce. +TEST_P(OEMCryptoRefreshTest, RefreshWithNonce) { + LoadLicense(); + RenewalRoundTrip renewal_messages(&license_messages_); + MakeRenewalRequest(&renewal_messages); + LoadRenewal(&renewal_messages, OEMCrypto_SUCCESS); +} + +// Refresh keys should work if the license does not use a nonce. +TEST_P(OEMCryptoRefreshTest, RefreshNoNonce) { + license_messages_.set_control(0); + LoadLicense(); + RenewalRoundTrip renewal_messages(&license_messages_); + MakeRenewalRequest(&renewal_messages); + LoadRenewal(&renewal_messages, OEMCrypto_SUCCESS); +} + +// Refresh keys should NOT work if a license has not been loaded. +TEST_P(OEMCryptoRefreshTestAPI16, RefreshNoLicense) { + Session s; + s.open(); + constexpr size_t message_size = kMaxCoreMessage + 42; + std::vector data(message_size); + for (size_t i = 0; i < data.size(); i++) data[i] = i & 0xFF; + size_t gen_signature_length = 0; + size_t core_message_length = 0; + OEMCryptoResult sts = OEMCrypto_PrepAndSignRenewalRequest( + s.session_id(), data.data(), data.size(), &core_message_length, nullptr, + &gen_signature_length); + ASSERT_LT(core_message_length, message_size); + if (sts == OEMCrypto_ERROR_SHORT_BUFFER) { + vector gen_signature(gen_signature_length); + sts = OEMCrypto_PrepAndSignRenewalRequest( + s.session_id(), data.data(), data.size(), &core_message_length, + gen_signature.data(), &gen_signature_length); + } + ASSERT_NE(OEMCrypto_SUCCESS, sts); +} + +// Refresh keys should fail if the nonce is not in the session. +TEST_P(OEMCryptoRefreshTestAPI16, RefreshBadNonce) { + LoadLicense(); + RenewalRoundTrip renewal_messages(&license_messages_); + MakeRenewalRequest(&renewal_messages); + renewal_messages.core_request().nonce ^= 42; + LoadRenewal(&renewal_messages, OEMCrypto_ERROR_INVALID_NONCE); +} + +// Refresh keys should fail if the session_id does not match the license. +TEST_P(OEMCryptoRefreshTestAPI16, RefreshBadSessionID) { + LoadLicense(); + RenewalRoundTrip renewal_messages(&license_messages_); + MakeRenewalRequest(&renewal_messages); + renewal_messages.core_request().session_id += 1; + LoadRenewal(&renewal_messages, OEMCrypto_ERROR_INVALID_NONCE); +} + +// Refresh keys should handle the maximum message size. +TEST_P(OEMCryptoRefreshTest, RefreshLargeBuffer) { + LoadLicense(); + RenewalRoundTrip renewal_messages(&license_messages_); + const size_t max_size = GetResourceValue(kLargeMessageSize); + renewal_messages.set_message_size(max_size); + MakeRenewalRequest(&renewal_messages); + LoadRenewal(&renewal_messages, OEMCrypto_SUCCESS); +} + +// This situation would occur if an app only uses one key in the license. When +// that happens, SelectKey would be called before the first decrypt, and then +// would not need to be called again, even if the license is refreshed. +TEST_P(OEMCryptoRefreshTest, RefreshWithNoSelectKey) { + LoadLicense(); + + // Call select key before the refresh. No calls below to TestDecryptCTR with + // select key set to true. + ASSERT_NO_FATAL_FAILURE(session_.TestDecryptCTR(true)); + + // This should still be valid key, even if the refresh failed, because this + // is before the original license duration. + wvutil::TestSleep::Sleep(kShortSleep); + ASSERT_NO_FATAL_FAILURE(session_.TestDecryptCTR(false)); + + // This should be after duration of the original license, but before the + // expiration of the refresh message. This should fail until we have loaded + // the renewal. + wvutil::TestSleep::Sleep(kShortSleep + kLongSleep); + ASSERT_NO_FATAL_FAILURE( + session_.TestDecryptCTR(false, OEMCrypto_ERROR_KEY_EXPIRED)); + + RenewalRoundTrip renewal_messages(&license_messages_); + MakeRenewalRequest(&renewal_messages); + LoadRenewal(&renewal_messages, OEMCrypto_SUCCESS); + + // After we've loaded the renewal, decrypt should succeed again. + ASSERT_NO_FATAL_FAILURE(session_.TestDecryptCTR(false)); +} + +INSTANTIATE_TEST_CASE_P(TestAll, OEMCryptoRefreshTest, + Range(kCurrentAPI - 1, kCurrentAPI + 1)); + +// These tests only work when the license has a core message. +INSTANTIATE_TEST_CASE_P(TestAPI16, OEMCryptoRefreshTestAPI16, + Range(kCoreMessagesAPI, kCurrentAPI + 1)); + +// If the license does not allow a hash, then we should not compute one. +TEST_P(OEMCryptoLicenseTest, HashForbiddenAPI15) { + uint32_t hash_type = OEMCrypto_SupportsDecryptHash(); + // If hash is not supported, or is vendor defined, don't try to test it. + if (hash_type != OEMCrypto_CRC_Clear_Buffer) return; + + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + + uint32_t frame_number = 1; + uint32_t hash = 42; + // It is OK to set the hash before loading the keys + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_SetDecryptHash(session_.session_id(), frame_number, + reinterpret_cast(&hash), + sizeof(hash))); + // It is OK to select the key and decrypt. + ASSERT_NO_FATAL_FAILURE(session_.TestDecryptCTR()); + // But the error code should be bad. + ASSERT_EQ(OEMCrypto_ERROR_UNKNOWN_FAILURE, + OEMCrypto_GetHashErrorCode(session_.session_id(), &frame_number)); +} + +// +// Decrypt Tests -- these test Decrypt CTR mode only. +// +TEST_P(OEMCryptoLicenseTest, Decrypt) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + license_messages_.core_response() + .timer_limits.total_playback_duration_seconds = kDuration; + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + ASSERT_NO_FATAL_FAILURE(session_.TestDecryptCTR()); +} + +// Verify that a zero duration means infinite license duration. +TEST_P(OEMCryptoLicenseTest, DecryptZeroDuration) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + license_messages_.core_response() + .timer_limits.total_playback_duration_seconds = 0; + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + ASSERT_NO_FATAL_FAILURE(session_.TestDecryptCTR()); +} + +struct SubsampleSize { + size_t clear_size; + size_t encrypted_size; + SubsampleSize(size_t clear, size_t encrypted) + : clear_size(clear), encrypted_size(encrypted) {} +}; + +// Struct for holding the data for one test sample in the decrypt tests. +struct TestSample { + // Encrypted data -- this is input to OEMCrypto, and output from EncryptData. + std::vector encrypted_buffer; + std::vector clear_buffer; // OEMCrypto store clear output here. + std::vector truth_buffer; // Truth data for clear text. + + OEMCrypto_SampleDescription description; + std::vector subsamples; + int secure_buffer_fid; +}; + +// A class of tests that test decryption for a variety of patterns and modes. +// This test is parameterized by three parameters: +// 1. The pattern used for pattern decryption. +// 2. The cipher mode for decryption: either CTR or CBC. +// 3. A boolean that determines if decrypt in place should be done. When the +// output buffer is clear, it should be possible for the input and output +// buffers to be the same. +class OEMCryptoSessionTestsDecryptTests + : public OEMCryptoLicenseTestAPI16, + public WithParamInterface> { + protected: + void SetUp() override { + OEMCryptoLicenseTestAPI16::SetUp(); + pattern_ = ::testing::get<0>(GetParam()); + cipher_mode_ = ::testing::get<1>(GetParam()); + decrypt_inplace_ = ::testing::get<2>(GetParam()).decrypt_inplace; + output_buffer_type_ = ::testing::get<2>(GetParam()).type; + verify_crc_ = global_features.supports_crc; + // Pick a random key. + EXPECT_EQ(GetRandBytes(key_, sizeof(key_)), 1); + // Pick a random starting iv. Some tests override this before using it. + EXPECT_EQ(GetRandBytes(initial_iv_, sizeof(initial_iv_)), 1); + } + + void TearDown() override { + FreeSecureBuffers(); + OEMCryptoLicenseTestAPI16::TearDown(); + } + + void SetSubsampleSizes(std::vector subsample_sizes) { + // This is just sugar for having one sample with the given subsamples in it. + SetSampleSizes({subsample_sizes}); + } + + void SetSampleSizes(std::vector> sample_sizes) { + ASSERT_GT(sample_sizes.size(), 0u); + samples_.clear(); + samples_.reserve(sample_sizes.size()); + + // Convert all the size arrays to TestSample structs + for (const std::vector& subsample_sizes : sample_sizes) { + // This could be one line if we had C++17 + samples_.emplace_back(); + TestSample& sample = samples_.back(); + + ASSERT_GT(subsample_sizes.size(), 0u); + sample.subsamples.reserve(subsample_sizes.size()); + + // Convert all the sizes to subsample descriptions and tally the total + // sample size + size_t sample_size = 0; + size_t current_block_offset = 0; + for (const SubsampleSize& size : subsample_sizes) { + sample.subsamples.push_back(OEMCrypto_SubSampleDescription{ + size.clear_size, size.encrypted_size, + 0, // Subsample Flags, to be filled in after the loop + current_block_offset}); + + // Update the rolling variables + sample_size += size.clear_size + size.encrypted_size; + if (cipher_mode_ == OEMCrypto_CipherMode_CTR) { + current_block_offset = + (current_block_offset + size.encrypted_size) % AES_BLOCK_SIZE; + } + } + + // Set the subsample flags now that all the subsamples are processed + sample.subsamples.front().subsample_flags |= OEMCrypto_FirstSubsample; + sample.subsamples.back().subsample_flags |= OEMCrypto_LastSubsample; + + // Set related information on the sample description + sample.description.subsamples = sample.subsamples.data(); + sample.description.subsamples_length = sample.subsamples.size(); + sample.description.buffers.input_data_length = sample_size; + } + } + + // Set up the input buffer and either a clear or secure output buffer for each + // test sample. This should be called after SetSubsampleSizes(). + void MakeBuffers() { + for (TestSample& sample : samples_) { + const size_t total_size = sample.description.buffers.input_data_length; + ASSERT_GT(total_size, 0u); + sample.encrypted_buffer.clear(); + sample.truth_buffer.clear(); + sample.clear_buffer.clear(); + sample.encrypted_buffer.resize(total_size); + sample.truth_buffer.resize(total_size); + for (size_t i = 0; i < total_size; i++) sample.truth_buffer[i] = i % 256; + + OEMCrypto_DestBufferDesc& output_descriptor = + sample.description.buffers.output_descriptor; + output_descriptor.type = output_buffer_type_; + switch (output_descriptor.type) { + case OEMCrypto_BufferType_Clear: + if (decrypt_inplace_) { + // Add some padding to verify there is no overrun. + sample.encrypted_buffer.resize(total_size + kBufferOverrunPadding, + 0xaa); + output_descriptor.buffer.clear.address = + sample.encrypted_buffer.data(); + } else { + // Add some padding to verify there is no overrun. + sample.clear_buffer.resize(total_size + kBufferOverrunPadding, + 0xaa); + output_descriptor.buffer.clear.address = sample.clear_buffer.data(); + } + output_descriptor.buffer.clear.address_length = total_size; + break; + + case OEMCrypto_BufferType_Secure: + output_descriptor.buffer.secure.handle_length = total_size; + ASSERT_EQ(OEMCrypto_AllocateSecureBuffer( + session_.session_id(), total_size, &output_descriptor, + &sample.secure_buffer_fid), + OEMCrypto_SUCCESS); + ASSERT_NE(output_descriptor.buffer.secure.handle, nullptr); + // It is OK if OEMCrypto changes the maximum size, but there must + // still be enough room for our data. + ASSERT_GE(output_descriptor.buffer.secure.handle_length, total_size); + output_descriptor.buffer.secure.offset = 0; + break; + + case OEMCrypto_BufferType_Direct: + output_descriptor.buffer.direct.is_video = false; + break; + } // switch (output_descriptor.type) + } // sample loop + } + + void FreeSecureBuffers() { + for (TestSample& sample : samples_) { + OEMCrypto_DestBufferDesc& output_descriptor = + sample.description.buffers.output_descriptor; + if (output_descriptor.type == OEMCrypto_BufferType_Secure) { + ASSERT_EQ(OEMCrypto_FreeSecureBuffer(session_.session_id(), + &output_descriptor, + sample.secure_buffer_fid), + OEMCrypto_SUCCESS); + } + } + } + + void EncryptData() { + AES_KEY aes_key; + AES_set_encrypt_key(key_, AES_BLOCK_SIZE * 8, &aes_key); + + for (TestSample& sample : samples_) { + uint8_t iv[KEY_IV_SIZE]; // Current IV + memcpy(iv, initial_iv_, KEY_IV_SIZE); + memcpy(sample.description.iv, initial_iv_, KEY_IV_SIZE); + + size_t buffer_index = 0; // byte index into in and out. + size_t block_offset = 0; // byte index into current block. + for (const OEMCrypto_SubSampleDescription& subsample : + sample.subsamples) { + // Copy clear content. + if (subsample.num_bytes_clear > 0) { + memcpy(&sample.encrypted_buffer[buffer_index], + &sample.truth_buffer[buffer_index], subsample.num_bytes_clear); + buffer_index += subsample.num_bytes_clear; + } + + // The IV resets at the start of each subsample in the 'cbcs' schema. + if (cipher_mode_ == OEMCrypto_CipherMode_CBC) { + memcpy(iv, initial_iv_, KEY_IV_SIZE); + } + + size_t pattern_offset = 0; + const size_t subsample_end = + buffer_index + subsample.num_bytes_encrypted; + while (buffer_index < subsample_end) { + const size_t size = + min(subsample_end - buffer_index, AES_BLOCK_SIZE - block_offset); + const size_t pattern_length = pattern_.encrypt + pattern_.skip; + const bool skip_block = + (pattern_offset >= pattern_.encrypt) && (pattern_length > 0); + if (pattern_length > 0) { + pattern_offset = (pattern_offset + 1) % pattern_length; + } + // CBC mode should just copy a partial block at the end. If there + // is a partial block at the beginning, an error is returned, so we + // can put whatever we want in the output buffer. + if (skip_block || ((cipher_mode_ == OEMCrypto_CipherMode_CBC) && + (size < AES_BLOCK_SIZE))) { + memcpy(&sample.encrypted_buffer[buffer_index], + &sample.truth_buffer[buffer_index], size); + block_offset = 0; // Next block should be complete. + } else { + if (cipher_mode_ == OEMCrypto_CipherMode_CTR) { + uint8_t aes_output[AES_BLOCK_SIZE]; + AES_encrypt(iv, aes_output, &aes_key); + for (size_t n = 0; n < size; n++) { + sample.encrypted_buffer[buffer_index + n] = + aes_output[n + block_offset] ^ + sample.truth_buffer[buffer_index + n]; + } + if (size + block_offset < AES_BLOCK_SIZE) { + // Partial block. Don't increment iv. Compute next block + // offset. + block_offset = block_offset + size; + } else { + EXPECT_EQ(block_offset + size, + static_cast(AES_BLOCK_SIZE)); + // Full block. Increment iv, and set offset to 0 for next + // block. + ctr128_inc64(1, iv); + block_offset = 0; + } + } else { + uint8_t aes_input[AES_BLOCK_SIZE]; + for (size_t n = 0; n < size; n++) { + aes_input[n] = sample.truth_buffer[buffer_index + n] ^ iv[n]; + } + AES_encrypt(aes_input, &sample.encrypted_buffer[buffer_index], + &aes_key); + memcpy(iv, &sample.encrypted_buffer[buffer_index], + AES_BLOCK_SIZE); + // CBC mode should always start on block boundary. + block_offset = 0; + } + } + buffer_index += size; + } // encryption loop + } // per-subsample loop + } // per-sample loop + } + + void LoadLicense() { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + uint32_t control = wvoec::kControlNonceEnabled; + if (verify_crc_) control |= kControlAllowHashVerification; + if (output_buffer_type_ == OEMCrypto_BufferType_Secure) + control |= kControlObserveDataPath | kControlDataPathSecure; + license_messages_.set_control(control); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + license_messages_.core_response() + .timer_limits.initial_renewal_duration_seconds = kDuration; + memcpy(license_messages_.response_data().keys[0].key_data, key_, + sizeof(key_)); + license_messages_.response_data().keys[0].cipher_mode = cipher_mode_; + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + ASSERT_EQ(OEMCrypto_SelectKey( + session_.session_id(), session_.license().keys[0].key_id, + session_.license().keys[0].key_id_length, cipher_mode_), + OEMCrypto_SUCCESS); + } + + void TestDecryptCENC() { + OEMCryptoResult sts; + + // OEMCrypto only supports providing a decrypt hash for one sample. + if (samples_.size() > 1) verify_crc_ = false; + + // If supported, check the decrypt hashes. + if (verify_crc_) { + const TestSample& sample = samples_[0]; + + uint32_t hash = + wvcrc32(sample.truth_buffer.data(), sample.truth_buffer.size()); + ASSERT_EQ(OEMCrypto_SetDecryptHash( + session_.session_id(), 1, + reinterpret_cast(&hash), sizeof(hash)), + OEMCrypto_SUCCESS); + } + + // Build an array of just the sample descriptions. + std::vector sample_descriptions; + sample_descriptions.reserve(samples_.size()); + for (TestSample& sample : samples_) { + // This must be deferred until this point in case the test modifies the + // buffer before testing decrypt. + sample.description.buffers.input_data = sample.encrypted_buffer.data(); + // Append to the description array. + sample_descriptions.push_back(sample.description); + } + + // Perform decryption using the test data that was previously set up. + sts = DecryptFallbackChain::Decrypt( + session_.session_id(), sample_descriptions.data(), + sample_descriptions.size(), cipher_mode_, &pattern_); + ASSERT_EQ(sts, OEMCrypto_SUCCESS); + + // Validate the decrypted data. + for (TestSample& sample : samples_) { + if (sample.description.buffers.output_descriptor.type == + OEMCrypto_BufferType_Clear) { + const size_t total_size = sample.description.buffers.input_data_length; + // To verify there is no buffer overrun after decrypting, look at the + // padded bytes just after the data buffer that was written. It should + // not have changed from the original 0xaa that we set in MakeBuffer + // function. + if (decrypt_inplace_) { + EXPECT_EQ(std::count(sample.encrypted_buffer.begin() + total_size, + sample.encrypted_buffer.end(), 0xaa), + static_cast(kBufferOverrunPadding)) + << "Buffer overrun."; + sample.encrypted_buffer.resize(total_size); // Remove padding. + // We expect encrypted buffer to have been changed by OEMCrypto. + EXPECT_EQ(sample.encrypted_buffer, sample.truth_buffer); + } else { + EXPECT_EQ(std::count(sample.clear_buffer.begin() + total_size, + sample.clear_buffer.end(), 0xaa), + static_cast(kBufferOverrunPadding)) + << "Buffer overrun."; + sample.clear_buffer.resize(total_size); // Remove padding. + EXPECT_EQ(sample.clear_buffer, sample.truth_buffer); + } + } + } + if (verify_crc_) { + uint32_t frame; + ASSERT_EQ(OEMCrypto_GetHashErrorCode(session_.session_id(), &frame), + OEMCrypto_SUCCESS); + } + } + + // Parameters of test case + OEMCrypto_CENCEncryptPatternDesc pattern_; + OEMCryptoCipherMode cipher_mode_; + bool decrypt_inplace_; // If true, input and output buffers are the same. + OEMCryptoBufferType output_buffer_type_; + + bool verify_crc_; + uint8_t key_[AES_BLOCK_SIZE]; // Encryption Key. + uint8_t initial_iv_[KEY_IV_SIZE]; // Starting IV for every sample. + std::vector samples_; +}; + +TEST_P(OEMCryptoSessionTestsDecryptTests, SingleLargeSubsample) { + // This subsample size is larger than a few encrypt/skip patterns. Most + // test cases use a pattern length of 160, so we'll run through at least two + // full patterns if we have more than 320 -- round up to 400. + ASSERT_NO_FATAL_FAILURE(SetSubsampleSizes({ + {0, 400}, + })); + ASSERT_NO_FATAL_FAILURE(LoadLicense()); + ASSERT_NO_FATAL_FAILURE(MakeBuffers()); + ASSERT_NO_FATAL_FAILURE(EncryptData()); + ASSERT_NO_FATAL_FAILURE(TestDecryptCENC()); +} + +// When the pattern length is 10 blocks, there is a discrepancy between the +// HLS and the CENC standards for samples of size 160*N+16, for N = 1, 2, 3... +// We require the CENC standard for OEMCrypto, and let a layer above us break +// samples into pieces if they wish to use the HLS standard. +TEST_P(OEMCryptoSessionTestsDecryptTests, PatternPlusOneBlock) { + ASSERT_NO_FATAL_FAILURE(SetSubsampleSizes({ + {0, 160 + 16}, + })); + ASSERT_NO_FATAL_FAILURE(LoadLicense()); + ASSERT_NO_FATAL_FAILURE(MakeBuffers()); + ASSERT_NO_FATAL_FAILURE(EncryptData()); + ASSERT_NO_FATAL_FAILURE(TestDecryptCENC()); +} + +// Test that a single block can be decrypted. +TEST_P(OEMCryptoSessionTestsDecryptTests, OneBlock) { + ASSERT_NO_FATAL_FAILURE(SetSubsampleSizes({ + {0, 16}, + })); + ASSERT_NO_FATAL_FAILURE(LoadLicense()); + ASSERT_NO_FATAL_FAILURE(MakeBuffers()); + ASSERT_NO_FATAL_FAILURE(EncryptData()); + ASSERT_NO_FATAL_FAILURE(TestDecryptCENC()); +} + +// This tests the ability to decrypt multiple subsamples with no offset. +// There is no offset within the block, used by CTR mode. +TEST_P(OEMCryptoSessionTestsDecryptTests, NoOffset) { + ASSERT_NO_FATAL_FAILURE(SetSubsampleSizes({ + {25, 160}, + {50, 256}, + {25, 160}, + })); + ASSERT_NO_FATAL_FAILURE(LoadLicense()); + ASSERT_NO_FATAL_FAILURE(MakeBuffers()); + ASSERT_NO_FATAL_FAILURE(EncryptData()); + ASSERT_NO_FATAL_FAILURE(TestDecryptCENC()); +} + +// This tests an offset into the block for the second encrypted subsample. +// This should only work for CTR mode, for CBC mode an error is expected in +// the decrypt step. +// If this test fails for CTR mode, then it is probably handling the +// block_offset incorrectly. +TEST_P(OEMCryptoSessionTestsDecryptTests, EvenOffset) { + ASSERT_NO_FATAL_FAILURE(SetSubsampleSizes({ + {25, 8}, + {25, 32}, + {25, 50}, + })); + ASSERT_NO_FATAL_FAILURE(LoadLicense()); + ASSERT_NO_FATAL_FAILURE(MakeBuffers()); + // CTR Mode is self-inverse -- i.e. We can pick the encrypted data and + // compute the unencrypted data. By picking the encrypted data to be all 0, + // it is easier to re-encrypt the data and debug problems. Similarly, we + // pick an iv = 0. + memset(initial_iv_, 0, KEY_IV_SIZE); + TestSample& sample = samples_[0]; // There is only one sample in this test + sample.truth_buffer.assign(sample.description.buffers.input_data_length, 0); + ASSERT_NO_FATAL_FAILURE(EncryptData()); + if (decrypt_inplace_) { + const size_t total_size = sample.description.buffers.input_data_length; + // In case of decrypt_inplace_, encrypted_buffer contains padded bytes + // which is used for buffer overrun validation. Do not copy the padded + // bytes to truth_buffer. + sample.truth_buffer.assign(sample.encrypted_buffer.begin(), + sample.encrypted_buffer.begin() + total_size); + } else { + sample.truth_buffer = + sample.encrypted_buffer; // truth_buffer_ = encrypted zero buffer. + } + // Run EncryptData to re-encrypt this buffer. For CTR mode, we should get + // back to zeros. + ASSERT_NO_FATAL_FAILURE(EncryptData()); + ASSERT_NO_FATAL_FAILURE(TestDecryptCENC()); +} + +// If the EvenOffset test passes, but this one doesn't, then DecryptCENC might +// be using the wrong definition of block offset. Adding the block offset to +// the block boundary should give you the beginning of the encrypted data. +// This should only work for CTR mode, for CBC mode, the block offset must be +// 0, so an error is expected in the decrypt step. +// Another way to view the block offset is with the formula: +// block_boundary + block_offset = beginning of subsample. +TEST_P(OEMCryptoSessionTestsDecryptTests, OddOffset) { + ASSERT_NO_FATAL_FAILURE(SetSubsampleSizes({ + {10, 50}, + {10, 75}, + {10, 75}, + })); + ASSERT_NO_FATAL_FAILURE(LoadLicense()); + ASSERT_NO_FATAL_FAILURE(MakeBuffers()); + ASSERT_NO_FATAL_FAILURE(EncryptData()); + ASSERT_NO_FATAL_FAILURE(TestDecryptCENC()); +} + +// This tests that the algorithm used to increment the counter for +// AES-CTR mode is correct. There are two possible implementations: +// 1) increment the counter as if it were a 128 bit number, +// 2) increment the low 64 bits as a 64 bit number and leave the high bits +// alone. +// For CENC, the algorithm we should use is the second one. OpenSSL defaults to +// the first. If this test is not passing, you should look at the way you +// increment the counter. Look at the example code in ctr128_inc64 above. +// If you start with an IV of 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE, after you +// increment twice, you should get 0xFFFFFFFFFFFFFFFF0000000000000000. +TEST_P(OEMCryptoSessionTestsDecryptTests, DecryptWithNearWrap) { + memcpy(initial_iv_, wvutil::a2b_hex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE").data(), + KEY_IV_SIZE); + ASSERT_NO_FATAL_FAILURE(SetSubsampleSizes({ + {0, 256}, + })); + ASSERT_NO_FATAL_FAILURE(LoadLicense()); + ASSERT_NO_FATAL_FAILURE(MakeBuffers()); + ASSERT_NO_FATAL_FAILURE(EncryptData()); + ASSERT_NO_FATAL_FAILURE(TestDecryptCENC()); +} + +// This tests the case where an encrypted sample is not an even number of +// blocks. For CTR mode, the partial block is encrypted. For CBC mode the +// partial block should be a copy of the clear data. +TEST_P(OEMCryptoSessionTestsDecryptTests, PartialBlock) { + // Note: for more complete test coverage, we want a sample size that is in + // the encrypted range for some tests, e.g. (3,7), and in the skip range for + // other tests, e.g. (7, 3). 3*16 < 50 and 7*16 > 50. + ASSERT_NO_FATAL_FAILURE(SetSubsampleSizes({ + {0, 50}, + })); + ASSERT_NO_FATAL_FAILURE(LoadLicense()); + ASSERT_NO_FATAL_FAILURE(MakeBuffers()); + ASSERT_NO_FATAL_FAILURE(EncryptData()); + ASSERT_NO_FATAL_FAILURE(TestDecryptCENC()); +} + +// Based on the resource rating, OEMCrypto should be able to handle the maximum +// amount of data that can be passed to it. This is the lesser of: +// +// 1) The maximum total sample size +// 2) The maximum number of subsamples multiplied by the maximum subsample size +TEST_P(OEMCryptoSessionTestsDecryptTests, DecryptMaxSampleAPI16) { + const size_t max_sample_size = GetResourceValue(kMaxSampleSize); + const size_t max_subsample_size = GetResourceValue(kMaxSubsampleSize); + const size_t max_num_subsamples = GetResourceValue(kMaxNumberSubsamples); + + // The +1 on this line ensures that, even in cases where max_sample_size is + // not evenly divisible by max_num_subsamples and thus the division gets + // truncated, (max_num_subsamples * subsample_size) will be greater than + // max_sample_size. + const size_t subsample_size = + std::min(max_sample_size / max_num_subsamples + 1, max_subsample_size); + + size_t bytes_remaining = max_sample_size; + std::vector subsample_sizes; + while (bytes_remaining > 0 && subsample_sizes.size() < max_num_subsamples) { + const size_t this_subsample_size = + std::min(subsample_size, bytes_remaining); + const size_t clear_size = this_subsample_size / 2; + const size_t encrypted_size = this_subsample_size - clear_size; + + subsample_sizes.push_back({clear_size, encrypted_size}); + bytes_remaining -= this_subsample_size; + } + ASSERT_NO_FATAL_FAILURE(SetSubsampleSizes(subsample_sizes)); + ASSERT_NO_FATAL_FAILURE(LoadLicense()); + ASSERT_NO_FATAL_FAILURE(MakeBuffers()); + ASSERT_NO_FATAL_FAILURE(EncryptData()); + ASSERT_NO_FATAL_FAILURE(TestDecryptCENC()); +} + +// Based on the resource rating, OEMCrypto should be able to handle the maximum +// subsample size. +TEST_P(OEMCryptoSessionTestsDecryptTests, DecryptMaxSubsample) { + const size_t max = GetResourceValue(kMaxSubsampleSize); + const size_t half_max = max / 2; + // This test assumes that the maximum sample size is always more than three + // times the maximum subsample size. + ASSERT_NO_FATAL_FAILURE(SetSubsampleSizes({ + {max, 0}, + {0, max}, + {half_max, max - half_max}, + })); + ASSERT_NO_FATAL_FAILURE(LoadLicense()); + ASSERT_NO_FATAL_FAILURE(MakeBuffers()); + ASSERT_NO_FATAL_FAILURE(EncryptData()); + ASSERT_NO_FATAL_FAILURE(TestDecryptCENC()); +} + +// There are probably no frames this small, but we should handle them anyway. +TEST_P(OEMCryptoSessionTestsDecryptTests, DecryptSmallBuffer) { + ASSERT_NO_FATAL_FAILURE(SetSubsampleSizes({ + {5, 5}, + })); + ASSERT_NO_FATAL_FAILURE(LoadLicense()); + ASSERT_NO_FATAL_FAILURE(MakeBuffers()); + ASSERT_NO_FATAL_FAILURE(EncryptData()); + ASSERT_NO_FATAL_FAILURE(TestDecryptCENC()); +} + +// Test the case where there is only a clear subsample and no encrypted +// subsample. +TEST_P(OEMCryptoSessionTestsDecryptTests, DecryptUnencrypted) { + ASSERT_NO_FATAL_FAILURE(SetSubsampleSizes({ + {256, 0}, + })); + ASSERT_NO_FATAL_FAILURE(LoadLicense()); + ASSERT_NO_FATAL_FAILURE(MakeBuffers()); + ASSERT_NO_FATAL_FAILURE(EncryptData()); + ASSERT_NO_FATAL_FAILURE(TestDecryptCENC()); +} + +TEST_P(OEMCryptoSessionTestsDecryptTests, DecryptUnencryptedNoKey) { + // Do not try to compute the CRC because we have not loaded a license. + verify_crc_ = false; + // Single clear subsample + ASSERT_NO_FATAL_FAILURE(SetSubsampleSizes({ + {400, 0}, + })); + ASSERT_NO_FATAL_FAILURE(MakeBuffers()); + // Clear data should be copied even if there is no key selected, and no + // license loaded. + // ASSERT_NO_FATAL_FAILURE(LoadLicense()); + ASSERT_NO_FATAL_FAILURE(EncryptData()); + ASSERT_NO_FATAL_FAILURE(TestDecryptCENC()); +} + +// This tests the ability to decrypt multiple samples at once. +TEST_P(OEMCryptoSessionTestsDecryptTests, MultipleSamples) { + ASSERT_NO_FATAL_FAILURE(SetSampleSizes({ + { + {52, 160}, + {25, 256}, + {25, 320}, + }, + { + {300, 64}, + {50, 160}, + {2, 160}, + {24, 160}, + {128, 256}, + }, + { + {70, 320}, + {160, 160}, + }, + })); + ASSERT_NO_FATAL_FAILURE(LoadLicense()); + ASSERT_NO_FATAL_FAILURE(MakeBuffers()); + ASSERT_NO_FATAL_FAILURE(EncryptData()); + ASSERT_NO_FATAL_FAILURE(TestDecryptCENC()); +} + +// Used to construct a specific pattern. +constexpr OEMCrypto_CENCEncryptPatternDesc MakePattern(size_t encrypt, + size_t skip) { + return {encrypt, skip}; +} + +INSTANTIATE_TEST_CASE_P( + CTRTests, OEMCryptoSessionTestsDecryptTests, + Combine(Values(MakePattern(0, 0)), Values(OEMCrypto_CipherMode_CTR), + ::testing::ValuesIn(global_features.GetOutputTypes()))); + +// Decrypt in place for CBC tests was only required in v13. +INSTANTIATE_TEST_CASE_P( + CBCTestsAPI14, OEMCryptoSessionTestsDecryptTests, + Combine( + Values(MakePattern(3, 7), MakePattern(9, 1), + // HLS edge cases. We should follow the CENC spec, not HLS spec. + MakePattern(1, 9), MakePattern(1, 0), + // AV1 patterns not already covered above. + MakePattern(5, 5), MakePattern(10, 0)), + Values(OEMCrypto_CipherMode_CBC), + ::testing::ValuesIn(global_features.GetOutputTypes()))); + +// A request to decrypt data to a clear buffer when the key control block +// requires a secure data path. +TEST_P(OEMCryptoLicenseTest, DecryptSecureToClear) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + license_messages_.set_control(wvoec::kControlObserveDataPath | + wvoec::kControlDataPathSecure); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + ASSERT_NO_FATAL_FAILURE( + session_.TestDecryptCTR(true, OEMCrypto_ERROR_UNKNOWN_FAILURE)); +} + +// If analog is forbidden, then decrypt to a clear buffer should be forbidden. +TEST_P(OEMCryptoLicenseTest, DecryptNoAnalogToClearAPI13) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + license_messages_.set_control(wvoec::kControlDisableAnalogOutput); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + ASSERT_NO_FATAL_FAILURE( + session_.TestDecryptCTR(true, OEMCrypto_ERROR_ANALOG_OUTPUT)); +} + +// Test that key duration is honored. +TEST_P(OEMCryptoLicenseTest, KeyDuration) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + license_messages_.core_response() + .timer_limits.total_playback_duration_seconds = kDuration; + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + + ASSERT_NO_FATAL_FAILURE(session_.TestDecryptCTR(true, OEMCrypto_SUCCESS)); + wvutil::TestSleep::Sleep(kShortSleep); // Should still be valid key. + ASSERT_NO_FATAL_FAILURE(session_.TestDecryptCTR(false, OEMCrypto_SUCCESS)); + wvutil::TestSleep::Sleep(kLongSleep); // Should be expired key. + ASSERT_NO_FATAL_FAILURE( + session_.TestDecryptCTR(false, OEMCrypto_ERROR_KEY_EXPIRED)); + ASSERT_NO_FATAL_FAILURE(session_.TestSelectExpired(0)); +} + +INSTANTIATE_TEST_CASE_P(TestAll, OEMCryptoLicenseTest, + Range(kCurrentAPI - 1, kCurrentAPI + 1)); + +// +// Certificate Root of Trust Tests +// +class OEMCryptoLoadsCertificate : public OEMCryptoSessionTestKeyboxTest {}; + +// This test verifies that we can create a wrapped RSA key, and then reload it. +TEST_F(OEMCryptoLoadsCertificate, LoadRSASessionKey) { + CreateWrappedRSAKey(); + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.InstallRSASessionTestKey(wrapped_rsa_key_)); +} + +TEST_F(OEMCryptoLoadsCertificate, SignProvisioningRequest) { + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + if (global_features.provisioning_method == OEMCrypto_OEMCertificate) { + s.LoadOEMCert(true); + } else { + EXPECT_EQ(global_features.provisioning_method, OEMCrypto_Keybox); + s.GenerateDerivedKeysFromKeybox(keybox_); + } + s.GenerateNonce(); + ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.SignAndVerifyRequest()); +} + +// This tests a large message size. The size is larger than we required in v15. +TEST_F(OEMCryptoLoadsCertificate, SignLargeProvisioningRequestAPI16) { + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + if (global_features.provisioning_method == OEMCrypto_OEMCertificate) { + s.LoadOEMCert(true); + } else { + EXPECT_EQ(global_features.provisioning_method, OEMCrypto_Keybox); + s.GenerateDerivedKeysFromKeybox(keybox_); + } + s.GenerateNonce(); + ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_); + const size_t max_size = GetResourceValue(kLargeMessageSize); + provisioning_messages.set_message_size(max_size); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.SignAndVerifyRequest()); +} + +// This creates a wrapped RSA key, and then does the sanity check that the +// unencrypted key is not found in the wrapped key. The wrapped key should be +// encrypted. +TEST_F(OEMCryptoLoadsCertificate, CertificateProvision) { + Session s; + ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_); + provisioning_messages.PrepareSession(keybox_); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, provisioning_messages.LoadResponse()); + // We should not be able to find the rsa key in the wrapped key. It should + // be encrypted. + EXPECT_EQ(nullptr, find(provisioning_messages.wrapped_rsa_key(), + provisioning_messages.encoded_rsa_key())); +} + +// Verify that RewrapDeviceRSAKey checks pointers are within the provisioning +// message. +TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange1_API16) { + Session s; + ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_); + provisioning_messages.PrepareSession(keybox_); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.CreateDefaultResponse()); + provisioning_messages.core_response().enc_private_key.offset = + provisioning_messages.encrypted_response_buffer().size() + 1; + ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse()); + ASSERT_NE(OEMCrypto_SUCCESS, provisioning_messages.LoadResponse()); + provisioning_messages.VerifyLoadFailed(); +} + +// Verify that RewrapDeviceRSAKey checks pointers are within the provisioning +// message. +TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange2_API16) { + Session s; + ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_); + provisioning_messages.PrepareSession(keybox_); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.CreateDefaultResponse()); + provisioning_messages.core_response().enc_private_key_iv.offset = + provisioning_messages.encrypted_response_buffer().size() + 1; + ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse()); + ASSERT_NE(OEMCrypto_SUCCESS, provisioning_messages.LoadResponse()); + provisioning_messages.VerifyLoadFailed(); +} + +// Verify that RewrapDeviceRSAKey checks pointers are within the provisioning +// message. +TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange3_API16) { + Session s; + ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_); + provisioning_messages.PrepareSession(keybox_); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.CreateDefaultResponse()); + // If the offset is before the end, but the offset+length is bigger, then + // the message should be rejected. + provisioning_messages.core_response().enc_private_key.offset = + provisioning_messages.encrypted_response_buffer().size() - 5; + ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse()); + ASSERT_NE(OEMCrypto_SUCCESS, provisioning_messages.LoadResponse()); + provisioning_messages.VerifyLoadFailed(); +} + +// Verify that RewrapDeviceRSAKey checks pointers are within the provisioning +// message. +TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange4_API16) { + Session s; + ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_); + provisioning_messages.PrepareSession(keybox_); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.CreateDefaultResponse()); + // If the offset is before the end, but the offset+length is bigger, then + // the message should be rejected. + provisioning_messages.core_response().enc_private_key_iv.offset = + provisioning_messages.encrypted_response_buffer().size() - 5; + ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse()); + ASSERT_NE(OEMCrypto_SUCCESS, provisioning_messages.LoadResponse()); + provisioning_messages.VerifyLoadFailed(); +} + +// Verify that RewrapDeviceRSAKey checks pointers are within the provisioning +// message. +TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange5Prov30_API16) { + Session s; + ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_); + provisioning_messages.PrepareSession(keybox_); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.CreateDefaultResponse()); + // If the offset is before the end, but the offset+length is bigger, then + // the message should be rejected. + provisioning_messages.core_response().encrypted_message_key.offset = + provisioning_messages.encrypted_response_buffer().size() + 1; + ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse()); + ASSERT_NE(OEMCrypto_SUCCESS, provisioning_messages.LoadResponse()); + provisioning_messages.VerifyLoadFailed(); +} + +// Test that RewrapDeviceRSAKey verifies the message signature. +// TODO(b/144186970): This test should also run on Prov 3.0 devices. +TEST_F(OEMCryptoLoadsCertificate, + CertificateProvisionBadSignatureKeyboxTestAPI16) { + Session s; + ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_); + provisioning_messages.PrepareSession(keybox_); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse()); + provisioning_messages.response_signature()[4] ^= 42; // bad signature. + ASSERT_EQ(OEMCrypto_ERROR_SIGNATURE_FAILURE, + provisioning_messages.LoadResponse()); + provisioning_messages.VerifyLoadFailed(); +} + +// Test that RewrapDeviceRSAKey verifies the nonce is current. +TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadNonce_API16) { + Session s; + ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_); + provisioning_messages.PrepareSession(keybox_); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.SignAndVerifyRequest()); + provisioning_messages.core_request().nonce ^= 42; // bad nonce. + ASSERT_NO_FATAL_FAILURE(provisioning_messages.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_ERROR_INVALID_NONCE, + provisioning_messages.LoadResponse()); + provisioning_messages.VerifyLoadFailed(); +} + +// Test that RewrapDeviceRSAKey verifies the RSA key is valid. +TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRSAKey) { + Session s; + ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_); + provisioning_messages.PrepareSession(keybox_); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.CreateDefaultResponse()); + provisioning_messages.response_data().rsa_key[4] ^= 42; // bad key. + ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse()); + ASSERT_NE(OEMCrypto_SUCCESS, provisioning_messages.LoadResponse()); + provisioning_messages.VerifyLoadFailed(); +} + +// Test that RewrapDeviceRSAKey verifies the RSA key is valid. +// TODO(b/144186970): This test should also run on Prov 3.0 devices. +TEST_F(OEMCryptoLoadsCertificate, + CertificateProvisionBadRSAKeyKeyboxTestAPI16) { + Session s; + ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_); + provisioning_messages.PrepareSession(keybox_); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse()); + size_t rsa_offset = + provisioning_messages.core_response().enc_private_key.offset; + // Offsets are relative to the message body, after the core message. + rsa_offset += provisioning_messages.serialized_core_message().size(); + rsa_offset += 4; // Change the middle of the key. + provisioning_messages.encrypted_response_buffer()[rsa_offset] ^= 42; + ASSERT_EQ(OEMCrypto_ERROR_SIGNATURE_FAILURE, + provisioning_messages.LoadResponse()); + provisioning_messages.VerifyLoadFailed(); +} + +// Test that RewrapDeviceRSAKey accepts the maximum message size. +TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionLargeBuffer) { + Session s; + ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_); + const size_t max_size = GetResourceValue(kLargeMessageSize); + provisioning_messages.set_message_size(max_size); + provisioning_messages.PrepareSession(keybox_); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, provisioning_messages.LoadResponse()); + // We should not be able to find the rsa key in the wrapped key. It should + // be encrypted. + EXPECT_EQ(nullptr, find(provisioning_messages.wrapped_rsa_key(), + provisioning_messages.encoded_rsa_key())); +} + +// Test that a wrapped RSA key can be loaded. +TEST_F(OEMCryptoLoadsCertificate, LoadWrappedRSAKey) { + OEMCryptoResult sts; + CreateWrappedRSAKey(); + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + sts = OEMCrypto_LoadDRMPrivateKey(s.session_id(), OEMCrypto_RSA_Private_Key, + wrapped_rsa_key_.data(), + wrapped_rsa_key_.size()); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); +} + +// Test a 3072 bit RSA key certificate. +TEST_F(OEMCryptoLoadsCertificate, TestLargeRSAKey3072) { + encoded_rsa_key_.assign(kTestRSAPKCS8PrivateKeyInfo3_3072, + kTestRSAPKCS8PrivateKeyInfo3_3072 + + sizeof(kTestRSAPKCS8PrivateKeyInfo3_3072)); + CreateWrappedRSAKey(); + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE( + s.PreparePublicKey(encoded_rsa_key_.data(), encoded_rsa_key_.size())); + ASSERT_NO_FATAL_FAILURE(s.InstallRSASessionTestKey(wrapped_rsa_key_)); + + LicenseRoundTrip license_messages(&s); + ASSERT_NO_FATAL_FAILURE(s.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages.LoadResponse()); + ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR()); +} + +// Test an RSA key certificate which has a private key generated using the +// Carmichael totient. +TEST_F(OEMCryptoLoadsCertificate, TestCarmichaelRSAKey) { + encoded_rsa_key_.assign( + kTestKeyRSACarmichael_2048, + kTestKeyRSACarmichael_2048 + sizeof(kTestKeyRSACarmichael_2048)); + CreateWrappedRSAKey(); + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE( + s.PreparePublicKey(encoded_rsa_key_.data(), encoded_rsa_key_.size())); + ASSERT_NO_FATAL_FAILURE(s.InstallRSASessionTestKey(wrapped_rsa_key_)); + + LicenseRoundTrip license_messages(&s); + ASSERT_NO_FATAL_FAILURE(s.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages.LoadResponse()); + ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR()); +} + +// This tests that two sessions can use different RSA keys simultaneously. +TEST_F(OEMCryptoLoadsCertificate, TestMultipleRSAKeys) { + CreateWrappedRSAKey(); + Session s1; // Session s1 loads the default rsa key, but doesn't use it + // until after s2 uses its key. + ASSERT_NO_FATAL_FAILURE(s1.open()); + ASSERT_NO_FATAL_FAILURE( + s1.PreparePublicKey(encoded_rsa_key_.data(), encoded_rsa_key_.size())); + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_LoadDRMPrivateKey( + s1.session_id(), OEMCrypto_RSA_Private_Key, + wrapped_rsa_key_.data(), wrapped_rsa_key_.size())); + + Session s2; // Session s2 uses a different rsa key. + encoded_rsa_key_.assign(kTestRSAPKCS8PrivateKeyInfo4_2048, + kTestRSAPKCS8PrivateKeyInfo4_2048 + + sizeof(kTestRSAPKCS8PrivateKeyInfo4_2048)); + CreateWrappedRSAKey(); + ASSERT_NO_FATAL_FAILURE(s2.open()); + ASSERT_NO_FATAL_FAILURE( + s2.PreparePublicKey(encoded_rsa_key_.data(), encoded_rsa_key_.size())); + ASSERT_NO_FATAL_FAILURE(s2.InstallRSASessionTestKey(wrapped_rsa_key_)); + LicenseRoundTrip license_messages2(&s2); + ASSERT_NO_FATAL_FAILURE(s2.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages2.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages2.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages2.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages2.LoadResponse()); + ASSERT_NO_FATAL_FAILURE(s2.TestDecryptCTR()); + s2.close(); + + // After s2 has loaded its rsa key, we continue using s1's key. + LicenseRoundTrip license_messages1(&s1); + ASSERT_NO_FATAL_FAILURE(s1.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages1.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages1.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages1.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages1.LoadResponse()); + ASSERT_NO_FATAL_FAILURE(s1.TestDecryptCTR()); +} + +// Devices that load certificates, should at least support RSA 2048 keys. +TEST_F(OEMCryptoLoadsCertificate, SupportsCertificatesAPI13) { + ASSERT_NE(0u, + OEMCrypto_Supports_RSA_2048bit & OEMCrypto_SupportedCertificates()) + << "Supported certificates is only " << OEMCrypto_SupportedCertificates(); +} + +// These tests are run by all L1 devices that load and use certificates. It is +// also run by a few L3 devices that use a baked in certificate, but cannot load +// a certificate. +class OEMCryptoUsesCertificate : public OEMCryptoLoadsCertificate { + protected: + void SetUp() override { + OEMCryptoLoadsCertificate::SetUp(); + ASSERT_NO_FATAL_FAILURE(session_.open()); + if (global_features.derive_key_method != + DeviceFeatures::LOAD_TEST_RSA_KEY) { + InstallTestRSAKey(&session_); + } + } + + void TearDown() override { + ASSERT_NO_FATAL_FAILURE(session_.close()); + OEMCryptoLoadsCertificate::TearDown(); + } + + Session session_; +}; + +// This test is not run by default, because it takes a long time and +// is used to measure RSA performance, not test functionality. +TEST_F(OEMCryptoLoadsCertificate, RSAPerformance) { + const std::chrono::milliseconds kTestDuration(5000); + OEMCryptoResult sts; + std::chrono::steady_clock clock; + wvutil::TestSleep::Sleep(kShortSleep); // Make sure we are not nonce limited. + + auto start_time = clock.now(); + int count = 15; + for (int i = 0; i < count; i++) { // Only 20 nonce available. + CreateWrappedRSAKey(); + } + auto delta_time = clock.now() - start_time; + const double provision_time = + delta_time / std::chrono::milliseconds(1) / count; + + Session session; + CreateWrappedRSAKey(); + start_time = clock.now(); + count = 0; + while (clock.now() - start_time < kTestDuration) { + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + sts = OEMCrypto_LoadDRMPrivateKey(s.session_id(), OEMCrypto_RSA_Private_Key, + wrapped_rsa_key_.data(), + wrapped_rsa_key_.size()); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + const size_t size = 50; + vector licenseRequest(size); + GetRandBytes(licenseRequest.data(), licenseRequest.size()); + size_t signature_length = 0; + sts = OEMCrypto_GenerateRSASignature(s.session_id(), licenseRequest.data(), + licenseRequest.size(), nullptr, + &signature_length, kSign_RSASSA_PSS); + ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts); + ASSERT_NE(static_cast(0), signature_length); + uint8_t* signature = new uint8_t[signature_length]; + sts = OEMCrypto_GenerateRSASignature(s.session_id(), licenseRequest.data(), + licenseRequest.size(), signature, + &signature_length, kSign_RSASSA_PSS); + delete[] signature; + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + count++; + } + delta_time = clock.now() - start_time; + const double license_request_time = + delta_time / std::chrono::milliseconds(1) / count; + + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_LoadDRMPrivateKey( + s.session_id(), OEMCrypto_RSA_Private_Key, + wrapped_rsa_key_.data(), wrapped_rsa_key_.size())); + vector session_key; + vector enc_session_key; + ASSERT_NO_FATAL_FAILURE( + s.PreparePublicKey(encoded_rsa_key_.data(), encoded_rsa_key_.size())); + ASSERT_TRUE(s.GenerateRSASessionKey(&session_key, &enc_session_key)); + vector mac_context; + vector enc_context; + s.FillDefaultContext(&mac_context, &enc_context); + + enc_session_key = wvutil::a2b_hex( + "7789c619aa3b9fa3c0a53f57a4abc6" + "02157c8aa57e3c6fb450b0bea22667fb" + "0c3200f9d9d618e397837c720dc2dadf" + "486f33590744b2a4e54ca134ae7dbf74" + "434c2fcf6b525f3e132262f05ea3b3c1" + "198595c0e52b573335b2e8a3debd0d0d" + "d0306f8fcdde4e76476be71342957251" + "e1688c9ca6c1c34ed056d3b989394160" + "cf6937e5ce4d39cc73d11a2e93da21a2" + "fa019d246c852fe960095b32f120c3c2" + "7085f7b64aac344a68d607c0768676ce" + "d4c5b2d057f7601921b453a451e1dea0" + "843ebfef628d9af2784d68e86b730476" + "e136dfe19989de4be30a4e7878efcde5" + "ad2b1254f80c0c5dd3cf111b56572217" + "b9f58fc1dacbf74b59d354a1e62cfa0e" + "bf"); + start_time = clock.now(); + while (clock.now() - start_time < kTestDuration) { + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_DeriveKeysFromSessionKey( + s.session_id(), enc_session_key.data(), + enc_session_key.size(), mac_context.data(), + mac_context.size(), enc_context.data(), enc_context.size())); + count++; + } + delta_time = clock.now() - start_time; + const double derive_keys_time = + delta_time / std::chrono::milliseconds(1) / count; + + const char* level = OEMCrypto_SecurityLevel(); + printf("PERF:head, security, provision (ms), lic req(ms), derive keys(ms)\n"); + printf("PERF:stat, %s, %8.3f, %8.3f, %8.3f\n", level, provision_time, + license_request_time, derive_keys_time); +} + +// Test DeriveKeysFromSessionKey using the maximum size for the HMAC context. +TEST_F(OEMCryptoUsesCertificate, GenerateDerivedKeysLargeBuffer) { + vector session_key; + vector enc_session_key; + ASSERT_NO_FATAL_FAILURE(session_.PreparePublicKey(encoded_rsa_key_.data(), + encoded_rsa_key_.size())); + ASSERT_TRUE(session_.GenerateRSASessionKey(&session_key, &enc_session_key)); + const size_t max_size = GetResourceValue(kLargeMessageSize); + vector mac_context(max_size); + vector enc_context(max_size); + // Stripe the data so the two vectors are not identical, and not all zeroes. + for (size_t i = 0; i < max_size; i++) { + mac_context[i] = i % 0x100; + enc_context[i] = (3 * i) % 0x100; + } + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_DeriveKeysFromSessionKey( + session_.session_id(), enc_session_key.data(), + enc_session_key.size(), mac_context.data(), mac_context.size(), + enc_context.data(), enc_context.size())); +} + +// This test attempts to use alternate algorithms for loaded device certs. +class OEMCryptoLoadsCertificateAlternates : public OEMCryptoLoadsCertificate { + protected: + void DisallowForbiddenPadding(RSA_Padding_Scheme scheme, size_t size) { + OEMCryptoResult sts; + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + sts = OEMCrypto_LoadDRMPrivateKey(s.session_id(), OEMCrypto_RSA_Private_Key, + wrapped_rsa_key_.data(), + wrapped_rsa_key_.size()); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + + // Sign a Message + vector licenseRequest(size); + GetRandBytes(licenseRequest.data(), licenseRequest.size()); + size_t signature_length = 256; + vector signature(signature_length); + sts = OEMCrypto_GenerateRSASignature( + s.session_id(), licenseRequest.data(), licenseRequest.size(), + signature.data(), &signature_length, scheme); + // Allow OEMCrypto to request a full buffer. + if (sts == OEMCrypto_ERROR_SHORT_BUFFER) { + ASSERT_NE(static_cast(0), signature_length); + signature.assign(signature_length, 0); + sts = OEMCrypto_GenerateRSASignature( + s.session_id(), licenseRequest.data(), licenseRequest.size(), + signature.data(), &signature_length, scheme); + } + + EXPECT_NE(OEMCrypto_SUCCESS, sts) + << "Signed with forbidden padding scheme=" << (int)scheme + << ", size=" << (int)size; + vector zero(signature_length, 0); + ASSERT_EQ(zero, signature); // signature should not be computed. + } + + void TestSignature(RSA_Padding_Scheme scheme, size_t size) { + OEMCryptoResult sts; + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + sts = OEMCrypto_LoadDRMPrivateKey(s.session_id(), OEMCrypto_RSA_Private_Key, + wrapped_rsa_key_.data(), + wrapped_rsa_key_.size()); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + + vector licenseRequest(size); + GetRandBytes(licenseRequest.data(), licenseRequest.size()); + size_t signature_length = 0; + sts = OEMCrypto_GenerateRSASignature(s.session_id(), licenseRequest.data(), + licenseRequest.size(), nullptr, + &signature_length, scheme); + ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts); + ASSERT_NE(static_cast(0), signature_length); + + uint8_t* signature = new uint8_t[signature_length]; + sts = OEMCrypto_GenerateRSASignature(s.session_id(), licenseRequest.data(), + licenseRequest.size(), signature, + &signature_length, scheme); + + ASSERT_EQ(OEMCrypto_SUCCESS, sts) + << "Failed to sign with padding scheme=" << (int)scheme + << ", size=" << (int)size; + ASSERT_NO_FATAL_FAILURE( + s.PreparePublicKey(encoded_rsa_key_.data(), encoded_rsa_key_.size())); + ASSERT_NO_FATAL_FAILURE(s.VerifyRSASignature(licenseRequest, signature, + signature_length, scheme)); + delete[] signature; + } + + void DisallowDeriveKeys() { + OEMCryptoResult sts; + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + sts = OEMCrypto_LoadDRMPrivateKey(s.session_id(), OEMCrypto_RSA_Private_Key, + wrapped_rsa_key_.data(), + wrapped_rsa_key_.size()); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + s.GenerateNonce(); + vector session_key; + vector enc_session_key; + ASSERT_NO_FATAL_FAILURE( + s.PreparePublicKey(encoded_rsa_key_.data(), encoded_rsa_key_.size())); + ASSERT_TRUE(s.GenerateRSASessionKey(&session_key, &enc_session_key)); + vector mac_context; + vector enc_context; + s.FillDefaultContext(&mac_context, &enc_context); + ASSERT_NE(OEMCrypto_SUCCESS, + OEMCrypto_DeriveKeysFromSessionKey( + s.session_id(), enc_session_key.data(), + enc_session_key.size(), mac_context.data(), + mac_context.size(), enc_context.data(), enc_context.size())); + } + + // If force is true, we assert that the key loads successfully. + void LoadWithAllowedSchemes(uint32_t schemes, bool force) { + Session s; + ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_); + provisioning_messages.set_allowed_schemes(schemes); + provisioning_messages.PrepareSession(keybox_); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse()); + OEMCryptoResult sts = provisioning_messages.LoadResponse(); + encoded_rsa_key_ = provisioning_messages.encoded_rsa_key(); + wrapped_rsa_key_ = provisioning_messages.wrapped_rsa_key(); + key_loaded_ = (wrapped_rsa_key_.size() > 0); + if (force) { + EXPECT_EQ(OEMCrypto_SUCCESS, sts); + EXPECT_EQ(nullptr, find(wrapped_rsa_key_, encoded_rsa_key_)); + ASSERT_TRUE(key_loaded_); + } + } + + bool key_loaded_; +}; + +// The alternate padding is only required for cast receivers, but all devices +// should forbid the alternate padding for regular certificates. +TEST_F(OEMCryptoLoadsCertificateAlternates, DisallowForbiddenPaddingAPI09) { + LoadWithAllowedSchemes(kSign_RSASSA_PSS, true); // Use default padding scheme + DisallowForbiddenPadding(kSign_PKCS1_Block1, 50); +} + +// The alternate padding is only required for cast receivers, but if a device +// does load an alternate certificate, it should NOT use it for generating +// a license request signature. +TEST_F(OEMCryptoLoadsCertificateAlternates, TestSignaturePKCS1_API16) { + // Try to load an RSA key with alternative padding schemes. This signing + // scheme is used by cast receivers. + LoadWithAllowedSchemes(kSign_PKCS1_Block1, false); + // If the device is a cast receiver, then this scheme is required. + if (global_features.cast_receiver) ASSERT_TRUE(key_loaded_); + // If the key loaded with no error, then we will verify that it is not used + // for forbidden padding schemes. + if (key_loaded_) { + // The other padding scheme should fail. + DisallowForbiddenPadding(kSign_RSASSA_PSS, 83); + DisallowDeriveKeys(); + if (global_features.cast_receiver) { + // A signature with a valid size should succeed. + TestSignature(kSign_PKCS1_Block1, 83); + TestSignature(kSign_PKCS1_Block1, 50); + } + // A signature with padding that is too big should fail. + DisallowForbiddenPadding(kSign_PKCS1_Block1, 84); // too big. + } +} + +// This test verifies RSA signing with the alternate padding scheme used by +// Android cast receivers, PKCS1 Block 1. These tests are not required for +// other devices, and should be filtered out by DeviceFeatures::Initialize for +// those devices. +class OEMCryptoCastReceiverTest : public OEMCryptoLoadsCertificateAlternates { + protected: + vector encode(uint8_t type, const vector& substring) { + vector result; + result.push_back(type); + if (substring.size() < 0x80) { + uint8_t length = substring.size(); + result.push_back(length); + } else if (substring.size() < 0x100) { + result.push_back(0x81); + uint8_t length = substring.size(); + result.push_back(length); + } else { + result.push_back(0x82); + uint16_t length = substring.size(); + result.push_back(length >> 8); + result.push_back(length & 0xFF); + } + result.insert(result.end(), substring.begin(), substring.end()); + return result; + } + vector concat(const vector& a, const vector& b) { + vector result = a; + result.insert(result.end(), b.begin(), b.end()); + return result; + } + + // This encodes the RSA key used in the PKCS#1 signing tests below. + void BuildRSAKey() { + vector field_n = + encode(0x02, wvutil::a2b_hex("00" + "df271fd25f8644496b0c81be4bd50297" + "ef099b002a6fd67727eb449cea566ed6" + "a3981a71312a141cabc9815c1209e320" + "a25b32464e9999f18ca13a9fd3892558" + "f9e0adefdd3650dd23a3f036d60fe398" + "843706a40b0b8462c8bee3bce12f1f28" + "60c2444cdc6a44476a75ff4aa24273cc" + "be3bf80248465f8ff8c3a7f3367dfc0d" + "f5b6509a4f82811cedd81cdaaa73c491" + "da412170d544d4ba96b97f0afc806549" + "8d3a49fd910992a1f0725be24f465cfe" + "7e0eabf678996c50bc5e7524abf73f15" + "e5bef7d518394e3138ce4944506aaaaf" + "3f9b236dcab8fc00f87af596fdc3d9d6" + "c75cd508362fae2cbeddcc4c7450b17b" + "776c079ecca1f256351a43b97dbe2153")); + vector field_e = encode(0x02, wvutil::a2b_hex("010001")); + vector field_d = + encode(0x02, wvutil::a2b_hex("5bd910257830dce17520b03441a51a8c" + "ab94020ac6ecc252c808f3743c95b7c8" + "3b8c8af1a5014346ebc4242cdfb5d718" + "e30a733e71f291e4d473b61bfba6daca" + "ed0a77bd1f0950ae3c91a8f901118825" + "89e1d62765ee671e7baeea309f64d447" + "bbcfa9ea12dce05e9ea8939bc5fe6108" + "581279c982b308794b3448e7f7b95229" + "2df88c80cb40142c4b5cf5f8ddaa0891" + "678d610e582fcb880f0d707caf47d09a" + "84e14ca65841e5a3abc5e9dba94075a9" + "084341f0edad9b68e3b8e082b80b6e6e" + "8a0547b44fb5061b6a9131603a5537dd" + "abd01d8e863d8922e9aa3e4bfaea0b39" + "d79283ad2cbc8a59cce7a6ecf4e4c81e" + "d4c6591c807defd71ab06866bb5e7745")); + vector field_p = + encode(0x02, wvutil::a2b_hex("00" + "f44f5e4246391f482b2f5296e3602eb3" + "4aa136427710f7c0416d403fd69d4b29" + "130cfebef34e885abdb1a8a0a5f0e9b5" + "c33e1fc3bfc285b1ae17e40cc67a1913" + "dd563719815ebaf8514c2a7aa0018e63" + "b6c631dc315a46235716423d11ff5803" + "4e610645703606919f5c7ce2660cd148" + "bd9efc123d9c54b6705590d006cfcf3f")); + vector field_q = + encode(0x02, wvutil::a2b_hex("00" + "e9d49841e0e0a6ad0d517857133e36dc" + "72c1bdd90f9174b52e26570f373640f1" + "c185e7ea8e2ed7f1e4ebb951f70a5802" + "3633b0097aec67c6dcb800fc1a67f9bb" + "0563610f08ebc8746ad129772136eb1d" + "daf46436450d318332a84982fe5d28db" + "e5b3e912407c3e0e03100d87d436ee40" + "9eec1cf85e80aba079b2e6106b97bced")); + vector field_exp1 = + encode(0x02, wvutil::a2b_hex("00" + "ed102acdb26871534d1c414ecad9a4d7" + "32fe95b10eea370da62f05de2c393b1a" + "633303ea741b6b3269c97f704b352702" + "c9ae79922f7be8d10db67f026a8145de" + "41b30c0a42bf923bac5f7504c248604b" + "9faa57ed6b3246c6ba158e36c644f8b9" + "548fcf4f07e054a56f768674054440bc" + "0dcbbc9b528f64a01706e05b0b91106f")); + vector field_exp2 = + encode(0x02, wvutil::a2b_hex("6827924a85e88b55ba00f8219128bd37" + "24c6b7d1dfe5629ef197925fecaff5ed" + "b9cdf3a7befd8ea2e8dd3707138b3ff8" + "7c3c39c57f439e562e2aa805a39d7cd7" + "9966d2ece7845f1dbc16bee99999e4d0" + "bf9eeca45fcda8a8500035fe6b5f03bc" + "2f6d1bfc4d4d0a3723961af0cdce4a01" + "eec82d7f5458ec19e71b90eeef7dff61")); + vector field_invq = + encode(0x02, wvutil::a2b_hex("57b73888d183a99a6307422277551a3d" + "9e18adf06a91e8b55ceffef9077c8496" + "948ecb3b16b78155cb2a3a57c119d379" + "951c010aa635edcf62d84c5a122a8d67" + "ab5fa9e5a4a8772a1e943bafc70ae3a4" + "c1f0f3a4ddffaefd1892c8cb33bb0d0b" + "9590e963a69110fb34db7b906fc4ba28" + "36995aac7e527490ac952a02268a4f18")); + + // Header of rsa key is constant. + encoded_rsa_key_ = wvutil::a2b_hex( + // 0x02 0x01 0x00 == integer, size 1 byte, value = 0 (field=version) + "020100" + // 0x30, sequence, size = d = 13 (field=pkeyalg) AlgorithmIdentifier + "300d" + // 0x06 = object identifier. length = 9 + // (this should be 1.2.840.113549.1.1.1) (field=algorithm) + "0609" + "2a" // 1*0x40 + 2 = 42 = 0x2a. + "8648" // 840 = 0x348, 0x03 *2 + 0x80 + (0x48>>15) = 0x86. + // 0x48 -> 0x48 + "86f70d" // 113549 = 0x1668d -> (110 , 1110111, 0001101) + // -> (0x80+0x06, 0x80+0x77, 0x0d) + "01" // 1 + "01" // 1 + "01" // 1 + "05" // null object. (field=parameter?) + "00" // size of null object + ); + + vector pkey = wvutil::a2b_hex("020100"); // integer, version = 0. + pkey = concat(pkey, field_n); + pkey = concat(pkey, field_e); + pkey = concat(pkey, field_d); + pkey = concat(pkey, field_p); + pkey = concat(pkey, field_q); + pkey = concat(pkey, field_exp1); + pkey = concat(pkey, field_exp2); + pkey = concat(pkey, field_invq); + pkey = encode(0x30, pkey); + pkey = encode(0x04, pkey); + + encoded_rsa_key_ = concat(encoded_rsa_key_, pkey); + encoded_rsa_key_ = encode(0x30, encoded_rsa_key_); // 0x30=sequence + } + + // This is used to test a signature from the file pkcs1v15sign-vectors.txt. + void TestSignature(RSA_Padding_Scheme scheme, const vector& message, + const vector& correct_signature) { + OEMCryptoResult sts; + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + sts = OEMCrypto_LoadDRMPrivateKey(s.session_id(), OEMCrypto_RSA_Private_Key, + wrapped_rsa_key_.data(), + wrapped_rsa_key_.size()); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + + // The application will compute the SHA-1 Hash of the message, so this + // test must do that also. + uint8_t hash[SHA_DIGEST_LENGTH]; + if (!SHA1(message.data(), message.size(), hash)) { + dump_boringssl_error(); + FAIL() << "boringssl error creating SHA1 hash."; + } + + // The application will prepend the digest info to the hash. + // SHA-1 digest info prefix = 0x30 0x21 0x30 ... + vector digest = wvutil::a2b_hex("3021300906052b0e03021a05000414"); + digest.insert(digest.end(), hash, hash + SHA_DIGEST_LENGTH); + + // OEMCrypto will apply the padding, and encrypt to generate the signature. + size_t signature_length = 0; + sts = OEMCrypto_GenerateRSASignature(s.session_id(), digest.data(), + digest.size(), nullptr, + &signature_length, scheme); + ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts); + ASSERT_NE(static_cast(0), signature_length); + + vector signature(signature_length); + sts = OEMCrypto_GenerateRSASignature(s.session_id(), digest.data(), + digest.size(), signature.data(), + &signature_length, scheme); + + ASSERT_EQ(OEMCrypto_SUCCESS, sts) + << "Failed to sign with padding scheme=" << (int)scheme + << ", size=" << (int)message.size(); + ASSERT_NO_FATAL_FAILURE( + s.PreparePublicKey(encoded_rsa_key_.data(), encoded_rsa_key_.size())); + + // Verify that the signature matches the official test vector. + ASSERT_EQ(correct_signature.size(), signature_length); + signature.resize(signature_length); + ASSERT_EQ(correct_signature, signature); + + // Also verify that our verification algorithm agrees. This is not needed + // to test OEMCrypto, but it does verify that this test is valid. + ASSERT_NO_FATAL_FAILURE(s.VerifyRSASignature(digest, signature.data(), + signature_length, scheme)); + ASSERT_NO_FATAL_FAILURE(s.VerifyRSASignature( + digest, correct_signature.data(), correct_signature.size(), scheme)); + } +}; + +// CAST Receivers should report that they support cast certificates. +TEST_F(OEMCryptoCastReceiverTest, SupportsCertificatesAPI13) { + ASSERT_NE(0u, + OEMCrypto_Supports_RSA_CAST & OEMCrypto_SupportedCertificates()); +} + +// # PKCS#1 v1.5 Signature Example 15.1 +TEST_F(OEMCryptoCastReceiverTest, TestSignaturePKCS1_15_1) { + BuildRSAKey(); + LoadWithAllowedSchemes(kSign_PKCS1_Block1, true); + vector message = wvutil::a2b_hex( + "f45d55f35551e975d6a8dc7ea9f48859" + "3940cc75694a278f27e578a163d839b3" + "4040841808cf9c58c9b8728bf5f9ce8e" + "e811ea91714f47bab92d0f6d5a26fcfe" + "ea6cd93b910c0a2c963e64eb1823f102" + "753d41f0335910ad3a977104f1aaf6c3" + "742716a9755d11b8eed690477f445c5d" + "27208b2e284330fa3d301423fa7f2d08" + "6e0ad0b892b9db544e456d3f0dab85d9" + "53c12d340aa873eda727c8a649db7fa6" + "3740e25e9af1533b307e61329993110e" + "95194e039399c3824d24c51f22b26bde" + "1024cd395958a2dfeb4816a6e8adedb5" + "0b1f6b56d0b3060ff0f1c4cb0d0e001d" + "d59d73be12"); + vector signature = wvutil::a2b_hex( + "b75a5466b65d0f300ef53833f2175c8a" + "347a3804fc63451dc902f0b71f908345" + "9ed37a5179a3b723a53f1051642d7737" + "4c4c6c8dbb1ca20525f5c9f32db77695" + "3556da31290e22197482ceb69906c46a" + "758fb0e7409ba801077d2a0a20eae7d1" + "d6d392ab4957e86b76f0652d68b83988" + "a78f26e11172ea609bf849fbbd78ad7e" + "dce21de662a081368c040607cee29db0" + "627227f44963ad171d2293b633a392e3" + "31dca54fe3082752f43f63c161b447a4" + "c65a6875670d5f6600fcc860a1caeb0a" + "88f8fdec4e564398a5c46c87f68ce070" + "01f6213abe0ab5625f87d19025f08d81" + "dac7bd4586bc9382191f6d2880f6227e" + "5df3eed21e7792d249480487f3655261"); + TestSignature(kSign_PKCS1_Block1, message, signature); +} +// # PKCS#1 v1.5 Signature Example 15.2 +TEST_F(OEMCryptoCastReceiverTest, TestSignaturePKCS1_15_2) { + BuildRSAKey(); + LoadWithAllowedSchemes(kSign_PKCS1_Block1, true); + vector message = wvutil::a2b_hex( + "c14b4c6075b2f9aad661def4ecfd3cb9" + "33c623f4e63bf53410d2f016d1ab98e2" + "729eccf8006cd8e08050737d95fdbf29" + "6b66f5b9792a902936c4f7ac69f51453" + "ce4369452dc22d96f037748114662000" + "dd9cd3a5e179f4e0f81fa6a0311ca1ae" + "e6519a0f63cec78d27bb726393fb7f1f" + "88cde7c97f8a66cd66301281dac3f3a4" + "33248c75d6c2dcd708b6a97b0a3f325e" + "0b2964f8a5819e479b"); + vector signature = wvutil::a2b_hex( + "afa7343462bea122cc149fca70abdae7" + "9446677db5373666af7dc313015f4de7" + "86e6e394946fad3cc0e2b02bedba5047" + "fe9e2d7d099705e4a39f28683279cf0a" + "c85c1530412242c0e918953be000e939" + "cf3bf182525e199370fa7907eba69d5d" + "b4631017c0e36df70379b5db8d4c695a" + "979a8e6173224065d7dc15132ef28cd8" + "22795163063b54c651141be86d36e367" + "35bc61f31fca574e5309f3a3bbdf91ef" + "f12b99e9cc1744f1ee9a1bd22c5bad96" + "ad481929251f0343fd36bcf0acde7f11" + "e5ad60977721202796fe061f9ada1fc4" + "c8e00d6022a8357585ffe9fdd59331a2" + "8c4aa3121588fb6cf68396d8ac054659" + "9500c9708500a5972bd54f72cf8db0c8"); + TestSignature(kSign_PKCS1_Block1, message, signature); +} + +// # PKCS#1 v1.5 Signature Example 15.3 +TEST_F(OEMCryptoCastReceiverTest, TestSignaturePKCS1_15_3) { + BuildRSAKey(); + LoadWithAllowedSchemes(kSign_PKCS1_Block1, true); + vector message = wvutil::a2b_hex( + "d02371ad7ee48bbfdb2763de7a843b94" + "08ce5eb5abf847ca3d735986df84e906" + "0bdbcdd3a55ba55dde20d4761e1a21d2" + "25c1a186f4ac4b3019d3adf78fe63346" + "67f56f70c901a0a2700c6f0d56add719" + "592dc88f6d2306c7009f6e7a635b4cb3" + "a502dfe68ddc58d03be10a1170004fe7" + "4dd3e46b82591ff75414f0c4a03e605e" + "20524f2416f12eca589f111b75d639c6" + "1baa80cafd05cf3500244a219ed9ced9" + "f0b10297182b653b526f400f2953ba21" + "4d5bcd47884132872ae90d4d6b1f4215" + "39f9f34662a56dc0e7b4b923b6231e30" + "d2676797817f7c337b5ac824ba93143b" + "3381fa3dce0e6aebd38e67735187b1eb" + "d95c02"); + vector signature = wvutil::a2b_hex( + "3bac63f86e3b70271203106b9c79aabd" + "9f477c56e4ee58a4fce5baf2cab4960f" + "88391c9c23698be75c99aedf9e1abf17" + "05be1dac33140adb48eb31f450bb9efe" + "83b7b90db7f1576d33f40c1cba4b8d6b" + "1d3323564b0f1774114fa7c08e6d1e20" + "dd8fbba9b6ac7ad41e26b4568f4a8aac" + "bfd178a8f8d2c9d5f5b88112935a8bc9" + "ae32cda40b8d20375510735096536818" + "ce2b2db71a9772c9b0dda09ae10152fa" + "11466218d091b53d92543061b7294a55" + "be82ff35d5c32fa233f05aaac7585030" + "7ecf81383c111674397b1a1b9d3bf761" + "2ccbe5bacd2b38f0a98397b24c83658f" + "b6c0b4140ef11970c4630d44344e76ea" + "ed74dcbee811dbf6575941f08a6523b8"); + TestSignature(kSign_PKCS1_Block1, message, signature); +}; + +// # PKCS#1 v1.5 Signature Example 15.4 +TEST_F(OEMCryptoCastReceiverTest, TestSignaturePKCS1_15_4) { + BuildRSAKey(); + LoadWithAllowedSchemes(kSign_PKCS1_Block1, true); + vector message = wvutil::a2b_hex( + "29035584ab7e0226a9ec4b02e8dcf127" + "2dc9a41d73e2820007b0f6e21feccd5b" + "d9dbb9ef88cd6758769ee1f956da7ad1" + "8441de6fab8386dbc693"); + vector signature = wvutil::a2b_hex( + "28d8e3fcd5dddb21ffbd8df1630d7377" + "aa2651e14cad1c0e43ccc52f907f946d" + "66de7254e27a6c190eb022ee89ecf622" + "4b097b71068cd60728a1aed64b80e545" + "7bd3106dd91706c937c9795f2b36367f" + "f153dc2519a8db9bdf2c807430c451de" + "17bbcd0ce782b3e8f1024d90624dea7f" + "1eedc7420b7e7caa6577cef43141a726" + "4206580e44a167df5e41eea0e69a8054" + "54c40eefc13f48e423d7a32d02ed42c0" + "ab03d0a7cf70c5860ac92e03ee005b60" + "ff3503424b98cc894568c7c56a023355" + "1cebe588cf8b0167b7df13adcad82867" + "6810499c704da7ae23414d69e3c0d2db" + "5dcbc2613bc120421f9e3653c5a87672" + "97643c7e0740de016355453d6c95ae72"); + TestSignature(kSign_PKCS1_Block1, message, signature); +} + +// # PKCS#1 v1.5 Signature Example 15.5 +TEST_F(OEMCryptoCastReceiverTest, TestSignaturePKCS1_15_5) { + BuildRSAKey(); + LoadWithAllowedSchemes(kSign_PKCS1_Block1, true); + vector message = wvutil::a2b_hex("bda3a1c79059eae598308d3df609"); + vector signature = wvutil::a2b_hex( + "a156176cb96777c7fb96105dbd913bc4" + "f74054f6807c6008a1a956ea92c1f81c" + "b897dc4b92ef9f4e40668dc7c556901a" + "cb6cf269fe615b0fb72b30a513386923" + "14b0e5878a88c2c7774bd16939b5abd8" + "2b4429d67bd7ac8e5ea7fe924e20a6ec" + "662291f2548d734f6634868b039aa5f9" + "d4d906b2d0cb8585bf428547afc91c6e" + "2052ddcd001c3ef8c8eefc3b6b2a82b6" + "f9c88c56f2e2c3cb0be4b80da95eba37" + "1d8b5f60f92538743ddbb5da2972c71f" + "e7b9f1b790268a0e770fc5eb4d5dd852" + "47d48ae2ec3f26255a3985520206a1f2" + "68e483e9dbb1d5cab190917606de31e7" + "c5182d8f151bf41dfeccaed7cde690b2" + "1647106b490c729d54a8fe2802a6d126"); + TestSignature(kSign_PKCS1_Block1, message, signature); +} + +// # PKCS#1 v1.5 Signature Example 15.6 +TEST_F(OEMCryptoCastReceiverTest, TestSignaturePKCS1_15_6) { + BuildRSAKey(); + LoadWithAllowedSchemes(kSign_PKCS1_Block1, true); + vector message = wvutil::a2b_hex( + "c187915e4e87da81c08ed4356a0cceac" + "1c4fb5c046b45281b387ec28f1abfd56" + "7e546b236b37d01ae71d3b2834365d3d" + "f380b75061b736b0130b070be58ae8a4" + "6d12166361b613dbc47dfaeb4ca74645" + "6c2e888385525cca9dd1c3c7a9ada76d" + "6c"); + vector signature = wvutil::a2b_hex( + "9cab74163608669f7555a333cf196fe3" + "a0e9e5eb1a32d34bb5c85ff689aaab0e" + "3e65668ed3b1153f94eb3d8be379b8ee" + "f007c4a02c7071ce30d8bb341e58c620" + "f73d37b4ecbf48be294f6c9e0ecb5e63" + "fec41f120e5553dfa0ebebbb72640a95" + "37badcb451330229d9f710f62e3ed8ec" + "784e50ee1d9262b42671340011d7d098" + "c6f2557b2131fa9bd0254636597e88ec" + "b35a240ef0fd85957124df8080fee1e1" + "49af939989e86b26c85a5881fae8673d" + "9fd40800dd134eb9bdb6410f420b0aa9" + "7b20efcf2eb0c807faeb83a3ccd9b51d" + "4553e41dfc0df6ca80a1e81dc234bb83" + "89dd195a38b42de4edc49d346478b9f1" + "1f0557205f5b0bd7ffe9c850f396d7c4"); + TestSignature(kSign_PKCS1_Block1, message, signature); +} + +// # PKCS#1 v1.5 Signature Example 15.7 +TEST_F(OEMCryptoCastReceiverTest, TestSignaturePKCS1_15_7) { + BuildRSAKey(); + LoadWithAllowedSchemes(kSign_PKCS1_Block1, true); + vector message = wvutil::a2b_hex( + "abfa2ecb7d29bd5bcb9931ce2bad2f74" + "383e95683cee11022f08e8e7d0b8fa05" + "8bf9eb7eb5f98868b5bb1fb5c31ceda3" + "a64f1a12cdf20fcd0e5a246d7a1773d8" + "dba0e3b277545babe58f2b96e3f4edc1" + "8eabf5cd2a560fca75fe96e07d859def" + "b2564f3a34f16f11e91b3a717b41af53" + "f6605323001aa406c6"); + vector signature = wvutil::a2b_hex( + "c4b437bcf703f352e1faf74eb9622039" + "426b5672caf2a7b381c6c4f0191e7e4a" + "98f0eebcd6f41784c2537ff0f99e7498" + "2c87201bfbc65eae832db71d16dacadb" + "0977e5c504679e40be0f9db06ffd848d" + "d2e5c38a7ec021e7f68c47dfd38cc354" + "493d5339b4595a5bf31e3f8f13816807" + "373df6ad0dc7e731e51ad19eb4754b13" + "4485842fe709d378444d8e36b1724a4f" + "da21cafee653ab80747f7952ee804dea" + "b1039d84139945bbf4be82008753f3c5" + "4c7821a1d241f42179c794ef7042bbf9" + "955656222e45c34369a384697b6ae742" + "e18fa5ca7abad27d9fe71052e3310d0f" + "52c8d12ea33bf053a300f4afc4f098df" + "4e6d886779d64594d369158fdbc1f694"); + TestSignature(kSign_PKCS1_Block1, message, signature); +} + +// # PKCS#1 v1.5 Signature Example 15.8 +TEST_F(OEMCryptoCastReceiverTest, TestSignaturePKCS1_15_8) { + BuildRSAKey(); + LoadWithAllowedSchemes(kSign_PKCS1_Block1, true); + vector message = wvutil::a2b_hex( + "df4044a89a83e9fcbf1262540ae3038b" + "bc90f2b2628bf2a4467ac67722d8546b" + "3a71cb0ea41669d5b4d61859c1b4e47c" + "ecc5933f757ec86db0644e311812d00f" + "b802f03400639c0e364dae5aebc5791b" + "c655762361bc43c53d3c7886768f7968" + "c1c544c6f79f7be820c7e2bd2f9d73e6" + "2ded6d2e937e6a6daef90ee37a1a52a5" + "4f00e31addd64894cf4c02e16099e29f" + "9eb7f1a7bb7f84c47a2b594813be02a1" + "7b7fc43b34c22c91925264126c89f86b" + "b4d87f3ef131296c53a308e0331dac8b" + "af3b63422266ecef2b90781535dbda41" + "cbd0cf22a8cbfb532ec68fc6afb2ac06"); + vector signature = wvutil::a2b_hex( + "1414b38567ae6d973ede4a06842dcc0e" + "0559b19e65a4889bdbabd0fd02806829" + "13bacd5dc2f01b30bb19eb810b7d9ded" + "32b284f147bbe771c930c6052aa73413" + "90a849f81da9cd11e5eccf246dbae95f" + "a95828e9ae0ca3550325326deef9f495" + "30ba441bed4ac29c029c9a2736b1a419" + "0b85084ad150426b46d7f85bd702f48d" + "ac5f71330bc423a766c65cc1dcab20d3" + "d3bba72b63b3ef8244d42f157cb7e3a8" + "ba5c05272c64cc1ad21a13493c3911f6" + "0b4e9f4ecc9900eb056ee59d6fe4b8ff" + "6e8048ccc0f38f2836fd3dfe91bf4a38" + "6e1ecc2c32839f0ca4d1b27a568fa940" + "dd64ad16bd0125d0348e383085f08894" + "861ca18987227d37b42b584a8357cb04"); + TestSignature(kSign_PKCS1_Block1, message, signature); +} + +// # PKCS#1 v1.5 Signature Example 15.9 +TEST_F(OEMCryptoCastReceiverTest, TestSignaturePKCS1_15_9) { + BuildRSAKey(); + LoadWithAllowedSchemes(kSign_PKCS1_Block1, true); + vector message = wvutil::a2b_hex( + "ea941ff06f86c226927fcf0e3b11b087" + "2676170c1bfc33bda8e265c77771f9d0" + "850164a5eecbcc5ce827fbfa07c85214" + "796d8127e8caa81894ea61ceb1449e72" + "fea0a4c943b2da6d9b105fe053b9039a" + "9cc53d420b7539fab2239c6b51d17e69" + "4c957d4b0f0984461879a0759c4401be" + "ecd4c606a0afbd7a076f50a2dfc2807f" + "24f1919baa7746d3a64e268ed3f5f8e6" + "da83a2a5c9152f837cb07812bd5ba7d3" + "a07985de88113c1796e9b466ec299c5a" + "c1059e27f09415"); + vector signature = wvutil::a2b_hex( + "ceeb84ccb4e9099265650721eea0e8ec" + "89ca25bd354d4f64564967be9d4b08b3" + "f1c018539c9d371cf8961f2291fbe0dc" + "2f2f95fea47b639f1e12f4bc381cef0c" + "2b7a7b95c3adf27605b7f63998c3cbad" + "542808c3822e064d4ad14093679e6e01" + "418a6d5c059684cd56e34ed65ab605b8" + "de4fcfa640474a54a8251bbb7326a42d" + "08585cfcfc956769b15b6d7fdf7da84f" + "81976eaa41d692380ff10eaecfe0a579" + "682909b5521fade854d797b8a0345b9a" + "864e0588f6caddbf65f177998e180d1f" + "102443e6dca53a94823caa9c3b35f322" + "583c703af67476159ec7ec93d1769b30" + "0af0e7157dc298c6cd2dee2262f8cddc" + "10f11e01741471bbfd6518a175734575"); + TestSignature(kSign_PKCS1_Block1, message, signature); +} + +// # PKCS#1 v1.5 Signature Example 15.10 +TEST_F(OEMCryptoCastReceiverTest, TestSignaturePKCS1_15_10) { + BuildRSAKey(); + LoadWithAllowedSchemes(kSign_PKCS1_Block1, true); + vector message = wvutil::a2b_hex( + "d8b81645c13cd7ecf5d00ed2c91b9acd" + "46c15568e5303c4a9775ede76b48403d" + "6be56c05b6b1cf77c6e75de096c5cb35" + "51cb6fa964f3c879cf589d28e1da2f9d" + "ec"); + vector signature = wvutil::a2b_hex( + "2745074ca97175d992e2b44791c323c5" + "7167165cdd8da579cdef4686b9bb404b" + "d36a56504eb1fd770f60bfa188a7b24b" + "0c91e881c24e35b04dc4dd4ce38566bc" + "c9ce54f49a175fc9d0b22522d9579047" + "f9ed42eca83f764a10163997947e7d2b" + "52ff08980e7e7c2257937b23f3d279d4" + "cd17d6f495546373d983d536efd7d1b6" + "7181ca2cb50ac616c5c7abfbb9260b91" + "b1a38e47242001ff452f8de10ca6eaea" + "dcaf9edc28956f28a711291fc9a80878" + "b8ba4cfe25b8281cb80bc9cd6d2bd182" + "5246eebe252d9957ef93707352084e6d" + "36d423551bf266a85340fb4a6af37088" + "0aab07153d01f48d086df0bfbec05e7b" + "443b97e71718970e2f4bf62023e95b67"); + TestSignature(kSign_PKCS1_Block1, message, signature); +} + +// # PKCS#1 v1.5 Signature Example 15.11 +TEST_F(OEMCryptoCastReceiverTest, TestSignaturePKCS1_15_11) { + BuildRSAKey(); + LoadWithAllowedSchemes(kSign_PKCS1_Block1, true); + vector message = wvutil::a2b_hex( + "e5739b6c14c92d510d95b826933337ff" + "0d24ef721ac4ef64c2bad264be8b44ef" + "a1516e08a27eb6b611d3301df0062dae" + "fc73a8c0d92e2c521facbc7b26473876" + "7ea6fc97d588a0baf6ce50adf79e600b" + "d29e345fcb1dba71ac5c0289023fe4a8" + "2b46a5407719197d2e958e3531fd54ae" + "f903aabb4355f88318994ed3c3dd62f4" + "20a7"); + vector signature = wvutil::a2b_hex( + "be40a5fb94f113e1b3eff6b6a33986f2" + "02e363f07483b792e68dfa5554df0466" + "cc32150950783b4d968b639a04fd2fb9" + "7f6eb967021f5adccb9fca95acc8f2cd" + "885a380b0a4e82bc760764dbab88c1e6" + "c0255caa94f232199d6f597cc9145b00" + "e3d4ba346b559a8833ad1516ad5163f0" + "16af6a59831c82ea13c8224d84d0765a" + "9d12384da460a8531b4c407e04f4f350" + "709eb9f08f5b220ffb45abf6b75d1579" + "fd3f1eb55fc75b00af8ba3b087827fe9" + "ae9fb4f6c5fa63031fe582852fe2834f" + "9c89bff53e2552216bc7c1d4a3d5dc2b" + "a6955cd9b17d1363e7fee8ed7629753f" + "f3125edd48521ae3b9b03217f4496d0d" + "8ede57acbc5bd4deae74a56f86671de2"); + TestSignature(kSign_PKCS1_Block1, message, signature); +} + +// # PKCS#1 v1.5 Signature Example 15.12 +TEST_F(OEMCryptoCastReceiverTest, TestSignaturePKCS1_15_12) { + BuildRSAKey(); + LoadWithAllowedSchemes(kSign_PKCS1_Block1, true); + vector message = wvutil::a2b_hex( + "7af42835917a88d6b3c6716ba2f5b0d5" + "b20bd4e2e6e574e06af1eef7c81131be" + "22bf8128b9cbc6ec00275ba80294a5d1" + "172d0824a79e8fdd830183e4c00b9678" + "2867b1227fea249aad32ffc5fe007bc5" + "1f21792f728deda8b5708aa99cabab20" + "a4aa783ed86f0f27b5d563f42e07158c" + "ea72d097aa6887ec411dd012912a5e03" + "2bbfa678507144bcc95f39b58be7bfd1" + "759adb9a91fa1d6d8226a8343a8b849d" + "ae76f7b98224d59e28f781f13ece605f" + "84f6c90bae5f8cf378816f4020a7dda1" + "bed90c92a23634d203fac3fcd86d68d3" + "182a7d9ccabe7b0795f5c655e9acc4e3" + "ec185140d10cef053464ab175c83bd83" + "935e3dabaf3462eebe63d15f573d269a"); + vector signature = wvutil::a2b_hex( + "4e78c5902b807914d12fa537ae6871c8" + "6db8021e55d1adb8eb0ccf1b8f36ab7d" + "ad1f682e947a627072f03e627371781d" + "33221d174abe460dbd88560c22f69011" + "6e2fbbe6e964363a3e5283bb5d946ef1" + "c0047eba038c756c40be7923055809b0" + "e9f34a03a58815ebdde767931f018f6f" + "1878f2ef4f47dd374051dd48685ded6e" + "fb3ea8021f44be1d7d149398f98ea9c0" + "8d62888ebb56192d17747b6b8e170954" + "31f125a8a8e9962aa31c285264e08fb2" + "1aac336ce6c38aa375e42bc92ab0ab91" + "038431e1f92c39d2af5ded7e43bc151e" + "6ebea4c3e2583af3437e82c43c5e3b5b" + "07cf0359683d2298e35948ed806c063c" + "606ea178150b1efc15856934c7255cfe"); + TestSignature(kSign_PKCS1_Block1, message, signature); +} + +// # PKCS#1 v1.5 Signature Example 15.13 +TEST_F(OEMCryptoCastReceiverTest, TestSignaturePKCS1_15_13) { + BuildRSAKey(); + LoadWithAllowedSchemes(kSign_PKCS1_Block1, true); + vector message = wvutil::a2b_hex( + "ebaef3f9f23bdfe5fa6b8af4c208c189" + "f2251bf32f5f137b9de4406378686b3f" + "0721f62d24cb8688d6fc41a27cbae21d" + "30e429feacc7111941c277"); + vector signature = wvutil::a2b_hex( + "c48dbef507114f03c95fafbeb4df1bfa" + "88e0184a33cc4f8a9a1035ff7f822a5e" + "38cda18723915ff078244429e0f6081c" + "14fd83331fa65c6ba7bb9a12dbf66223" + "74cd0ca57de3774e2bd7ae823677d061" + "d53ae9c4040d2da7ef7014f3bbdc95a3" + "61a43855c8ce9b97ecabce174d926285" + "142b534a3087f9f4ef74511ec742b0d5" + "685603faf403b5072b985df46adf2d25" + "29a02d40711e2190917052371b79b749" + "b83abf0ae29486c3f2f62477b2bd362b" + "039c013c0c5076ef520dbb405f42cee9" + "5425c373a975e1cdd032c49622c85079" + "b09e88dab2b13969ef7a723973781040" + "459f57d5013638483de2d91cb3c490da" + "81c46de6cd76ea8a0c8f6fe331712d24"); + TestSignature(kSign_PKCS1_Block1, message, signature); +} + +// # PKCS#1 v1.5 Signature Example 15.14 +TEST_F(OEMCryptoCastReceiverTest, TestSignaturePKCS1_15_14) { + BuildRSAKey(); + LoadWithAllowedSchemes(kSign_PKCS1_Block1, true); + vector message = wvutil::a2b_hex( + "c5a2711278761dfcdd4f0c99e6f5619d" + "6c48b5d4c1a80982faa6b4cf1cf7a60f" + "f327abef93c801429efde08640858146" + "1056acc33f3d04f5ada21216cacd5fd1" + "f9ed83203e0e2fe6138e3eae8424e591" + "5a083f3f7ab76052c8be55ae882d6ec1" + "482b1e45c5dae9f41015405327022ec3" + "2f0ea2429763b255043b1958ee3cf6d6" + "3983596eb385844f8528cc9a9865835d" + "c5113c02b80d0fca68aa25e72bcaaeb3" + "cf9d79d84f984fd417"); + vector signature = wvutil::a2b_hex( + "6bd5257aa06611fb4660087cb4bc4a9e" + "449159d31652bd980844daf3b1c7b353" + "f8e56142f7ea9857433b18573b4deede" + "818a93b0290297783f1a2f23cbc72797" + "a672537f01f62484cd4162c3214b9ac6" + "28224c5de01f32bb9b76b27354f2b151" + "d0e8c4213e4615ad0bc71f515e300d6a" + "64c6743411fffde8e5ff190e54923043" + "126ecfc4c4539022668fb675f25c07e2" + "0099ee315b98d6afec4b1a9a93dc3349" + "6a15bd6fde1663a7d49b9f1e639d3866" + "4b37a010b1f35e658682d9cd63e57de0" + "f15e8bdd096558f07ec0caa218a8c06f" + "4788453940287c9d34b6d40a3f09bf77" + "99fe98ae4eb49f3ff41c5040a50cefc9" + "bdf2394b749cf164480df1ab6880273b"); + TestSignature(kSign_PKCS1_Block1, message, signature); +} + +// # PKCS#1 v1.5 Signature Example 15.15 +TEST_F(OEMCryptoCastReceiverTest, TestSignaturePKCS1_15_15) { + BuildRSAKey(); + LoadWithAllowedSchemes(kSign_PKCS1_Block1, true); + vector message = wvutil::a2b_hex( + "9bf8aa253b872ea77a7e23476be26b23" + "29578cf6ac9ea2805b357f6fc3ad130d" + "baeb3d869a13cce7a808bbbbc969857e" + "03945c7bb61df1b5c2589b8e046c2a5d" + "7e4057b1a74f24c711216364288529ec" + "9570f25197213be1f5c2e596f8bf8b2c" + "f3cb38aa56ffe5e31df7395820e94ecf" + "3b1189a965dcf9a9cb4298d3c88b2923" + "c19fc6bc34aacecad4e0931a7c4e5d73" + "dc86dfa798a8476d82463eefaa90a8a9" + "192ab08b23088dd58e1280f7d72e4548" + "396baac112252dd5c5346adb2004a2f7" + "101ccc899cc7fafae8bbe295738896a5" + "b2012285014ef6"); + vector signature = wvutil::a2b_hex( + "27f7f4da9bd610106ef57d32383a448a" + "8a6245c83dc1309c6d770d357ba89e73" + "f2ad0832062eb0fe0ac915575bcd6b8b" + "cadb4e2ba6fa9da73a59175152b2d4fe" + "72b070c9b7379e50000e55e6c269f665" + "8c937972797d3add69f130e34b85bdec" + "9f3a9b392202d6f3e430d09caca82277" + "59ab825f7012d2ff4b5b62c8504dbad8" + "55c05edd5cab5a4cccdc67f01dd6517c" + "7d41c43e2a4957aff19db6f18b17859a" + "f0bc84ab67146ec1a4a60a17d7e05f8b" + "4f9ced6ad10908d8d78f7fc88b76adc8" + "290f87daf2a7be10ae408521395d54ed" + "2556fb7661854a730ce3d82c71a8d493" + "ec49a378ac8a3c74439f7cc555ba13f8" + "59070890ee18ff658fa4d741969d70a5"); + TestSignature(kSign_PKCS1_Block1, message, signature); +} + +// # PKCS#1 v1.5 Signature Example 15.16 +TEST_F(OEMCryptoCastReceiverTest, TestSignaturePKCS1_15_16) { + BuildRSAKey(); + LoadWithAllowedSchemes(kSign_PKCS1_Block1, true); + vector message = wvutil::a2b_hex( + "32474830e2203754c8bf0681dc4f842a" + "fe360930378616c108e833656e5640c8" + "6856885bb05d1eb9438efede679263de" + "07cb39553f6a25e006b0a52311a063ca" + "088266d2564ff6490c46b5609818548f" + "88764dad34a25e3a85d575023f0b9e66" + "5048a03c350579a9d32446c7bb96cc92" + "e065ab94d3c8952e8df68ef0d9fa456b" + "3a06bb80e3bbc4b28e6a94b6d0ff7696" + "a64efe05e735fea025d7bdbc4139f3a3" + "b546075cba7efa947374d3f0ac80a68d" + "765f5df6210bca069a2d88647af7ea04" + "2dac690cb57378ec0777614fb8b65ff4" + "53ca6b7dce6098451a2f8c0da9bfecf1" + "fdf391bbaa4e2a91ca18a1121a7523a2" + "abd42514f489e8"); + vector signature = wvutil::a2b_hex( + "6917437257c22ccb5403290c3dee82d9" + "cf7550b31bd31c51bd57bfd35d452ab4" + "db7c4be6b2e25ac9a59a1d2a7feb627f" + "0afd4976b3003cc9cffd8896505ec382" + "f265104d4cf8c932fa9fe86e00870795" + "9912389da4b2d6b369b36a5e72e29d24" + "c9a98c9d31a3ab44e643e6941266a47a" + "45e3446ce8776abe241a8f5fc6423b24" + "b1ff250dc2c3a8172353561077e850a7" + "69b25f0325dac88965a3b9b472c494e9" + "5f719b4eac332caa7a65c7dfe46d9aa7" + "e6e00f525f303dd63ab7919218901868" + "f9337f8cd26aafe6f33b7fb2c98810af" + "19f7fcb282ba1577912c1d368975fd5d" + "440b86e10c199715fa0b6f4250b53373" + "2d0befe1545150fc47b876de09b00a94"); + TestSignature(kSign_PKCS1_Block1, message, signature); +} + +// # PKCS#1 v1.5 Signature Example 15.17 +TEST_F(OEMCryptoCastReceiverTest, TestSignaturePKCS1_15_17) { + BuildRSAKey(); + LoadWithAllowedSchemes(kSign_PKCS1_Block1, true); + vector message = wvutil::a2b_hex( + "008e59505eafb550aae5e845584cebb0" + "0b6de1733e9f95d42c882a5bbeb5ce1c" + "57e119e7c0d4daca9f1ff7870217f7cf" + "d8a6b373977cac9cab8e71e420"); + vector signature = wvutil::a2b_hex( + "922503b673ee5f3e691e1ca85e9ff417" + "3cf72b05ac2c131da5603593e3bc259c" + "94c1f7d3a06a5b9891bf113fa39e59ff" + "7c1ed6465e908049cb89e4e125cd37d2" + "ffd9227a41b4a0a19c0a44fbbf3de55b" + "ab802087a3bb8d4ff668ee6bbb8ad89e" + "6857a79a9c72781990dfcf92cd519404" + "c950f13d1143c3184f1d250c90e17ac6" + "ce36163b9895627ad6ffec1422441f55" + "e4499dba9be89546ae8bc63cca01dd08" + "463ae7f1fce3d893996938778c1812e6" + "74ad9c309c5acca3fde44e7dd8695993" + "e9c1fa87acda99ece5c8499e468957ad" + "66359bf12a51adbe78d3a213b449bf0b" + "5f8d4d496acf03d3033b7ccd196bc22f" + "68fb7bef4f697c5ea2b35062f48a36dd"); + TestSignature(kSign_PKCS1_Block1, message, signature); +} + +// # PKCS#1 v1.5 Signature Example 15.18 +TEST_F(OEMCryptoCastReceiverTest, TestSignaturePKCS1_15_18) { + BuildRSAKey(); + LoadWithAllowedSchemes(kSign_PKCS1_Block1, true); + vector message = wvutil::a2b_hex( + "6abc54cf8d1dff1f53b17d8160368878" + "a8788cc6d22fa5c2258c88e660b09a89" + "33f9f2c0504ddadc21f6e75e0b833beb" + "555229dee656b9047b92f62e76b8ffcc" + "60dab06b80"); + vector signature = wvutil::a2b_hex( + "0b6daf42f7a862147e417493c2c401ef" + "ae32636ab4cbd44192bbf5f195b50ae0" + "96a475a1614f0a9fa8f7a026cb46c650" + "6e518e33d83e56477a875aca8c7e714c" + "e1bdbd61ef5d535239b33f2bfdd61771" + "bab62776d78171a1423cea8731f82e60" + "766d6454265620b15f5c5a584f55f95b" + "802fe78c574ed5dacfc831f3cf2b0502" + "c0b298f25ccf11f973b31f85e4744219" + "85f3cff702df3946ef0a6605682111b2" + "f55b1f8ab0d2ea3a683c69985ead93ed" + "449ea48f0358ddf70802cb41de2fd83f" + "3c808082d84936948e0c84a131b49278" + "27460527bb5cd24bfab7b48e071b2417" + "1930f99763272f9797bcb76f1d248157" + "5558fcf260b1f0e554ebb3df3cfcb958"); + TestSignature(kSign_PKCS1_Block1, message, signature); +} + +// # PKCS#1 v1.5 Signature Example 15.19 +TEST_F(OEMCryptoCastReceiverTest, TestSignaturePKCS1_15_19) { + BuildRSAKey(); + LoadWithAllowedSchemes(kSign_PKCS1_Block1, true); + vector message = wvutil::a2b_hex( + "af2d78152cf10efe01d274f217b177f6" + "b01b5e749f1567715da324859cd3dd88" + "db848ec79f48dbba7b6f1d33111ef31b" + "64899e7391c2bffd69f49025cf201fc5" + "85dbd1542c1c778a2ce7a7ee108a309f" + "eca26d133a5ffedc4e869dcd7656596a" + "c8427ea3ef6e3fd78fe99d8ddc71d839" + "f6786e0da6e786bd62b3a4f19b891a56" + "157a554ec2a2b39e25a1d7c7d37321c7" + "a1d946cf4fbe758d9276f08563449d67" + "414a2c030f4251cfe2213d04a5410637" + "87"); + vector signature = wvutil::a2b_hex( + "209c61157857387b71e24bf3dd564145" + "50503bec180ff53bdd9bac062a2d4995" + "09bf991281b79527df9136615b7a6d9d" + "b3a103b535e0202a2caca197a7b74e53" + "56f3dd595b49acfd9d30049a98ca88f6" + "25bca1d5f22a392d8a749efb6eed9b78" + "21d3110ac0d244199ecb4aa3d735a83a" + "2e8893c6bf8581383ccaee834635b7fa" + "1faffa45b13d15c1da33af71e89303d6" + "8090ff62ee615fdf5a84d120711da53c" + "2889198ab38317a9734ab27d67924cea" + "74156ff99bef9876bb5c339e93745283" + "e1b34e072226b88045e017e9f05b2a8c" + "416740258e223b2690027491732273f3" + "229d9ef2b1b3807e321018920ad3e53d" + "ae47e6d9395c184b93a374c671faa2ce"); + TestSignature(kSign_PKCS1_Block1, message, signature); +} + +// # PKCS#1 v1.5 Signature Example 15.20 +TEST_F(OEMCryptoCastReceiverTest, TestSignaturePKCS1_15_20) { + BuildRSAKey(); + LoadWithAllowedSchemes(kSign_PKCS1_Block1, true); + vector message = wvutil::a2b_hex( + "40ee992458d6f61486d25676a96dd2cb" + "93a37f04b178482f2b186cf88215270d" + "ba29d786d774b0c5e78c7f6e56a956e7" + "f73950a2b0c0c10a08dbcd67e5b210bb" + "21c58e2767d44f7dd4014e3966143bf7" + "e3d66ff0c09be4c55f93b39994b8518d" + "9c1d76d5b47374dea08f157d57d70634" + "978f3856e0e5b481afbbdb5a3ac48d48" + "4be92c93de229178354c2de526e9c65a" + "31ede1ef68cb6398d7911684fec0babc" + "3a781a66660783506974d0e14825101c" + "3bfaea"); + vector signature = wvutil::a2b_hex( + "927502b824afc42513ca6570de338b8a" + "64c3a85eb828d3193624f27e8b1029c5" + "5c119c9733b18f5849b3500918bcc005" + "51d9a8fdf53a97749fa8dc480d6fe974" + "2a5871f973926528972a1af49e3925b0" + "adf14a842719b4a5a2d89fa9c0b6605d" + "212bed1e6723b93406ad30e86829a5c7" + "19b890b389306dc5506486ee2f36a8df" + "e0a96af678c9cbd6aff397ca200e3edc" + "1e36bd2f08b31d540c0cb282a9559e4a" + "dd4fc9e6492eed0ccbd3a6982e5faa2d" + "dd17be47417c80b4e5452d31f72401a0" + "42325109544d954c01939079d409a5c3" + "78d7512dfc2d2a71efcc3432a765d1c6" + "a52cfce899cd79b15b4fc3723641ef6b" + "d00acc10407e5df58dd1c3c5c559a506"); + TestSignature(kSign_PKCS1_Block1, message, signature); +} + +// This class is for testing the generic crypto functionality. +class OEMCryptoGenericCryptoTest : public OEMCryptoRefreshTest { + protected: + // buffer_size_ must be a multiple of encryption block size, 16. We'll use a + // reasonable number of blocks for most of the tests. + OEMCryptoGenericCryptoTest() : buffer_size_(160) {} + + void SetUp() override { + OEMCryptoRefreshTest::SetUp(); + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE( + license_messages_.CreateResponseWithGenericCryptoKeys()); + InitializeClearBuffer(); + } + + void InitializeClearBuffer() { + clear_buffer_.assign(buffer_size_, 0); + for (size_t i = 0; i < clear_buffer_.size(); i++) { + clear_buffer_[i] = 1 + i % 250; + } + for (size_t i = 0; i < wvoec::KEY_IV_SIZE; i++) { + iv_[i] = i; + } + } + + void ResizeBuffer(size_t new_size) { + buffer_size_ = new_size; + InitializeClearBuffer(); // Re-initialize the clear buffer. + } + + void EncryptAndLoadKeys() { + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + } + + // Encrypt the buffer with the specified key made in + // CreateResponseWithGenericCryptoKeys. + void EncryptBuffer(unsigned int key_index, const vector& in_buffer, + vector* out_buffer) { + EncryptBufferWithKey(session_.license().keys[key_index].key_data, in_buffer, + out_buffer); + } + // Encrypt the buffer with the specified key. + void EncryptBufferWithKey(const uint8_t* key_data, + const vector& in_buffer, + vector* out_buffer) { + AES_KEY aes_key; + ASSERT_EQ(0, AES_set_encrypt_key(key_data, AES_BLOCK_SIZE * 8, &aes_key)); + uint8_t iv_buffer[wvoec::KEY_IV_SIZE]; + memcpy(iv_buffer, iv_, wvoec::KEY_IV_SIZE); + out_buffer->resize(in_buffer.size()); + ASSERT_GT(in_buffer.size(), 0u); + ASSERT_EQ(0u, in_buffer.size() % AES_BLOCK_SIZE); + AES_cbc_encrypt(in_buffer.data(), out_buffer->data(), in_buffer.size(), + &aes_key, iv_buffer, AES_ENCRYPT); + } + + // Sign the buffer with the specified key made in + // CreateResponseWithGenericCryptoKeys. + void SignBuffer(unsigned int key_index, const vector& in_buffer, + vector* signature) { + SignBufferWithKey(session_.license().keys[key_index].key_data, in_buffer, + signature); + } + + // Sign the buffer with the specified key. + void SignBufferWithKey(const uint8_t* key_data, + const vector& in_buffer, + vector* signature) { + unsigned int md_len = SHA256_DIGEST_LENGTH; + signature->resize(SHA256_DIGEST_LENGTH); + HMAC(EVP_sha256(), key_data, wvoec::MAC_KEY_SIZE, in_buffer.data(), + in_buffer.size(), signature->data(), &md_len); + } + + // This asks OEMCrypto to encrypt with the specified key, and expects a + // failure. + void BadEncrypt(unsigned int key_index, OEMCrypto_Algorithm algorithm, + size_t buffer_length) { + OEMCryptoResult sts; + vector expected_encrypted; + EncryptBuffer(key_index, clear_buffer_, &expected_encrypted); + sts = OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, + session_.license().keys[key_index].key_id_length, + OEMCrypto_CipherMode_CTR); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + vector encrypted(buffer_length); + sts = OEMCrypto_Generic_Encrypt(session_.session_id(), clear_buffer_.data(), + buffer_length, iv_, algorithm, + encrypted.data()); + EXPECT_NE(OEMCrypto_SUCCESS, sts); + expected_encrypted.resize(buffer_length); + EXPECT_NE(encrypted, expected_encrypted); + } + + // This asks OEMCrypto to decrypt with the specified key, and expects a + // failure. + void BadDecrypt(unsigned int key_index, OEMCrypto_Algorithm algorithm, + size_t buffer_length) { + OEMCryptoResult sts; + vector encrypted; + EncryptBuffer(key_index, clear_buffer_, &encrypted); + sts = OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, + session_.license().keys[key_index].key_id_length, + OEMCrypto_CipherMode_CTR); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + vector resultant(encrypted.size()); + sts = OEMCrypto_Generic_Decrypt(session_.session_id(), encrypted.data(), + buffer_length, iv_, algorithm, + resultant.data()); + EXPECT_NE(OEMCrypto_SUCCESS, sts); + EXPECT_NE(clear_buffer_, resultant); + } + + // This asks OEMCrypto to sign with the specified key, and expects a + // failure. + void BadSign(unsigned int key_index, OEMCrypto_Algorithm algorithm) { + OEMCryptoResult sts; + vector expected_signature; + SignBuffer(key_index, clear_buffer_, &expected_signature); + + sts = OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, + session_.license().keys[key_index].key_id_length, + OEMCrypto_CipherMode_CTR); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + size_t signature_length = (size_t)SHA256_DIGEST_LENGTH; + vector signature(SHA256_DIGEST_LENGTH); + sts = OEMCrypto_Generic_Sign(session_.session_id(), clear_buffer_.data(), + clear_buffer_.size(), algorithm, + signature.data(), &signature_length); + EXPECT_NE(OEMCrypto_SUCCESS, sts); + EXPECT_NE(signature, expected_signature); + } + + // This asks OEMCrypto to verify a signature with the specified key, and + // expects a failure. + void BadVerify(unsigned int key_index, OEMCrypto_Algorithm algorithm, + size_t signature_size, bool alter_data) { + OEMCryptoResult sts; + vector signature; + SignBuffer(key_index, clear_buffer_, &signature); + if (alter_data) { + signature[0] ^= 42; + } + + sts = OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, + session_.license().keys[key_index].key_id_length, + OEMCrypto_CipherMode_CTR); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + sts = OEMCrypto_Generic_Verify(session_.session_id(), clear_buffer_.data(), + clear_buffer_.size(), algorithm, + signature.data(), signature_size); + EXPECT_NE(OEMCrypto_SUCCESS, sts); + } + + // This must be a multiple of encryption block size. + size_t buffer_size_; + vector clear_buffer_; + vector encrypted_buffer_; + uint8_t iv_[wvoec::KEY_IV_SIZE]; +}; + +TEST_P(OEMCryptoGenericCryptoTest, GenericKeyLoad) { EncryptAndLoadKeys(); } + +// Test that the Generic_Encrypt function works correctly. +TEST_P(OEMCryptoGenericCryptoTest, GenericKeyEncrypt) { + EncryptAndLoadKeys(); + unsigned int key_index = 0; + vector expected_encrypted; + EncryptBuffer(key_index, clear_buffer_, &expected_encrypted); + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, + session_.license().keys[key_index].key_id_length, + OEMCrypto_CipherMode_CTR)); + vector encrypted(clear_buffer_.size()); + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_Generic_Encrypt( + session_.session_id(), clear_buffer_.data(), clear_buffer_.size(), + iv_, OEMCrypto_AES_CBC_128_NO_PADDING, encrypted.data())); + ASSERT_EQ(expected_encrypted, encrypted); +} + +// Test that the Generic_Encrypt function fails when not allowed. +TEST_P(OEMCryptoGenericCryptoTest, GenericKeyBadEncrypt) { + EncryptAndLoadKeys(); + BadEncrypt(0, OEMCrypto_HMAC_SHA256, buffer_size_); + // The buffer size must be a multiple of 16, so subtracting 10 is bad. + BadEncrypt(0, OEMCrypto_AES_CBC_128_NO_PADDING, buffer_size_ - 10); + BadEncrypt(1, OEMCrypto_AES_CBC_128_NO_PADDING, buffer_size_); + BadEncrypt(2, OEMCrypto_AES_CBC_128_NO_PADDING, buffer_size_); + BadEncrypt(3, OEMCrypto_AES_CBC_128_NO_PADDING, buffer_size_); +} + +// Test that the Generic_Encrypt works if the input and output buffers are the +// same. +TEST_P(OEMCryptoGenericCryptoTest, GenericKeyEncryptSameBufferAPI12) { + EncryptAndLoadKeys(); + unsigned int key_index = 0; + vector expected_encrypted; + EncryptBuffer(key_index, clear_buffer_, &expected_encrypted); + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, + session_.license().keys[key_index].key_id_length, + OEMCrypto_CipherMode_CTR)); + // Input and output are same buffer: + vector buffer = clear_buffer_; + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_Generic_Encrypt( + session_.session_id(), buffer.data(), buffer.size(), iv_, + OEMCrypto_AES_CBC_128_NO_PADDING, buffer.data())); + ASSERT_EQ(expected_encrypted, buffer); +} + +// Test Generic_Decrypt works correctly. +TEST_P(OEMCryptoGenericCryptoTest, GenericKeyDecrypt) { + EncryptAndLoadKeys(); + unsigned int key_index = 1; + vector encrypted; + EncryptBuffer(key_index, clear_buffer_, &encrypted); + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, + session_.license().keys[key_index].key_id_length, + OEMCrypto_CipherMode_CTR)); + vector resultant(encrypted.size()); + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_Generic_Decrypt( + session_.session_id(), encrypted.data(), encrypted.size(), iv_, + OEMCrypto_AES_CBC_128_NO_PADDING, resultant.data())); + ASSERT_EQ(clear_buffer_, resultant); +} + +// Test that Generic_Decrypt works correctly when the input and output buffers +// are the same. +TEST_P(OEMCryptoGenericCryptoTest, GenericKeyDecryptSameBufferAPI12) { + EncryptAndLoadKeys(); + unsigned int key_index = 1; + vector encrypted; + EncryptBuffer(key_index, clear_buffer_, &encrypted); + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, + session_.license().keys[key_index].key_id_length, + OEMCrypto_CipherMode_CTR)); + vector buffer = encrypted; + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_Generic_Decrypt( + session_.session_id(), buffer.data(), buffer.size(), iv_, + OEMCrypto_AES_CBC_128_NO_PADDING, buffer.data())); + ASSERT_EQ(clear_buffer_, buffer); +} + +// Test that Generic_Decrypt fails to decrypt to an insecure buffer if the key +// requires a secure data path. +TEST_P(OEMCryptoGenericCryptoTest, GenericSecureToClear) { + license_messages_.set_control(wvoec::kControlObserveDataPath | + wvoec::kControlDataPathSecure); + license_messages_.CreateResponseWithGenericCryptoKeys(); + EncryptAndLoadKeys(); + unsigned int key_index = 1; + vector encrypted; + EncryptBuffer(key_index, clear_buffer_, &encrypted); + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, + session_.license().keys[key_index].key_id_length, + OEMCrypto_CipherMode_CTR)); + vector resultant(encrypted.size()); + ASSERT_NE(OEMCrypto_SUCCESS, + OEMCrypto_Generic_Decrypt( + session_.session_id(), encrypted.data(), encrypted.size(), iv_, + OEMCrypto_AES_CBC_128_NO_PADDING, resultant.data())); + ASSERT_NE(clear_buffer_, resultant); +} + +// Test that the Generic_Decrypt function fails when not allowed. +TEST_P(OEMCryptoGenericCryptoTest, GenericKeyBadDecrypt) { + EncryptAndLoadKeys(); + BadDecrypt(1, OEMCrypto_HMAC_SHA256, buffer_size_); + // The buffer size must be a multiple of 16, so subtracting 10 is bad. + BadDecrypt(1, OEMCrypto_AES_CBC_128_NO_PADDING, buffer_size_ - 10); + BadDecrypt(0, OEMCrypto_AES_CBC_128_NO_PADDING, buffer_size_); + BadDecrypt(2, OEMCrypto_AES_CBC_128_NO_PADDING, buffer_size_); + BadDecrypt(3, OEMCrypto_AES_CBC_128_NO_PADDING, buffer_size_); +} + +TEST_P(OEMCryptoGenericCryptoTest, GenericKeySign) { + EncryptAndLoadKeys(); + unsigned int key_index = 2; + vector expected_signature; + SignBuffer(key_index, clear_buffer_, &expected_signature); + + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, + session_.license().keys[key_index].key_id_length, + OEMCrypto_CipherMode_CTR)); + size_t gen_signature_length = 0; + ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, + OEMCrypto_Generic_Sign(session_.session_id(), clear_buffer_.data(), + clear_buffer_.size(), OEMCrypto_HMAC_SHA256, + nullptr, &gen_signature_length)); + ASSERT_EQ(static_cast(SHA256_DIGEST_LENGTH), gen_signature_length); + vector signature(SHA256_DIGEST_LENGTH); + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_Generic_Sign(session_.session_id(), clear_buffer_.data(), + clear_buffer_.size(), OEMCrypto_HMAC_SHA256, + signature.data(), &gen_signature_length)); + ASSERT_EQ(expected_signature, signature); +} + +// Test that the Generic_Sign function fails when not allowed. +TEST_P(OEMCryptoGenericCryptoTest, GenericKeyBadSign) { + EncryptAndLoadKeys(); + BadSign(0, OEMCrypto_HMAC_SHA256); // Can't sign with encrypt key. + BadSign(1, OEMCrypto_HMAC_SHA256); // Can't sign with decrypt key. + BadSign(3, OEMCrypto_HMAC_SHA256); // Can't sign with verify key. + BadSign(2, OEMCrypto_AES_CBC_128_NO_PADDING); // Bad signing algorithm. +} + +TEST_P(OEMCryptoGenericCryptoTest, GenericKeyVerify) { + EncryptAndLoadKeys(); + unsigned int key_index = 3; + vector signature; + SignBuffer(key_index, clear_buffer_, &signature); + + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, + session_.license().keys[key_index].key_id_length, + OEMCrypto_CipherMode_CTR)); + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_Generic_Verify( + session_.session_id(), clear_buffer_.data(), + clear_buffer_.size(), OEMCrypto_HMAC_SHA256, + signature.data(), signature.size())); +} + +// Test that the Generic_Verify function fails when not allowed. +TEST_P(OEMCryptoGenericCryptoTest, GenericKeyBadVerify) { + EncryptAndLoadKeys(); + BadVerify(0, OEMCrypto_HMAC_SHA256, SHA256_DIGEST_LENGTH, false); + BadVerify(1, OEMCrypto_HMAC_SHA256, SHA256_DIGEST_LENGTH, false); + BadVerify(2, OEMCrypto_HMAC_SHA256, SHA256_DIGEST_LENGTH, false); + BadVerify(3, OEMCrypto_HMAC_SHA256, SHA256_DIGEST_LENGTH, true); + BadVerify(3, OEMCrypto_HMAC_SHA256, SHA256_DIGEST_LENGTH - 1, false); + BadVerify(3, OEMCrypto_HMAC_SHA256, SHA256_DIGEST_LENGTH + 1, false); + BadVerify(3, OEMCrypto_AES_CBC_128_NO_PADDING, SHA256_DIGEST_LENGTH, false); +} + +// Test Generic_Encrypt with the maximum buffer size. +TEST_P(OEMCryptoGenericCryptoTest, GenericKeyEncryptLargeBuffer) { + ResizeBuffer(GetResourceValue(kMaxGenericBuffer)); + EncryptAndLoadKeys(); + unsigned int key_index = 0; + vector expected_encrypted; + EncryptBuffer(key_index, clear_buffer_, &expected_encrypted); + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, + session_.license().keys[key_index].key_id_length, + OEMCrypto_CipherMode_CTR)); + vector encrypted(clear_buffer_.size()); + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_Generic_Encrypt( + session_.session_id(), clear_buffer_.data(), clear_buffer_.size(), + iv_, OEMCrypto_AES_CBC_128_NO_PADDING, encrypted.data())); + ASSERT_EQ(expected_encrypted, encrypted); +} + +// Test Generic_Decrypt with the maximum buffer size. +TEST_P(OEMCryptoGenericCryptoTest, GenericKeyDecryptLargeBuffer) { + // Some applications are known to pass in a block that is almost 400k. + ResizeBuffer(GetResourceValue(kMaxGenericBuffer)); + EncryptAndLoadKeys(); + unsigned int key_index = 1; + vector encrypted; + EncryptBuffer(key_index, clear_buffer_, &encrypted); + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, + session_.license().keys[key_index].key_id_length, + OEMCrypto_CipherMode_CTR)); + vector resultant(encrypted.size()); + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_Generic_Decrypt( + session_.session_id(), encrypted.data(), encrypted.size(), iv_, + OEMCrypto_AES_CBC_128_NO_PADDING, resultant.data())); + ASSERT_EQ(clear_buffer_, resultant); +} + +// Test Generic_Sign with the maximum buffer size. +TEST_P(OEMCryptoGenericCryptoTest, GenericKeySignLargeBuffer) { + ResizeBuffer(GetResourceValue(kMaxGenericBuffer)); + EncryptAndLoadKeys(); + unsigned int key_index = 2; + vector expected_signature; + SignBuffer(key_index, clear_buffer_, &expected_signature); + + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, + session_.license().keys[key_index].key_id_length, + OEMCrypto_CipherMode_CTR)); + size_t gen_signature_length = 0; + ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, + OEMCrypto_Generic_Sign(session_.session_id(), clear_buffer_.data(), + clear_buffer_.size(), OEMCrypto_HMAC_SHA256, + nullptr, &gen_signature_length)); + ASSERT_EQ(static_cast(SHA256_DIGEST_LENGTH), gen_signature_length); + vector signature(SHA256_DIGEST_LENGTH); + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_Generic_Sign(session_.session_id(), clear_buffer_.data(), + clear_buffer_.size(), OEMCrypto_HMAC_SHA256, + signature.data(), &gen_signature_length)); + ASSERT_EQ(expected_signature, signature); +} + +// Test Generic_Verify with the maximum buffer size. +TEST_P(OEMCryptoGenericCryptoTest, GenericKeyVerifyLargeBuffer) { + ResizeBuffer(GetResourceValue(kMaxGenericBuffer)); + EncryptAndLoadKeys(); + unsigned int key_index = 3; + vector signature; + SignBuffer(key_index, clear_buffer_, &signature); + + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, + session_.license().keys[key_index].key_id_length, + OEMCrypto_CipherMode_CTR)); + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_Generic_Verify( + session_.session_id(), clear_buffer_.data(), + clear_buffer_.size(), OEMCrypto_HMAC_SHA256, + signature.data(), signature.size())); +} + +// Test Generic_Encrypt when the key duration has expired. +TEST_P(OEMCryptoGenericCryptoTest, KeyDurationEncrypt) { + license_messages_.core_response() + .timer_limits.total_playback_duration_seconds = kDuration; + license_messages_.CreateResponseWithGenericCryptoKeys(); + EncryptAndLoadKeys(); + vector expected_encrypted; + EncryptBuffer(0, clear_buffer_, &expected_encrypted); + unsigned int key_index = 0; + vector encrypted(clear_buffer_.size()); + + // Should be valid key at the start. + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, + session_.license().keys[key_index].key_id_length, + OEMCrypto_CipherMode_CTR)); + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_Generic_Encrypt( + session_.session_id(), clear_buffer_.data(), clear_buffer_.size(), + iv_, OEMCrypto_AES_CBC_128_NO_PADDING, encrypted.data())); + ASSERT_EQ(expected_encrypted, encrypted); + + wvutil::TestSleep::Sleep(kLongSleep + kShortSleep); // Should be expired key. + encrypted.assign(clear_buffer_.size(), 0); + OEMCryptoResult status = OEMCrypto_Generic_Encrypt( + session_.session_id(), clear_buffer_.data(), clear_buffer_.size(), iv_, + OEMCrypto_AES_CBC_128_NO_PADDING, encrypted.data()); + ASSERT_EQ(OEMCrypto_ERROR_KEY_EXPIRED, status); + ASSERT_NE(encrypted, expected_encrypted); + ASSERT_NO_FATAL_FAILURE(session_.TestSelectExpired(key_index)); +} + +// Test Generic_Decrypt when the key duration has expired. +TEST_P(OEMCryptoGenericCryptoTest, KeyDurationDecrypt) { + license_messages_.core_response() + .timer_limits.total_playback_duration_seconds = kDuration; + license_messages_.CreateResponseWithGenericCryptoKeys(); + EncryptAndLoadKeys(); + + // Should be valid key at the start. + unsigned int key_index = 1; + vector encrypted; + EncryptBuffer(key_index, clear_buffer_, &encrypted); + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, + session_.license().keys[key_index].key_id_length, + OEMCrypto_CipherMode_CTR)); + vector resultant(encrypted.size()); + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_Generic_Decrypt( + session_.session_id(), encrypted.data(), encrypted.size(), iv_, + OEMCrypto_AES_CBC_128_NO_PADDING, resultant.data())); + ASSERT_EQ(clear_buffer_, resultant); + + wvutil::TestSleep::Sleep(kLongSleep + kShortSleep); // Should be expired key. + resultant.assign(encrypted.size(), 0); + OEMCryptoResult status = OEMCrypto_Generic_Decrypt( + session_.session_id(), encrypted.data(), encrypted.size(), iv_, + OEMCrypto_AES_CBC_128_NO_PADDING, resultant.data()); + ASSERT_EQ(OEMCrypto_ERROR_KEY_EXPIRED, status); + ASSERT_NE(clear_buffer_, resultant); + ASSERT_NO_FATAL_FAILURE(session_.TestSelectExpired(key_index)); +} + +// Test Generic_Sign when the key duration has expired. +TEST_P(OEMCryptoGenericCryptoTest, KeyDurationSign) { + license_messages_.core_response() + .timer_limits.total_playback_duration_seconds = kDuration; + license_messages_.CreateResponseWithGenericCryptoKeys(); + EncryptAndLoadKeys(); + + unsigned int key_index = 2; + vector expected_signature; + vector signature(SHA256_DIGEST_LENGTH); + size_t signature_length = signature.size(); + SignBuffer(key_index, clear_buffer_, &expected_signature); + + // Should be valid key at the start. + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, + session_.license().keys[key_index].key_id_length, + OEMCrypto_CipherMode_CTR)); + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_Generic_Sign(session_.session_id(), clear_buffer_.data(), + clear_buffer_.size(), OEMCrypto_HMAC_SHA256, + signature.data(), &signature_length)); + ASSERT_EQ(expected_signature, signature); + + wvutil::TestSleep::Sleep(kLongSleep + kShortSleep); // Should be expired key. + signature.assign(SHA256_DIGEST_LENGTH, 0); + OEMCryptoResult status = OEMCrypto_Generic_Sign( + session_.session_id(), clear_buffer_.data(), clear_buffer_.size(), + OEMCrypto_HMAC_SHA256, signature.data(), &signature_length); + ASSERT_EQ(OEMCrypto_ERROR_KEY_EXPIRED, status); + ASSERT_NE(expected_signature, signature); + ASSERT_NO_FATAL_FAILURE(session_.TestSelectExpired(key_index)); +} + +// Test Generic_Verify when the key duration has expired. +TEST_P(OEMCryptoGenericCryptoTest, KeyDurationVerify) { + license_messages_.core_response() + .timer_limits.total_playback_duration_seconds = kDuration; + license_messages_.CreateResponseWithGenericCryptoKeys(); + EncryptAndLoadKeys(); + + unsigned int key_index = 3; + vector signature; + SignBuffer(key_index, clear_buffer_, &signature); + + // Should be valid key at the start. + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_SelectKey(session_.session_id(), + session_.license().keys[key_index].key_id, + session_.license().keys[key_index].key_id_length, + OEMCrypto_CipherMode_CTR)); + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_Generic_Verify( + session_.session_id(), clear_buffer_.data(), + clear_buffer_.size(), OEMCrypto_HMAC_SHA256, + signature.data(), signature.size())); + + wvutil::TestSleep::Sleep(kLongSleep + kShortSleep); // Should be expired key. + OEMCryptoResult status = OEMCrypto_Generic_Verify( + session_.session_id(), clear_buffer_.data(), clear_buffer_.size(), + OEMCrypto_HMAC_SHA256, signature.data(), signature.size()); + ASSERT_EQ(OEMCrypto_ERROR_KEY_EXPIRED, status); + ASSERT_NO_FATAL_FAILURE(session_.TestSelectExpired(key_index)); +} + +const unsigned int kLongKeyId = 2; + +// Test that short key ids are allowed. +class OEMCryptoGenericCryptoKeyIdLengthTest + : public OEMCryptoGenericCryptoTest { + protected: + void SetUp() override { + OEMCryptoGenericCryptoTest::SetUp(); + license_messages_.set_num_keys(5); + license_messages_.set_control(wvoec::kControlAllowDecrypt); + license_messages_.core_response() + .timer_limits.total_playback_duration_seconds = kDuration; + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + SetUniformKeyIdLength(16); // Start with all key ids being 16 bytes. + // But, we are testing that the key ids do not have to have the same length. + // 12 bytes (common key id length). + license_messages_.SetKeyId(0, "123456789012"); + license_messages_.SetKeyId(1, "12345"); // short key id. + // 16 byte key id. (default) + license_messages_.SetKeyId(2, "1234567890123456"); + license_messages_.SetKeyId(3, "12345678901234"); // 14 byte. (uncommon) + license_messages_.SetKeyId(4, "1"); // very short key id. + ASSERT_EQ(2u, kLongKeyId); + ASSERT_NO_FATAL_FAILURE(license_messages_.FillCoreResponseSubstrings()); + } + + // Make all four keys have the same length. + void SetUniformKeyIdLength(size_t key_id_length) { + for (size_t i = 0; i < license_messages_.num_keys(); i++) { + string key_id; + key_id.resize(key_id_length, i + 'a'); + license_messages_.SetKeyId(i, key_id); + } + ASSERT_NO_FATAL_FAILURE(license_messages_.FillCoreResponseSubstrings()); + } + + void TestWithKey(unsigned int key_index) { + ASSERT_LT(key_index, license_messages_.num_keys()); + EncryptAndLoadKeys(); + vector encrypted; + // To make sure OEMCrypto is not expecting the key_id to be zero padded, we + // will create a buffer that is padded with 'Z'. + // Then, we use fill the buffer with the longer of the three keys. If + // OEMCrypto is paying attention to the key id length, it should pick out + // the correct key. + vector key_id_buffer( + session_.license().keys[kLongKeyId].key_id_length + 5, + 'Z'); // Fill a bigger buffer with letter 'Z'. + memcpy(key_id_buffer.data(), session_.license().keys[kLongKeyId].key_id, + session_.license().keys[kLongKeyId].key_id_length); + EncryptBuffer(key_index, clear_buffer_, &encrypted); + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_SelectKey(session_.session_id(), key_id_buffer.data(), + session_.license().keys[key_index].key_id_length, + OEMCrypto_CipherMode_CTR)); + vector resultant(encrypted.size()); + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_Generic_Decrypt( + session_.session_id(), encrypted.data(), encrypted.size(), + iv_, OEMCrypto_AES_CBC_128_NO_PADDING, resultant.data())); + ASSERT_EQ(clear_buffer_, resultant); + } +}; + +TEST_P(OEMCryptoGenericCryptoKeyIdLengthTest, MediumKeyId) { TestWithKey(0); } + +TEST_P(OEMCryptoGenericCryptoKeyIdLengthTest, ShortKeyId) { TestWithKey(1); } + +TEST_P(OEMCryptoGenericCryptoKeyIdLengthTest, LongKeyId) { TestWithKey(2); } + +TEST_P(OEMCryptoGenericCryptoKeyIdLengthTest, FourteenByteKeyId) { + TestWithKey(3); +} + +TEST_P(OEMCryptoGenericCryptoKeyIdLengthTest, VeryShortKeyId) { + TestWithKey(4); +} + +TEST_P(OEMCryptoGenericCryptoKeyIdLengthTest, UniformShortKeyId) { + SetUniformKeyIdLength(5); + TestWithKey(2); +} + +TEST_P(OEMCryptoGenericCryptoKeyIdLengthTest, UniformLongKeyId) { + SetUniformKeyIdLength(kTestKeyIdMaxLength); + TestWithKey(2); +} + +INSTANTIATE_TEST_CASE_P(TestAll, OEMCryptoGenericCryptoTest, + Range(kCurrentAPI - 1, kCurrentAPI + 1)); + +INSTANTIATE_TEST_CASE_P(TestAll, OEMCryptoGenericCryptoKeyIdLengthTest, + Range(kCurrentAPI - 1, kCurrentAPI + 1)); + +// Test usage table functionality. +class LicenseWithUsageEntry { + public: + LicenseWithUsageEntry(const std::string& pst = "my_pst") + : session_(), + license_messages_(&session_), + generic_crypto_(false), + time_license_received_(0), + time_first_decrypt_(0), + time_last_decrypt_(0), + active_(true) { + license_messages_.set_pst(pst); + } + + void MakeAndLoadOnline(OEMCryptoSessionTests* test) { + MakeAndLoad(test, + wvoec::kControlNonceEnabled | wvoec::kControlNonceRequired); + } + + // If status in not a nullptr, then creating a new entry is allowed to fail, + // and its error code is stored in status. + void MakeOfflineAndClose(OEMCryptoSessionTests* test, + OEMCryptoResult* status = nullptr) { + MakeAndLoad(test, wvoec::kControlNonceOrEntry, status); + if (status != nullptr && *status != OEMCrypto_SUCCESS) { + ASSERT_NO_FATAL_FAILURE(session_.close()); + return; + } + ASSERT_NO_FATAL_FAILURE( + session_.UpdateUsageEntry(&(test->encrypted_usage_header_))); + ASSERT_NO_FATAL_FAILURE(GenerateVerifyReport(kUnused)); + ASSERT_NO_FATAL_FAILURE(session_.close()); + } + + // If status in not a nullptr, then creating a new entry is allowed to fail, + // and its error code is stored in status. + void MakeAndLoad(SessionUtil* util, uint32_t control, + OEMCryptoResult* status = nullptr) { + license_messages_.set_control(control); + ASSERT_NO_FATAL_FAILURE(session_.open()); + ASSERT_NO_FATAL_FAILURE(util->InstallTestRSAKey(&session_)); + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + if (generic_crypto_) { + ASSERT_NO_FATAL_FAILURE( + license_messages_.CreateResponseWithGenericCryptoKeys()); + } else { + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + } + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_NO_FATAL_FAILURE(session_.CreateNewUsageEntry(status)); + if (status != nullptr && *status != OEMCrypto_SUCCESS) return; + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + time_license_received_ = wvutil::Clock().GetCurrentTime(); + } + + void OpenAndReload(SessionUtil* util) { + ASSERT_NO_FATAL_FAILURE(session_.open()); + ASSERT_NO_FATAL_FAILURE(session_.ReloadUsageEntry()); + ASSERT_NO_FATAL_FAILURE(util->InstallTestRSAKey(&session_)); + ASSERT_NO_FATAL_FAILURE(session_.GenerateDerivedKeysFromSessionKey()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + } + + // Test decrypt, and update the decrypt times for the pst report. + void TestDecryptCTR(bool select_key_first = true, + OEMCryptoResult expected_result = OEMCrypto_SUCCESS) { + session_.TestDecryptCTR(select_key_first, expected_result); + time_last_decrypt_ = wvutil::Clock().GetCurrentTime(); + if (time_first_decrypt_ == 0) time_first_decrypt_ = time_last_decrypt_; + } + + void DeactivateUsageEntry() { + active_ = false; + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_DeactivateUsageEntry( + session_.session_id(), + reinterpret_cast(pst().c_str()), pst().length())); + } + + void GenerateVerifyReport(OEMCrypto_Usage_Entry_Status status) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateReport(pst())); + Test_PST_Report expected(pst(), status); + ASSERT_NO_FATAL_FAILURE( + session_.VerifyReport(expected, time_license_received_, + time_first_decrypt_, time_last_decrypt_)); + // The PST report was signed above. Below we verify that the entire message + // that is sent to the server will be signed by the right mac keys. + RenewalRoundTrip renewal_messages(&license_messages_); + renewal_messages.set_is_release(!active_); + ASSERT_NO_FATAL_FAILURE(renewal_messages.SignAndVerifyRequest()); + } + + void ReloadUsageEntry() { + session_.ReloadUsageEntry(); + session_.set_mac_keys(license_messages_.response_data().mac_keys); + } + + const std::string& pst() const { return license_messages_.pst(); } + void set_pst(const std::string& pst) { license_messages_.set_pst(pst); } + LicenseRoundTrip& license_messages() { return license_messages_; } + Session& session() { return session_; } + void set_generic_crypto(bool generic_crypto) { + generic_crypto_ = generic_crypto; + } + + private: + Session session_; + LicenseRoundTrip license_messages_; + bool generic_crypto_; + int64_t time_license_received_; + int64_t time_first_decrypt_; + int64_t time_last_decrypt_; + bool active_; +}; + +class OEMCryptoUsageTableTest : public OEMCryptoGenericCryptoTest { + public: + virtual void ShutDown() { + ASSERT_NO_FATAL_FAILURE(session_.close()); + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_Terminate()); + } + + virtual void Restart() { + OEMCrypto_SetSandbox(kTestSandbox, sizeof(kTestSandbox)); + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_Initialize()); + EnsureTestKeys(); + ASSERT_NO_FATAL_FAILURE(session_.open()); + } + + void PrintDotsWhileSleep(int64_t total_seconds, int64_t interval_seconds) { + int64_t dot_time = interval_seconds; + int64_t elapsed_time = 0; + const int64_t start_time = wvutil::Clock().GetCurrentTime(); + do { + wvutil::TestSleep::Sleep(1); + elapsed_time = wvutil::Clock().GetCurrentTime() - start_time; + if (elapsed_time >= dot_time) { + cout << "."; + cout.flush(); + dot_time += interval_seconds; + } + } while (elapsed_time < total_seconds); + cout << endl; + } +}; + +// Test an online or streaming license with PST. This license requires a valid +// nonce and can only be loaded once. +TEST_P(OEMCryptoUsageTableTest, OnlineLicense) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.MakeAndLoadOnline(this); + Session& s = entry.session(); + + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + + // test repeated report generation + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused)); + ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); + // Flag the entry as inactive. + ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + // It should report as inactive. + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed)); + // Decrypt should fail. + ASSERT_NO_FATAL_FAILURE( + entry.TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE)); + // We could call DeactivateUsageEntry multiple times. The state should not + // change. + ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + // It should report as inactive. + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed)); +} + +// Test the usage report when the license is loaded but the keys are never used +// for decryption. +TEST_P(OEMCryptoUsageTableTest, OnlineLicenseUnused) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.MakeAndLoadOnline(this); + Session& s = entry.session(); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + // No decrypt. We do not use this license. + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused)); + // Flag the entry as inactive. + ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + // It should report as inactive. + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUnused)); + // Decrypt should fail. + ASSERT_NO_FATAL_FAILURE( + entry.TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE)); + // We could call DeactivateUsageEntry multiple times. The state should not + // change. + ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + // It should report as inactive. + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUnused)); +} + +// Test that the usage table has been updated and saved before a report can be +// generated. +TEST_P(OEMCryptoUsageTableTest, ForbidReportWithNoUpdate) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.MakeAndLoadOnline(this); + Session& s = entry.session(); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused)); + ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR()); + // Cannot generate a report without first updating the file. + ASSERT_NO_FATAL_FAILURE( + s.GenerateReport(entry.pst(), OEMCrypto_ERROR_ENTRY_NEEDS_UPDATE)); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + // Now it's OK. + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); + // Flag the entry as inactive. + ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry()); + // Cannot generate a report without first updating the file. + ASSERT_NO_FATAL_FAILURE( + s.GenerateReport(entry.pst(), OEMCrypto_ERROR_ENTRY_NEEDS_UPDATE)); + // Decrypt should fail. + ASSERT_NO_FATAL_FAILURE( + entry.TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE)); +} + +// Test an online license with a license renewal. +TEST_P(OEMCryptoUsageTableTest, OnlineLicenseWithRefreshAPI16) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.MakeAndLoadOnline(this); + Session& s = entry.session(); + ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR()); + + RenewalRoundTrip renewal_messages(&entry.license_messages()); + MakeRenewalRequest(&renewal_messages); + LoadRenewal(&renewal_messages, OEMCrypto_SUCCESS); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); +} + +// Verify that a streaming license cannot be reloaded. +TEST_P(OEMCryptoUsageTableTest, RepeatOnlineLicense) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.MakeAndLoadOnline(this); + Session& s = entry.session(); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(s.close()); + Session s2; + ASSERT_NO_FATAL_FAILURE(s2.open()); + ASSERT_NO_FATAL_FAILURE(InstallTestRSAKey(&s2)); + s2.LoadUsageEntry(s); // Use the same entry. + ASSERT_NE(OEMCrypto_SUCCESS, entry.license_messages().LoadResponse(&s2)); +} + +// An offline license should not load on the first call if the nonce is bad. +TEST_P(OEMCryptoUsageTableTest, OnlineBadNonce) { + Session s; + LicenseRoundTrip license_messages(&s); + license_messages.set_api_version(license_api_version_); + license_messages.set_control(wvoec::kControlNonceEnabled | + wvoec::kControlNonceRequired); + license_messages.set_pst("my-pst"); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(InstallTestRSAKey(&s)); + ASSERT_NO_FATAL_FAILURE(s.CreateNewUsageEntry()); + ASSERT_NO_FATAL_FAILURE(s.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages.CreateDefaultResponse()); + for (uint32_t i = 0; i < license_messages.num_keys(); i++) + license_messages.response_data().keys[i].control.nonce ^= 42; + ASSERT_NO_FATAL_FAILURE(license_messages.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_ERROR_INVALID_NONCE, license_messages.LoadResponse()); +} + +// A license with non-zero replay control bits needs a valid pst. +TEST_P(OEMCryptoUsageTableTest, OnlineEmptyPST) { + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(InstallTestRSAKey(&s)); + ASSERT_NO_FATAL_FAILURE(s.GenerateNonce()); + LicenseRoundTrip license_messages(&s); + license_messages.set_api_version(license_api_version_); + license_messages.set_control(wvoec::kControlNonceEnabled | + wvoec::kControlNonceRequired); + // DO NOT SET PST: license_messages.set_pst(pst); + ASSERT_NO_FATAL_FAILURE(license_messages.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages.EncryptAndSignResponse()); + ASSERT_NO_FATAL_FAILURE(s.CreateNewUsageEntry()); + ASSERT_NE(OEMCrypto_SUCCESS, license_messages.LoadResponse()); +} + +// A license with non-zero replay control bits needs a valid pst. +TEST_P(OEMCryptoUsageTableTest, OnlineMissingEntry) { + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(InstallTestRSAKey(&s)); + ASSERT_NO_FATAL_FAILURE(s.GenerateNonce()); + LicenseRoundTrip license_messages(&s); + license_messages.set_api_version(license_api_version_); + license_messages.set_control(wvoec::kControlNonceEnabled | + wvoec::kControlNonceRequired); + license_messages.set_pst("my-pst"); + ASSERT_NO_FATAL_FAILURE(license_messages.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages.EncryptAndSignResponse()); + // ENTRY NOT CREATED: ASSERT_NO_FATAL_FAILURE(s.CreateNewUsageEntry()); + ASSERT_NE(OEMCrypto_SUCCESS, license_messages.LoadResponse()); +} + +// Sessions should have at most one entry at a time. This tests different +// orderings of CreateNewUsageEntry and LoadUsageEntry calls. +TEST_P(OEMCryptoUsageTableTest, CreateAndLoadMultipleEntriesAPI16) { + // Entry Count: we start each test with an empty header. + uint32_t usage_entry_number; + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + Session& s = entry.session(); + // Make first entry 0. + ASSERT_NO_FATAL_FAILURE(entry.MakeOfflineAndClose(this)); + + // Load an entry, then try to create a second. + ASSERT_NO_FATAL_FAILURE(s.open()); + // Reload entry 0. + ASSERT_NO_FATAL_FAILURE(s.ReloadUsageEntry()); + // Create new entry 1 should fail. + ASSERT_EQ(OEMCrypto_ERROR_MULTIPLE_USAGE_ENTRIES, + OEMCrypto_CreateNewUsageEntry(entry.session().session_id(), + &usage_entry_number)); + ASSERT_NO_FATAL_FAILURE(s.close()); + + // Create an entry, then try to load a second. + Session s2; + ASSERT_NO_FATAL_FAILURE(s2.open()); + // Create entry 1. + ASSERT_NO_FATAL_FAILURE(s2.CreateNewUsageEntry()); + // Try to reload entry 0. + ASSERT_EQ(OEMCrypto_ERROR_MULTIPLE_USAGE_ENTRIES, + OEMCrypto_LoadUsageEntry(s2.session_id(), s.usage_entry_number(), + s.encrypted_usage_entry().data(), + s.encrypted_usage_entry().size())); + ASSERT_NO_FATAL_FAILURE(s2.close()); + + // Reload an entry and a license, then try to load the same entry again. + // This reloads entry 0. + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.ReloadUsageEntry()); + ASSERT_EQ(OEMCrypto_ERROR_MULTIPLE_USAGE_ENTRIES, + OEMCrypto_LoadUsageEntry(s.session_id(), s.usage_entry_number(), + s.encrypted_usage_entry().data(), + s.encrypted_usage_entry().size())); + ASSERT_NO_FATAL_FAILURE(s.close()); + + // Create an entry, then try to create a second entry. + ASSERT_NO_FATAL_FAILURE(s2.open()); + ASSERT_NO_FATAL_FAILURE(s2.CreateNewUsageEntry()); + ASSERT_EQ( + OEMCrypto_ERROR_MULTIPLE_USAGE_ENTRIES, + OEMCrypto_CreateNewUsageEntry(s2.session_id(), &usage_entry_number)); +} + +// An entry can be loaded in only one session at a time. +TEST_P(OEMCryptoUsageTableTest, LoadEntryInMultipleSessions) { + // Entry Count: we start each test with an empty header. + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + Session& s = entry.session(); + // Make first entry 0. + ASSERT_NO_FATAL_FAILURE(entry.MakeOfflineAndClose(this)); + const uint32_t usage_entry_number = s.usage_entry_number(); + EXPECT_EQ(usage_entry_number, 0u); // Should be only entry in this test. + + // Load an entry, then try to create a second. + ASSERT_NO_FATAL_FAILURE(s.open()); + // Reload entry 0. + ASSERT_NO_FATAL_FAILURE(s.ReloadUsageEntry()); + + // Create an entry, then try to load a second. + Session s2; + ASSERT_NO_FATAL_FAILURE(s2.open()); + // Try to load entry 0 into session 2. + ASSERT_EQ(OEMCrypto_ERROR_INVALID_SESSION, + OEMCrypto_LoadUsageEntry(s2.session_id(), usage_entry_number, + s.encrypted_usage_entry().data(), + s.encrypted_usage_entry().size())); +} + +// Test generic encrypt when the license uses a PST. +TEST_P(OEMCryptoUsageTableTest, GenericCryptoEncrypt) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.set_generic_crypto(true); + entry.MakeAndLoadOnline(this); + Session& s = entry.session(); + OEMCryptoResult sts; + unsigned int key_index = 0; + vector expected_encrypted; + EncryptBufferWithKey(s.license().keys[key_index].key_data, clear_buffer_, + &expected_encrypted); + sts = OEMCrypto_SelectKey(s.session_id(), s.license().keys[key_index].key_id, + s.license().keys[key_index].key_id_length, + OEMCrypto_CipherMode_CTR); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + vector encrypted(clear_buffer_.size()); + sts = OEMCrypto_Generic_Encrypt( + s.session_id(), clear_buffer_.data(), clear_buffer_.size(), iv_, + OEMCrypto_AES_CBC_128_NO_PADDING, encrypted.data()); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + EXPECT_EQ(expected_encrypted, encrypted); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); + ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed)); + encrypted.assign(clear_buffer_.size(), 0); + sts = OEMCrypto_Generic_Encrypt( + s.session_id(), clear_buffer_.data(), clear_buffer_.size(), iv_, + OEMCrypto_AES_CBC_128_NO_PADDING, encrypted.data()); + ASSERT_NE(OEMCrypto_SUCCESS, sts); + EXPECT_NE(encrypted, expected_encrypted); +} + +// Test generic decrypt when the license uses a PST. +TEST_P(OEMCryptoUsageTableTest, GenericCryptoDecrypt) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.set_generic_crypto(true); + entry.MakeAndLoadOnline(this); + Session& s = entry.session(); + OEMCryptoResult sts; + unsigned int key_index = 1; + vector encrypted; + EncryptBufferWithKey(s.license().keys[key_index].key_data, clear_buffer_, + &encrypted); + sts = OEMCrypto_SelectKey(s.session_id(), s.license().keys[key_index].key_id, + s.license().keys[key_index].key_id_length, + OEMCrypto_CipherMode_CTR); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + vector resultant(encrypted.size()); + sts = OEMCrypto_Generic_Decrypt( + s.session_id(), encrypted.data(), encrypted.size(), iv_, + OEMCrypto_AES_CBC_128_NO_PADDING, resultant.data()); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + EXPECT_EQ(clear_buffer_, resultant); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); + ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed)); + resultant.assign(encrypted.size(), 0); + sts = OEMCrypto_Generic_Decrypt( + s.session_id(), encrypted.data(), encrypted.size(), iv_, + OEMCrypto_AES_CBC_128_NO_PADDING, resultant.data()); + ASSERT_NE(OEMCrypto_SUCCESS, sts); + EXPECT_NE(clear_buffer_, resultant); +} + +// Test generic sign when the license uses a PST. +TEST_P(OEMCryptoUsageTableTest, GenericCryptoSign) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.set_generic_crypto(true); + entry.MakeAndLoadOnline(this); + Session& s = entry.session(); + OEMCryptoResult sts; + unsigned int key_index = 2; + vector expected_signature; + SignBufferWithKey(s.license().keys[key_index].key_data, clear_buffer_, + &expected_signature); + + sts = OEMCrypto_SelectKey(s.session_id(), s.license().keys[key_index].key_id, + s.license().keys[key_index].key_id_length, + OEMCrypto_CipherMode_CTR); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + size_t gen_signature_length = 0; + sts = OEMCrypto_Generic_Sign(s.session_id(), clear_buffer_.data(), + clear_buffer_.size(), OEMCrypto_HMAC_SHA256, + nullptr, &gen_signature_length); + ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts); + ASSERT_EQ(static_cast(SHA256_DIGEST_LENGTH), gen_signature_length); + vector signature(SHA256_DIGEST_LENGTH); + sts = OEMCrypto_Generic_Sign(s.session_id(), clear_buffer_.data(), + clear_buffer_.size(), OEMCrypto_HMAC_SHA256, + signature.data(), &gen_signature_length); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + ASSERT_EQ(expected_signature, signature); + + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); + ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed)); + signature.assign(SHA256_DIGEST_LENGTH, 0); + gen_signature_length = SHA256_DIGEST_LENGTH; + sts = OEMCrypto_Generic_Sign(s.session_id(), clear_buffer_.data(), + clear_buffer_.size(), OEMCrypto_HMAC_SHA256, + signature.data(), &gen_signature_length); + ASSERT_NE(OEMCrypto_SUCCESS, sts); + ASSERT_NE(signature, expected_signature); +} + +// Test generic verify when the license uses a PST. +TEST_P(OEMCryptoUsageTableTest, GenericCryptoVerify) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.set_generic_crypto(true); + entry.MakeAndLoadOnline(this); + Session& s = entry.session(); + OEMCryptoResult sts; + unsigned int key_index = 3; + vector signature; + SignBufferWithKey(s.license().keys[key_index].key_data, clear_buffer_, + &signature); + + sts = OEMCrypto_SelectKey(s.session_id(), s.license().keys[key_index].key_id, + s.license().keys[key_index].key_id_length, + OEMCrypto_CipherMode_CTR); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + sts = OEMCrypto_Generic_Verify(s.session_id(), clear_buffer_.data(), + clear_buffer_.size(), OEMCrypto_HMAC_SHA256, + signature.data(), signature.size()); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); + ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed)); + sts = OEMCrypto_Generic_Verify(s.session_id(), clear_buffer_.data(), + clear_buffer_.size(), OEMCrypto_HMAC_SHA256, + signature.data(), signature.size()); + ASSERT_NE(OEMCrypto_SUCCESS, sts); +} + +// Test that an offline license can be loaded. +TEST_P(OEMCryptoUsageTableTest, OfflineLicense) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + ASSERT_NO_FATAL_FAILURE(entry.MakeOfflineAndClose(this)); +} + +// Test that an offline license can be loaded and that the license can be +// renewed. +TEST_P(OEMCryptoUsageTableTest, OfflineLicenseRefresh) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.MakeAndLoad(this, wvoec::kControlNonceOrEntry); + Session& s = entry.session(); + + ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR()); + // License renewal message is signed by client and verified by the server. + RenewalRoundTrip renewal_messages(&entry.license_messages()); + MakeRenewalRequest(&renewal_messages); + LoadRenewal(&renewal_messages, OEMCrypto_SUCCESS); + ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); +} + +// Test that an offline license can be reloaded in a new session. +TEST_P(OEMCryptoUsageTableTest, ReloadOfflineLicense) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.MakeOfflineAndClose(this); + Session& s = entry.session(); + ASSERT_NO_FATAL_FAILURE(entry.OpenAndReload(this)); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused)); + ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); +} + +// Test that an offline license can be reloaded in a new session, and then +// refreshed. +TEST_P(OEMCryptoUsageTableTest, ReloadOfflineLicenseWithRefresh) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.MakeOfflineAndClose(this); + Session& s = entry.session(); + + ASSERT_NO_FATAL_FAILURE(entry.OpenAndReload(this)); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused)); + ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + RenewalRoundTrip renewal_messages(&entry.license_messages()); + MakeRenewalRequest(&renewal_messages); + LoadRenewal(&renewal_messages, OEMCrypto_SUCCESS); + ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); +} + +// Verify that we can still reload an offline license after OEMCrypto_Terminate +// and Initialize are called. This is as close to a reboot as we can do in a +// unit test. +TEST_P(OEMCryptoUsageTableTest, ReloadOfflineLicenseWithTerminate) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.MakeOfflineAndClose(this); + Session& s = entry.session(); + ShutDown(); // This calls OEMCrypto_Terminate. + Restart(); // This calls OEMCrypto_Initialize. + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_LoadUsageTableHeader(encrypted_usage_header_.data(), + encrypted_usage_header_.size())); + + ASSERT_NO_FATAL_FAILURE(entry.OpenAndReload(this)); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused)); + ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); +} + +// If we attempt to load a second license with the same usage entry as the +// first, but it has different mac keys, then the attempt should fail. This is +// how we verify that we are reloading the same license. +TEST_P(OEMCryptoUsageTableTest, BadReloadOfflineLicense) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.MakeOfflineAndClose(this); + Session& s = entry.session(); + + // Offline license with new mac keys should fail. + Session s2; + LicenseRoundTrip license_messages2(&s2); + // Copy the response, and then change the mac keys. + license_messages2.response_data() = entry.license_messages().response_data(); + license_messages2.core_response() = entry.license_messages().core_response(); + license_messages2.response_data().mac_keys[7] ^= 42; + ASSERT_NO_FATAL_FAILURE(s2.open()); + ASSERT_NO_FATAL_FAILURE(InstallTestRSAKey(&s2)); + // This is a valid license: it is correctly signed. + license_messages2.EncryptAndSignResponse(); + // Load the usage entry should be OK. + ASSERT_NO_FATAL_FAILURE(s2.LoadUsageEntry(s)); + ASSERT_NE(OEMCrypto_SUCCESS, license_messages2.LoadResponse()); + ASSERT_NO_FATAL_FAILURE(s2.close()); + + // Now we go back to the original license response. It should load OK. + ASSERT_NO_FATAL_FAILURE(entry.OpenAndReload(this)); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused)); +} + +// An offline license should not load on the first call if the nonce is bad. +TEST_P(OEMCryptoUsageTableTest, OfflineBadNonce) { + Session s; + LicenseRoundTrip license_messages(&s); + license_messages.set_api_version(license_api_version_); + license_messages.set_control(wvoec::kControlNonceOrEntry); + license_messages.set_pst("my-pst"); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(InstallTestRSAKey(&s)); + ASSERT_NO_FATAL_FAILURE(s.CreateNewUsageEntry()); + ASSERT_NO_FATAL_FAILURE(s.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages.CreateDefaultResponse()); + for (size_t i = 0; i < license_messages.num_keys(); i++) + license_messages.response_data().keys[i].control.nonce ^= 42; + ASSERT_NO_FATAL_FAILURE(license_messages.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_ERROR_INVALID_NONCE, license_messages.LoadResponse()); +} + +// An offline license needs a valid pst. +TEST_P(OEMCryptoUsageTableTest, OfflineEmptyPST) { + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(InstallTestRSAKey(&s)); + ASSERT_NO_FATAL_FAILURE(s.GenerateNonce()); + LicenseRoundTrip license_messages(&s); + license_messages.set_api_version(license_api_version_); + license_messages.set_control(wvoec::kControlNonceOrEntry); + // DO NOT SET PST: license_messages.set_pst(pst); + ASSERT_NO_FATAL_FAILURE(license_messages.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages.EncryptAndSignResponse()); + ASSERT_NO_FATAL_FAILURE(s.CreateNewUsageEntry()); + ASSERT_NE(OEMCrypto_SUCCESS, license_messages.LoadResponse()); +} + +// If we try to reload a license with a different PST, the attempt should fail. +TEST_P(OEMCryptoUsageTableTest, ReloadOfflineWrongPST) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.MakeOfflineAndClose(this); + Session& s = entry.session(); + + Session s2; + LicenseRoundTrip license_messages2(&s2); + license_messages2.response_data() = entry.license_messages().response_data(); + license_messages2.core_response() = entry.license_messages().core_response(); + // Change the middle of the pst. + license_messages2.response_data().pst[3] ^= 'Z'; + ASSERT_NO_FATAL_FAILURE(s2.open()); + ASSERT_NO_FATAL_FAILURE(InstallTestRSAKey(&s2)); + // This is a valid license: it is correctly signed. + license_messages2.EncryptAndSignResponse(); + // Load the usage entry should be OK. + ASSERT_NO_FATAL_FAILURE(s2.LoadUsageEntry(s)); + ASSERT_NE(OEMCrypto_SUCCESS, license_messages2.LoadResponse()); +} + +// Once a license has been deactivated, the keys can no longer be used for +// decryption. However, we can still generate a usage report. +TEST_P(OEMCryptoUsageTableTest, DeactivateOfflineLicense) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.MakeOfflineAndClose(this); + Session& s = entry.session(); + // Reload the offline license. + ASSERT_NO_FATAL_FAILURE(entry.OpenAndReload(this)); + ASSERT_NO_FATAL_FAILURE( + entry.TestDecryptCTR()); // Should be able to decrypt. + ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry()); // Then deactivate. + // After deactivate, should not be able to decrypt. + ASSERT_NO_FATAL_FAILURE( + entry.TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE)); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed)); + ASSERT_NO_FATAL_FAILURE(s.close()); + + // Offline license can not be reused if it has been deactivated. + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(InstallTestRSAKey(&s)); + ASSERT_NE(OEMCrypto_SUCCESS, entry.license_messages().LoadResponse(&s)); + s.close(); + + // But we can still generate a report. + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(entry.ReloadUsageEntry()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + // Sending a release from an offline license that has been deactivate will + // only work if the license server can handle v16 licenses. This is a rare + // condition, so it is OK to break it during the transition months. + entry.license_messages().set_api_version(global_features.api_version); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed)); + // We could call DeactivateUsageEntry multiple times. The state should not + // change. + ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed)); +} + +// The usage report should indicate that the keys were never used for +// decryption. +TEST_P(OEMCryptoUsageTableTest, DeactivateOfflineLicenseUnused) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.MakeOfflineAndClose(this); + Session& s = entry.session(); + // No Decrypt. This license is unused. + ASSERT_NO_FATAL_FAILURE(entry.OpenAndReload(this)); + ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry()); // Then deactivate. + // After deactivate, should not be able to decrypt. + ASSERT_NO_FATAL_FAILURE( + entry.TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE)); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUnused)); + ASSERT_NO_FATAL_FAILURE(s.close()); + + // Offline license can not be reused if it has been deactivated. + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(InstallTestRSAKey(&s)); + ASSERT_NE(OEMCrypto_SUCCESS, entry.license_messages().LoadResponse(&s)); + s.close(); + + // But we can still generate a report. + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(entry.ReloadUsageEntry()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + // Sending a release from an offline license that has been deactivate will + // only work if the license server can handle v16 licenses. This is a rare + // condition, so it is OK to break it during the transition months. + entry.license_messages().set_api_version(global_features.api_version); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUnused)); + // We could call DeactivateUsageEntry multiple times. The state should not + // change. + ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUnused)); +} + +TEST_P(OEMCryptoUsageTableTest, SecureStop) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.MakeAndLoadOnline(this); + Session& s = entry.session(); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(s.close()); + // When we generate a secure stop without loading the license first, it + // should assume the server does not support core messages. + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(entry.ReloadUsageEntry()); + ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + // It should report as inactive. + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed)); +} + +// Test update usage table fails when passed a null pointer. +TEST_P(OEMCryptoUsageTableTest, UpdateFailsWithNullPtr) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.MakeAndLoadOnline(this); + Session& s = entry.session(); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + size_t header_buffer_length = encrypted_usage_header_.size(); + size_t entry_buffer_length = s.encrypted_usage_entry().size(); + vector buffer(entry_buffer_length); + // Now try to pass in null pointers for the buffers. This should fail. + ASSERT_NE( + OEMCrypto_SUCCESS, + OEMCrypto_UpdateUsageEntry(s.session_id(), nullptr, &header_buffer_length, + buffer.data(), &entry_buffer_length)); + ASSERT_NE(OEMCrypto_SUCCESS, + OEMCrypto_UpdateUsageEntry( + s.session_id(), encrypted_usage_header_.data(), + &header_buffer_length, nullptr, &entry_buffer_length)); +} + +// Class used to test usage table defragmentation. +class OEMCryptoUsageTableDefragTest : public OEMCryptoUsageTableTest { + protected: + void ReloadLicense(LicenseWithUsageEntry* entry) { + Session& s = entry->session(); + ASSERT_NO_FATAL_FAILURE(entry->OpenAndReload(this)); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry->TestDecryptCTR()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry->GenerateVerifyReport(kActive)); + ASSERT_NO_FATAL_FAILURE(s.close()); + } + + void FailReloadLicense(LicenseWithUsageEntry* entry, + OEMCryptoResult expected_result) { + Session& s = entry->session(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(InstallTestRSAKey(&s)); + ASSERT_EQ(expected_result, + OEMCrypto_LoadUsageEntry(s.session_id(), s.usage_entry_number(), + s.encrypted_usage_entry().data(), + s.encrypted_usage_entry().size())); + + ASSERT_NE(OEMCrypto_SUCCESS, entry->license_messages().LoadResponse()); + ASSERT_NO_FATAL_FAILURE(s.close()); + } + + void ShrinkHeader(uint32_t new_size, + OEMCryptoResult expected_result = OEMCrypto_SUCCESS) { + // We call OEMCrypto_ShrinkUsageTableHeader once with a zero length buffer, + // so that OEMCrypto can tell us how big the buffer should be. + size_t header_buffer_length = 0; + OEMCryptoResult sts = OEMCrypto_ShrinkUsageTableHeader( + new_size, nullptr, &header_buffer_length); + // If we are expecting success, then the first call shall return + // SHORT_BUFFER. However, if we are not expecting success, this first call + // may return either SHORT_BUFFER or the expect error. + if (expected_result == OEMCrypto_SUCCESS) { + ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts); + } else if (sts != OEMCrypto_ERROR_SHORT_BUFFER) { + // If we got any thing from the first call, it should be the expected + // error, and we don't need to call a second time. + ASSERT_EQ(expected_result, sts); + return; + } + // If the first call resulted in SHORT_BUFFER, we should resize the buffer + // and try again. + ASSERT_LT(0u, header_buffer_length); + encrypted_usage_header_.resize(header_buffer_length); + sts = OEMCrypto_ShrinkUsageTableHeader( + new_size, encrypted_usage_header_.data(), &header_buffer_length); + // For the second call, we always demand the expected result. + ASSERT_EQ(expected_result, sts); + } +}; + +// Verify that usage table entries can be moved around in the table. +TEST_P(OEMCryptoUsageTableDefragTest, MoveUsageEntries) { + const size_t ENTRY_COUNT = 10; + vector entries(ENTRY_COUNT); + for (size_t i = 0; i < ENTRY_COUNT; i++) { + entries[i].set_pst("pst " + std::to_string(i)); + ASSERT_NO_FATAL_FAILURE(entries[i].MakeOfflineAndClose(this)) + << "On license " << i << " pst=" << entries[i].pst(); + wvutil::TestSleep::SyncFakeClock(); + } + for (size_t i = 0; i < ENTRY_COUNT; i++) { + ASSERT_NO_FATAL_FAILURE(entries[i].OpenAndReload(this)) + << "On license " << i << " pst=" << entries[i].pst(); + ASSERT_NO_FATAL_FAILURE(entries[i].session().close()) + << "On license " << i << " pst=" << entries[i].pst(); + } + // Move 4 to 1. + ASSERT_NO_FATAL_FAILURE( + entries[4].session().MoveUsageEntry(1, &encrypted_usage_header_)); + // Shrink header to 3 entries 0, 1 was 4, 2. + ASSERT_NO_FATAL_FAILURE(ShrinkHeader(3)); + ShutDown(); + Restart(); + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_LoadUsageTableHeader(encrypted_usage_header_.data(), + encrypted_usage_header_.size())); + wvutil::TestSleep::SyncFakeClock(); + ASSERT_NO_FATAL_FAILURE(ReloadLicense(&entries[0])); + // Now has index 1. + ASSERT_NO_FATAL_FAILURE(ReloadLicense(&entries[4])); + ASSERT_NO_FATAL_FAILURE(ReloadLicense(&entries[2])); + // When 4 was moved to 1, it increased the gen. number in the header. + ASSERT_NO_FATAL_FAILURE( + FailReloadLicense(&entries[1], OEMCrypto_ERROR_GENERATION_SKEW)); + // Index 3 is beyond the end of the table. + ASSERT_NO_FATAL_FAILURE( + FailReloadLicense(&entries[3], OEMCrypto_ERROR_UNKNOWN_FAILURE)); +} + +// A usage table entry cannot be moved into an entry where an open session is +// currently using the entry. +TEST_P(OEMCryptoUsageTableDefragTest, MoveUsageEntriesToOpenSession) { + LicenseWithUsageEntry entry0; + entry0.set_pst("pst 0"); + entry0.MakeOfflineAndClose(this); + LicenseWithUsageEntry entry1; + entry1.set_pst("pst 1"); + entry1.MakeOfflineAndClose(this); + + entry0.session().open(); + ASSERT_NO_FATAL_FAILURE(entry0.ReloadUsageEntry()); + // s0 currently open on index 0. Expect this to fail: + ASSERT_NO_FATAL_FAILURE(entry1.session().MoveUsageEntry( + 0, &encrypted_usage_header_, OEMCrypto_ERROR_ENTRY_IN_USE)); +} + +// The usage table cannot be shrunk if any session is using an entry that would +// be deleted. +TEST_P(OEMCryptoUsageTableDefragTest, ShrinkOverOpenSessions) { + LicenseWithUsageEntry entry0; + entry0.set_pst("pst 0"); + entry0.MakeOfflineAndClose(this); + LicenseWithUsageEntry entry1; + entry1.set_pst("pst 1"); + entry1.MakeOfflineAndClose(this); + + entry0.session().open(); + ASSERT_NO_FATAL_FAILURE(entry0.ReloadUsageEntry()); + entry1.session().open(); + ASSERT_NO_FATAL_FAILURE(entry1.ReloadUsageEntry()); + // Since s0 and s1 are open, we can't shrink. + ASSERT_NO_FATAL_FAILURE(ShrinkHeader(1, OEMCrypto_ERROR_ENTRY_IN_USE)); + entry1.session().close(); // Can shrink after closing s1, even if s0 is open. + ASSERT_NO_FATAL_FAILURE(ShrinkHeader(1, OEMCrypto_SUCCESS)); +} + +// Verify the usage table size can be increased. +TEST_P(OEMCryptoUsageTableDefragTest, EnlargeHeader) { + LicenseWithUsageEntry entry0; + entry0.set_pst("pst 0"); + entry0.MakeOfflineAndClose(this); + LicenseWithUsageEntry entry1; + entry1.set_pst("pst 1"); + entry1.MakeOfflineAndClose(this); + + // Can only shrink the header -- not make it bigger. + ASSERT_NO_FATAL_FAILURE(ShrinkHeader(4, OEMCrypto_ERROR_UNKNOWN_FAILURE)); +} + +// A new header can only be created while no entries are in use. +TEST_P(OEMCryptoUsageTableDefragTest, CreateNewHeaderWhileUsingOldOne) { + LicenseWithUsageEntry entry0; + entry0.set_pst("pst 0"); + entry0.MakeOfflineAndClose(this); + LicenseWithUsageEntry entry1; + entry1.set_pst("pst 1"); + entry1.MakeOfflineAndClose(this); + + entry0.session().open(); + ASSERT_NO_FATAL_FAILURE(entry0.ReloadUsageEntry()); + const bool kExpectFailure = false; + ASSERT_NO_FATAL_FAILURE(CreateUsageTableHeader(kExpectFailure)); +} + +// Verify that a usage table entry can only be loaded into the correct index of +// the table. +TEST_P(OEMCryptoUsageTableDefragTest, ReloadUsageEntryWrongIndex) { + LicenseWithUsageEntry entry0; + entry0.set_pst("pst 0"); + entry0.MakeOfflineAndClose(this); + LicenseWithUsageEntry entry1; + entry1.set_pst("pst 1"); + entry1.MakeOfflineAndClose(this); + + entry0.session().set_usage_entry_number(1); + ASSERT_NO_FATAL_FAILURE( + FailReloadLicense(&entry0, OEMCrypto_ERROR_INVALID_SESSION)); +} + +// Verify that a usage table entry cannot be loaded if it has been altered. +TEST_P(OEMCryptoUsageTableDefragTest, ReloadUsageEntryBadData) { + LicenseWithUsageEntry entry; + entry.MakeOfflineAndClose(this); + Session& s = entry.session(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(InstallTestRSAKey(&s)); + vector data = s.encrypted_usage_entry(); + ASSERT_LT(0UL, data.size()); + data[0] ^= 42; + // Error could be signature or verification error. + ASSERT_NE(OEMCrypto_SUCCESS, + OEMCrypto_LoadUsageEntry(s.session_id(), s.usage_entry_number(), + data.data(), data.size())); +} + +// This verifies we can actually create the required number of usage table +// entries. +TEST_P(OEMCryptoUsageTableDefragTest, ManyUsageEntries) { + // OEMCrypto is required to store at least 300 entries in the usage table + // header, but it is allowed to store more. This test verifies that if we keep + // adding entries, the error indicates a resource limit. It then verifies + // that all of the successful entries are still valid after we throw out the + // last invalid entry. + + // After API 16, we require 300 entries in the usage table. Before API 16, we + // required 200. + const size_t required_capacity = RequiredUsageSize(); + + // We try to make a much large header, and assume there is an error at some + // point. + const size_t attempt_count = required_capacity * 5; + // Count of how many entries we successfully create. + size_t successful_count = 0; + + // These entries have licenses tied to them. + std::vector> entries; + // Store the status of the last attempt to create an entry. + OEMCryptoResult status = OEMCrypto_SUCCESS; + while (successful_count < attempt_count && status == OEMCrypto_SUCCESS) { + wvutil::TestSleep::SyncFakeClock(); + LOGD("Creating license for entry %zd", successful_count); + entries.push_back( + std::unique_ptr(new LicenseWithUsageEntry())); + entries.back()->set_pst("pst " + std::to_string(successful_count)); + ASSERT_NO_FATAL_FAILURE(entries.back()->MakeOfflineAndClose(this, &status)) + << "Failed creating license for entry " << successful_count; + if (status != OEMCrypto_SUCCESS) { + // Remove the failed session. + entries.resize(entries.size() - 1); + break; + } + EXPECT_EQ(entries.back()->session().usage_entry_number(), successful_count); + successful_count++; + // We don't create a license for each entry. For every license, we'll + // create 10 empty entries. + constexpr size_t filler_count = 10; + for (size_t i = 0; i < filler_count; i++) { + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.CreateNewUsageEntry(&status)) + << "Failed creating entry " << successful_count; + if (status != OEMCrypto_SUCCESS) break; + EXPECT_EQ(s.usage_entry_number(), successful_count); + successful_count++; + } + } + LOGD("successful_count = %d", successful_count); + if (status != OEMCrypto_SUCCESS) { + // If we failed to create this many entries because of limited resources, + // then the error returned should be insufficient resources. + EXPECT_EQ(OEMCrypto_ERROR_INSUFFICIENT_RESOURCES, status) + << "Failed to create license " << successful_count + << ", with wrong error code."; + } + EXPECT_GE(successful_count, required_capacity); + wvutil::TestSleep::SyncFakeClock(); + // Shrink the table a little. + constexpr size_t small_number = 5; + size_t smaller_size = successful_count - small_number; + ASSERT_NO_FATAL_FAILURE(ShrinkHeader(smaller_size)); + // Throw out the last license if it was in the part of the table that was + // shrunk. + if (entries.back()->session().usage_entry_number() >= smaller_size) { + entries.pop_back(); + } + // Create a few more license + for (size_t i = 0; i < small_number; i++) { + wvutil::TestSleep::SyncFakeClock(); + entries.push_back( + std::unique_ptr(new LicenseWithUsageEntry())); + entries.back()->set_pst("new pst " + std::to_string(smaller_size + i)); + entries.back()->MakeOfflineAndClose(this); + } + // Make sure that all of the licenses can be reloaded. + for (size_t i = 0; i < entries.size(); i++) { + wvutil::TestSleep::SyncFakeClock(); + Session& s = entries[i]->session(); + ASSERT_NO_FATAL_FAILURE(entries[i]->OpenAndReload(this)); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entries[i]->GenerateVerifyReport(kUnused)); + ASSERT_NO_FATAL_FAILURE(entries[i]->TestDecryptCTR()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entries[i]->GenerateVerifyReport(kActive)); + ASSERT_NO_FATAL_FAILURE(s.close()); + } +} + +// This verifies that the usage table header can be loaded if the generation +// number is off by one, but not off by two. +TEST_P(OEMCryptoUsageTableTest, ReloadUsageTableWithSkew) { + // This also tests a few other error conditions with usage table headers. + LicenseWithUsageEntry entry; + entry.MakeOfflineAndClose(this); + Session& s = entry.session(); + + // Reload the license, and save the header. + ASSERT_NO_FATAL_FAILURE(entry.OpenAndReload(this)); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + vector old_usage_header_2_ = encrypted_usage_header_; + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + vector old_usage_header_1_ = encrypted_usage_header_; + vector old_usage_entry_1 = s.encrypted_usage_entry(); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(s.close()); + + ShutDown(); + Restart(); + // Null pointer generates error. + ASSERT_NE(OEMCrypto_SUCCESS, OEMCrypto_LoadUsageTableHeader( + nullptr, old_usage_header_2_.size())); + ASSERT_NO_FATAL_FAILURE(s.open()); + // Cannot load an entry if header didn't load. + ASSERT_EQ(OEMCrypto_ERROR_UNKNOWN_FAILURE, + OEMCrypto_LoadUsageEntry(s.session_id(), s.usage_entry_number(), + s.encrypted_usage_entry().data(), + s.encrypted_usage_entry().size())); + ASSERT_NO_FATAL_FAILURE(s.close()); + + // Modified header generates error. + vector bad_header = encrypted_usage_header_; + bad_header[3] ^= 42; + ASSERT_NE(OEMCrypto_SUCCESS, OEMCrypto_LoadUsageTableHeader( + bad_header.data(), bad_header.size())); + ASSERT_NO_FATAL_FAILURE(s.open()); + // Cannot load an entry if header didn't load. + ASSERT_EQ(OEMCrypto_ERROR_UNKNOWN_FAILURE, + OEMCrypto_LoadUsageEntry(s.session_id(), s.usage_entry_number(), + s.encrypted_usage_entry().data(), + s.encrypted_usage_entry().size())); + ASSERT_NO_FATAL_FAILURE(s.close()); + + // Old by 2 generation numbers is error. + ASSERT_EQ(OEMCrypto_ERROR_GENERATION_SKEW, + OEMCrypto_LoadUsageTableHeader(old_usage_header_2_.data(), + old_usage_header_2_.size())); + ASSERT_NO_FATAL_FAILURE(s.open()); + // Cannot load an entry if header didn't load. + ASSERT_NE(OEMCrypto_SUCCESS, + OEMCrypto_LoadUsageEntry(s.session_id(), s.usage_entry_number(), + s.encrypted_usage_entry().data(), + s.encrypted_usage_entry().size())); + ASSERT_NO_FATAL_FAILURE(s.close()); + + // Old by 1 generation numbers is just warning. + ASSERT_EQ(OEMCrypto_WARNING_GENERATION_SKEW, + OEMCrypto_LoadUsageTableHeader(old_usage_header_1_.data(), + old_usage_header_1_.size())); + // Everything else should still work. The old entry goes with the old header. + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_LoadUsageEntry(s.session_id(), s.usage_entry_number(), + old_usage_entry_1.data(), + old_usage_entry_1.size())); + ASSERT_NO_FATAL_FAILURE(InstallTestRSAKey(&s)); + ASSERT_NO_FATAL_FAILURE(s.GenerateDerivedKeysFromSessionKey()); + ASSERT_EQ(OEMCrypto_SUCCESS, entry.license_messages().LoadResponse()); +} + +// A usage report with the wrong pst should fail. +TEST_P(OEMCryptoUsageTableTest, GenerateReportWrongPST) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.MakeAndLoadOnline(this); + Session& s = entry.session(); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE( + s.GenerateReport("wrong_pst", OEMCrypto_ERROR_WRONG_PST)); +} + +// Test usage table timing. +TEST_P(OEMCryptoUsageTableTest, TimingTest) { + LicenseWithUsageEntry entry1; + entry1.license_messages().set_api_version(license_api_version_); + Session& s1 = entry1.session(); + entry1.set_pst("my_pst_1"); + ASSERT_NO_FATAL_FAILURE(entry1.MakeOfflineAndClose(this)); + + LicenseWithUsageEntry entry2; + entry2.license_messages().set_api_version(license_api_version_); + Session& s2 = entry2.session(); + entry2.set_pst("my_pst_2"); + ASSERT_NO_FATAL_FAILURE(entry2.MakeOfflineAndClose(this)); + + LicenseWithUsageEntry entry3; + entry3.license_messages().set_api_version(license_api_version_); + Session& s3 = entry3.session(); + entry3.set_pst("my_pst_3"); + ASSERT_NO_FATAL_FAILURE(entry3.MakeOfflineAndClose(this)); + + ASSERT_NO_FATAL_FAILURE(entry1.MakeOfflineAndClose(this)); + ASSERT_NO_FATAL_FAILURE(entry2.MakeOfflineAndClose(this)); + ASSERT_NO_FATAL_FAILURE(entry3.MakeOfflineAndClose(this)); + + wvutil::TestSleep::Sleep(kLongSleep); + ASSERT_NO_FATAL_FAILURE(entry1.OpenAndReload(this)); + ASSERT_NO_FATAL_FAILURE(entry1.TestDecryptCTR()); + + ASSERT_NO_FATAL_FAILURE(entry2.OpenAndReload(this)); + ASSERT_NO_FATAL_FAILURE(entry2.TestDecryptCTR()); + + wvutil::TestSleep::Sleep(kLongSleep); + ASSERT_NO_FATAL_FAILURE(entry1.TestDecryptCTR()); + ASSERT_NO_FATAL_FAILURE(entry2.TestDecryptCTR()); + + wvutil::TestSleep::Sleep(kLongSleep); + ASSERT_NO_FATAL_FAILURE(entry1.DeactivateUsageEntry()); + ASSERT_NO_FATAL_FAILURE(s1.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(s2.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(s1.close()); + ASSERT_NO_FATAL_FAILURE(s2.close()); + + wvutil::TestSleep::Sleep(kLongSleep); + // This is as close to reboot as we can simulate in code. + ShutDown(); + wvutil::TestSleep::Sleep(kShortSleep); + Restart(); + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_LoadUsageTableHeader(encrypted_usage_header_.data(), + encrypted_usage_header_.size())); + + // After a reboot, we should be able to reload keys, and generate reports. + wvutil::TestSleep::Sleep(kLongSleep); + ASSERT_NO_FATAL_FAILURE(entry2.OpenAndReload(this)); + ASSERT_NO_FATAL_FAILURE(entry2.TestDecryptCTR()); + ASSERT_NO_FATAL_FAILURE(s2.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(s2.close()); + + ASSERT_NO_FATAL_FAILURE(s1.open()); + ASSERT_NO_FATAL_FAILURE(entry1.ReloadUsageEntry()); + ASSERT_NO_FATAL_FAILURE(entry2.OpenAndReload(this)); + ASSERT_NO_FATAL_FAILURE(entry3.OpenAndReload(this)); + + wvutil::TestSleep::Sleep(kLongSleep); + ASSERT_NO_FATAL_FAILURE(s1.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry1.GenerateVerifyReport(kInactiveUsed)); + ASSERT_NO_FATAL_FAILURE(s2.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry2.GenerateVerifyReport(kActive)); + ASSERT_NO_FATAL_FAILURE(s3.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry3.GenerateVerifyReport(kUnused)); +} + +// Verify the times in the usage report. For performance reasons, we allow the +// times in the usage report to be off by as much as kUsageTimeTolerance, which +// is 10 seconds. This acceptable error is called slop. This test needs to run +// long enough that the reported values are distinct, even after accounting for +// this slop. +TEST_P(OEMCryptoUsageTableTest, VerifyUsageTimes) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.MakeAndLoadOnline(this); + Session& s = entry.session(); + + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused)); + + const int64_t kDotIntervalInSeconds = 5; + const int64_t kIdleInSeconds = 20; + const int64_t kPlaybackLoopInSeconds = 2 * 60; + + cout << "This test verifies the elapsed time reported in the usage table " + "for a 2 minute simulated playback." + << endl; + cout << "The total time for this test is about " + << kPlaybackLoopInSeconds + 2 * kIdleInSeconds << " seconds." << endl; + cout << "Wait " << kIdleInSeconds + << " seconds to verify usage table time before playback." << endl; + + PrintDotsWhileSleep(kIdleInSeconds, kDotIntervalInSeconds); + + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused)); + cout << "Start simulated playback..." << endl; + + int64_t dot_time = kDotIntervalInSeconds; + int64_t playback_time = 0; + const int64_t start_time = wvutil::Clock().GetCurrentTime(); + do { + ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); + wvutil::TestSleep::Sleep(kShortSleep); + playback_time = wvutil::Clock().GetCurrentTime() - start_time; + ASSERT_LE(0, playback_time); + if (playback_time >= dot_time) { + cout << "."; + cout.flush(); + dot_time += kDotIntervalInSeconds; + } + } while (playback_time < kPlaybackLoopInSeconds); + cout << "\nSimulated playback time = " << playback_time << " seconds.\n"; + + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); + EXPECT_NEAR(s.pst_report().seconds_since_first_decrypt() - + s.pst_report().seconds_since_last_decrypt(), + playback_time, kUsageTableTimeTolerance); + + cout << "Wait another " << kIdleInSeconds + << " seconds " + "to verify usage table time since playback ended." + << endl; + PrintDotsWhileSleep(kIdleInSeconds, kDotIntervalInSeconds); + + // At this point, this is what we expect: + // idle playback loop idle + // |-----|-------------------------|-----| + // |<--->| = seconds_since_last_decrypt + // |<----------------------------->| = seconds_since_first_decrypt + // |<------------------------------------| = seconds_since_license_received + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); + ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed)); + ASSERT_NO_FATAL_FAILURE( + entry.TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE)); +} + +// This test class is only used to roll back the wall clock. It is used to +// verify that OEMCrypto's system clock is monotonic. It is should only be run +// on a device that allows an application to set the clock. +class OEMCryptoUsageTableTestWallClock : public OEMCryptoUsageTableTest { + public: + void SetUp() override { + OEMCryptoUsageTableTest::SetUp(); + } + + void TearDown() override { + wvutil::TestSleep::ResetRollback(); + OEMCryptoUsageTableTest::TearDown(); + } +}; + +// NOTE: This test needs root access since clock_settime messes with the system +// time in order to verify that OEMCrypto protects against rollbacks in usage +// entries. Therefore, this test is filtered if not run as root. +// We don't test roll-forward protection or instances where the user rolls back +// the time to the last decrypt call since this requires hardware-secure clocks +// to guarantee. +// +// This test overlaps two tests in parallel because they each have several +// seconds of sleeping, then we roll the system clock back, and then we sleep +// some more. +// For the first test, we use entry1. The playback duration is 6 short +// intervals. We play for 3, roll the clock back 2, and then play for 3 more. +// We then sleep until after the allowed playback duration and try to play. If +// OEMCrypto allows the rollback, then there is only 5 intervals, which is +// legal. But if OEMCrypto forbids the rollback, then there is 8 intervals of +// playback, which is not legal. +// +// For the second test, we use entry2. The rental duration is 6 short +// intervals. The times are the same as for entry1, except we do not start +// playback for entry2 until the end. + +// clang-format off +// [--][--][--][--][--][--][--] -- playback or rental limit. +// +// Here's what the system clock sees with rollback: +// [--][--][--] 3 short intervals of playback or sleep +// <------> Rollback 2 short intervals. +// [--][--][--] 3 short intervals of playback or sleep +// [--] 1 short intervals of sleep. +// +// Here's what the system clock sees without rollback: +// [--][--][--] 3 short intervals of playback or sleep +// [--][--][--] 3 short intervals of playback or sleep +// [--][--]X 2 short intervals of sleep. +// +// |<---------------------------->| 8 short intervals from license received +// until pst reports generated. +// clang-format on + +TEST_P(OEMCryptoUsageTableTestWallClock, TimeRollbackPrevention) { + cout << "This test temporarily rolls back the system time in order to verify " + << "that the usage report accounts for the change. After the test, it " + << "rolls the clock back forward." << endl; + constexpr int kRollBackTime = kShortSleep * 2; + constexpr int kPlaybackCount = 3; + constexpr int kTotalTime = kShortSleep * 8; + + LicenseWithUsageEntry entry1; + entry1.license_messages() + .core_response() + .timer_limits.total_playback_duration_seconds = 7 * kShortSleep; + entry1.MakeOfflineAndClose(this); + Session& s1 = entry1.session(); + ASSERT_NO_FATAL_FAILURE(entry1.OpenAndReload(this)); + + LicenseWithUsageEntry entry2; + entry2.license_messages() + .core_response() + .timer_limits.rental_duration_seconds = 7 * kShortSleep; + entry2.MakeOfflineAndClose(this); + Session& s2 = entry2.session(); + ASSERT_NO_FATAL_FAILURE(entry2.OpenAndReload(this)); + + // Start with three short intervals of playback for entry1. + for (int i = 0; i < kPlaybackCount; i++) { + ASSERT_NO_FATAL_FAILURE(entry1.TestDecryptCTR()); + wvutil::TestSleep::Sleep(kShortSleep); + ASSERT_NO_FATAL_FAILURE(entry1.TestDecryptCTR()); + } + + cout << "Rolling the system time back..." << endl; + ASSERT_TRUE(wvutil::TestSleep::RollbackSystemTime(kRollBackTime)); + + // Three more short intervals of playback after the rollback. + for (int i = 0; i < kPlaybackCount; i++) { + ASSERT_NO_FATAL_FAILURE(entry1.TestDecryptCTR()); + wvutil::TestSleep::Sleep(kShortSleep); + ASSERT_NO_FATAL_FAILURE(entry1.TestDecryptCTR()); + } + + // One short interval of sleep to push us past the 6 interval duration. + wvutil::TestSleep::Sleep(2 * kShortSleep); + + // Should not be able to continue playback in entry1. + ASSERT_NO_FATAL_FAILURE( + entry1.TestDecryptCTR(false, OEMCrypto_ERROR_KEY_EXPIRED)); + // Should not be able to start playback in entry2. + ASSERT_NO_FATAL_FAILURE( + entry2.TestDecryptCTR(true, OEMCrypto_ERROR_KEY_EXPIRED)); + + // Now we look at the usage reports: + ASSERT_NO_FATAL_FAILURE(s1.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(s2.UpdateUsageEntry(&encrypted_usage_header_)); + + ASSERT_NO_FATAL_FAILURE(s1.GenerateReport(entry1.pst())); + wvutil::Unpacked_PST_Report report1 = s1.pst_report(); + EXPECT_EQ(report1.status(), kActive); + EXPECT_GE(report1.seconds_since_license_received(), kTotalTime); + EXPECT_GE(report1.seconds_since_first_decrypt(), kTotalTime); + + ASSERT_NO_FATAL_FAILURE(s2.GenerateReport(entry2.pst())); + wvutil::Unpacked_PST_Report report2 = s2.pst_report(); + EXPECT_EQ(report2.status(), kUnused); + EXPECT_GE(report2.seconds_since_license_received(), kTotalTime); +} + +// Verify that a large PST can be used with usage table entries. +TEST_P(OEMCryptoUsageTableTest, PSTLargeBuffer) { + std::string pst(kMaxPSTLength, 'a'); // A large PST. + LicenseWithUsageEntry entry(pst); + entry.MakeOfflineAndClose(this); + Session& s = entry.session(); + + ASSERT_NO_FATAL_FAILURE(entry.OpenAndReload(this)); + ASSERT_NO_FATAL_FAILURE( + entry.TestDecryptCTR()); // Should be able to decrypt. + ASSERT_NO_FATAL_FAILURE(entry.DeactivateUsageEntry()); // Then deactivate. + // After deactivate, should not be able to decrypt. + ASSERT_NO_FATAL_FAILURE( + entry.TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE)); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed)); + ASSERT_NO_FATAL_FAILURE(s.close()); +} + +INSTANTIATE_TEST_CASE_P(TestAll, OEMCryptoUsageTableTest, + Range(kCurrentAPI - 1, kCurrentAPI + 1)); + +// These tests only work when the license has a core message. +INSTANTIATE_TEST_CASE_P(TestAPI16, OEMCryptoUsageTableDefragTest, + Values(kCurrentAPI)); + +// These tests only work when the license has a core message. +INSTANTIATE_TEST_CASE_P(TestAPI16, OEMCryptoUsageTableTestWallClock, + Values(kCurrentAPI)); + +} // namespace wvoec diff --git a/oemcrypto/test/oemcrypto_test_android.cpp b/oemcrypto/test/oemcrypto_test_android.cpp new file mode 100644 index 0000000..79f3a0a --- /dev/null +++ b/oemcrypto/test/oemcrypto_test_android.cpp @@ -0,0 +1,161 @@ +// 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. +// +// OEMCrypto unit tests - extra tests required for Android platform. +// +// The Widevine CDM system can be built on many platforms, with different +// capabilities. For example, some platforms do not require usage tables, +// and some can have a pre-installed certificate and do not need a keybox. +// On Android, these features are not optional. This set of unit tests +// verify that these features are implemented. +// +// In the file oemcrypto_test.cpp, the unit tests only verify correct +// functionality for functions that are implemented. Android devices must pass +// unit tests in both files. + +#include +#include "oec_test_data.h" +#include "OEMCryptoCAS.h" + +namespace wvoec { + +// These tests are required for LollyPop Android devices. +class OEMCryptoAndroidLMPTest : public ::testing::Test { + protected: + void SetUp() override { + OEMCrypto_SetSandbox(kTestSandbox, sizeof(kTestSandbox)); + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_Initialize()); + } + + void TearDown() override { OEMCrypto_Terminate(); } +}; + +// Android devices must have a keybox, or use provisioning 3.0. +TEST_F(OEMCryptoAndroidLMPTest, GetKeyDataImplemented) { + uint8_t key_data[256]; + size_t key_data_len = sizeof(key_data); + if (OEMCrypto_Keybox == OEMCrypto_GetProvisioningMethod()) { + ASSERT_NE(OEMCrypto_ERROR_NOT_IMPLEMENTED, + OEMCrypto_GetKeyData(key_data, &key_data_len)); + } else { + ASSERT_EQ(OEMCrypto_OEMCertificate, OEMCrypto_GetProvisioningMethod()); + } +} + +// Android devices must have a valid keybox. +TEST_F(OEMCryptoAndroidLMPTest, ValidKeybox) { + if (OEMCrypto_GetProvisioningMethod() == OEMCrypto_Keybox) { + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_IsKeyboxValid()); + } +} + +TEST_F(OEMCryptoAndroidLMPTest, MinVersionNumber9) { + uint32_t version = OEMCrypto_APIVersion(); + ASSERT_LE(9u, version); +} + +TEST_F(OEMCryptoAndroidLMPTest, ValidKeyboxTest) { + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_IsKeyboxValid()); +} + +TEST_F(OEMCryptoAndroidLMPTest, RewrapDeviceRSAKeyImplemented) { + ASSERT_NE(OEMCrypto_ERROR_NOT_IMPLEMENTED, + OEMCrypto_LoadProvisioning(0, nullptr, 0, 0, nullptr, 0, nullptr, + nullptr)); +} + +// The Generic Crypto API functions are required for Android. +TEST_F(OEMCryptoAndroidLMPTest, GenericCryptoImplemented) { + ASSERT_NE( + OEMCrypto_ERROR_NOT_IMPLEMENTED, + OEMCrypto_Generic_Encrypt(0, nullptr, 0, nullptr, + OEMCrypto_AES_CBC_128_NO_PADDING, nullptr)); + ASSERT_NE( + OEMCrypto_ERROR_NOT_IMPLEMENTED, + OEMCrypto_Generic_Decrypt(0, nullptr, 0, nullptr, + OEMCrypto_AES_CBC_128_NO_PADDING, nullptr)); + ASSERT_NE(OEMCrypto_ERROR_NOT_IMPLEMENTED, + OEMCrypto_Generic_Sign(0, nullptr, 0, OEMCrypto_HMAC_SHA256, + nullptr, nullptr)); + ASSERT_NE(OEMCrypto_ERROR_NOT_IMPLEMENTED, + OEMCrypto_Generic_Verify(0, nullptr, 0, OEMCrypto_HMAC_SHA256, + nullptr, 0)); +} + +// Android requires support of usage table. The usage table is used for Secure +// Stops and for offline licenses. +TEST_F(OEMCryptoAndroidLMPTest, SupportsUsageTable) { + ASSERT_TRUE(OEMCrypto_SupportsUsageTable()); +} + +// Android devices require L1 OEMCrypto. +TEST_F(OEMCryptoAndroidLMPTest, Level1Required) { + const char* char_level = OEMCrypto_SecurityLevel(); + std::string security_level(char_level ? char_level : ""); + EXPECT_EQ("L1", security_level) + << "The security level is " << security_level << ". but we expect L1.\n" + << "If you are testing a device that should be L3 or L2, please\n" + << "repeat the tests with the flag --gtest_filter=\"*-*Level1Required\""; +} + +// These tests are required for M Android devices. +class OEMCryptoAndroidMNCTest : public OEMCryptoAndroidLMPTest {}; + +TEST_F(OEMCryptoAndroidMNCTest, MinVersionNumber10) { + uint32_t version = OEMCrypto_APIVersion(); + ASSERT_GE(version, 10u); +} + +// Android devices using Provisioning 2.0 must be able to load a test keybox. +// If they are not using Provisioning 2.0, then they must use Provisioning 3.0. +TEST_F(OEMCryptoAndroidMNCTest, LoadsTestKeyboxImplemented) { + if (OEMCrypto_Keybox == OEMCrypto_GetProvisioningMethod()) { + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_LoadTestKeybox( + reinterpret_cast(&kTestKeybox), + sizeof(kTestKeybox))); + } else { + // Android should use keybox or provisioning 3.0. + ASSERT_EQ(OEMCrypto_OEMCertificate, OEMCrypto_GetProvisioningMethod()); + } +} + +// Android requires implementation of these functions. +TEST_F(OEMCryptoAndroidMNCTest, NumberOfSessionsImplemented) { + ASSERT_NE(OEMCrypto_ERROR_NOT_IMPLEMENTED, + OEMCrypto_GetNumberOfOpenSessions(nullptr)); + ASSERT_NE(OEMCrypto_ERROR_NOT_IMPLEMENTED, + OEMCrypto_GetMaxNumberOfSessions(nullptr)); +} + +// Android requires implementation of these functions. +TEST_F(OEMCryptoAndroidMNCTest, QueryKeyControlImplemented) { + ASSERT_NE(OEMCrypto_ERROR_NOT_IMPLEMENTED, + OEMCrypto_QueryKeyControl(0, nullptr, 0, nullptr, nullptr)); +} + +// These tests are required for N Android devices. +class OEMCryptoAndroidNYCTest : public OEMCryptoAndroidMNCTest {}; + +TEST_F(OEMCryptoAndroidNYCTest, MinVersionNumber11) { + uint32_t version = OEMCrypto_APIVersion(); + ASSERT_GE(version, 11u); +} + +// These tests are required for O MR1 Android devices. +class OEMCryptoAndroidOCTest : public OEMCryptoAndroidNYCTest {}; + +TEST_F(OEMCryptoAndroidOCTest, MinVersionNumber13) { + uint32_t version = OEMCrypto_APIVersion(); + ASSERT_GE(version, 13u); +} + +// These tests are required for Q Android devices. +class OEMCryptoAndroidQTest : public OEMCryptoAndroidOCTest {}; + +TEST_F(OEMCryptoAndroidQTest, MinVersionNumber14) { + uint32_t version = OEMCrypto_APIVersion(); + ASSERT_GE(version, 15u); +} + +} // namespace wvoec diff --git a/oemcrypto/test/oemcrypto_test_main.cpp b/oemcrypto/test/oemcrypto_test_main.cpp new file mode 100644 index 0000000..46fd3fa --- /dev/null +++ b/oemcrypto/test/oemcrypto_test_main.cpp @@ -0,0 +1,59 @@ +#include +#include + +#include "OEMCryptoCAS.h" +#include "log.h" +#include "oec_device_features.h" +#include "oemcrypto_corpus_generator_helper.h" +#include "test_sleep.h" + +static void acknowledge_cast() { + std::cout + << "==================================================================\n" + << "= This device is expected to load x509 certs as a cast receiver. =\n" + << "==================================================================\n"; +} + +// This special main procedure is used instead of the standard GTest main, +// because we need to initialize the list of features supported by the device. +// Also, the test filter is updated based on the feature list. +int main(int argc, char** argv) { + bool is_cast_receiver = false; + bool filter_tests = true; + int verbosity = 0; + // Skip the first element, which is the program name. + const std::vector args(argv + 1, argv + argc); + for (const std::string& arg : args) { + if (arg == "--generate_corpus") { + wvoec::SetGenerateCorpus(true); + } + if (arg == "--verbose" || arg == "-v") { + ++verbosity; + } else if (arg == "--cast") { + acknowledge_cast(); + is_cast_receiver = true; + } + if (arg == "--force_load_test_keybox") { + std::cerr << "The argument --force_load_test_keybox is obsolete.\n"; + return 1; + } + if (arg == "--no_filter") { + filter_tests = false; + } + if (arg == "--fake_sleep") { + wvutil::TestSleep::set_real_sleep(false); + } + } + wvutil::g_cutoff = static_cast(verbosity); + wvoec::global_features.Initialize(); + wvoec::global_features.set_cast_receiver(is_cast_receiver); + // Init GTest after device properties has been initialized. + ::testing::InitGoogleTest(&argc, argv); + // If the user requests --no_filter, we don't change the filter, otherwise, we + // filter out features that are not supported. + if (filter_tests) { + ::testing::GTEST_FLAG(filter) = + wvoec::global_features.RestrictFilter(::testing::GTEST_FLAG(filter)); + } + return RUN_ALL_TESTS(); +} diff --git a/oemcrypto/test/test_sleep.cpp b/oemcrypto/test/test_sleep.cpp new file mode 100644 index 0000000..96c0bce --- /dev/null +++ b/oemcrypto/test/test_sleep.cpp @@ -0,0 +1,148 @@ +// 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. + +#include "test_sleep.h" + +#ifdef _WIN32 +# include +#else +# include +#endif + +#include +#include +#include + +#include + +#include "clock.h" +#include "log.h" + +namespace wvutil { + +bool TestSleep::real_sleep_ = true; +TestSleep::CallBack* TestSleep::callback_ = nullptr; +int TestSleep::total_clock_rollback_ = 0; + +void TestSleep::Sleep(unsigned int seconds) { + int64_t milliseconds = 1000 * seconds; + if (real_sleep_) { + // This next bit of logic is to avoid slow drift apart of the real clock and + // the fake clock. We compute how far from the real clock has advanced in + // total since the start, and then compare to a running total of sleep + // calls. We sleep for approximately x second, and then advance the clock by + // the amount of time that has actually passed. + static auto start_real = std::chrono::system_clock().now(); + static int64_t fake_clock = 0; + sleep(seconds); + auto now_real = std::chrono::system_clock().now(); + int64_t total_real = (now_real - start_real) / std::chrono::milliseconds(1); + // We want to advance the fake clock by the difference between the real + // clock, and the previous value on the fake clock. + milliseconds = total_real - fake_clock; + fake_clock += milliseconds; + } + if (callback_ != nullptr) callback_->ElapseTime(milliseconds); +} + +void TestSleep::SyncFakeClock() { + // Syncing can be done by sleeping 0 seconds. + Sleep(0); +} + +bool TestSleep::RollbackSystemTime(int seconds) { + if (real_sleep_) { +#ifdef _WIN32 + // See remarks from this for why this series is used. + // https://msdn.microsoft.com/en-us/f77cdf86-0f97-4a89-b565-95b46fa7d65b + SYSTEMTIME time; + GetSystemTime(&time); + FILETIME file_time; + if (!SystemTimeToFileTime(time, &file_time)) return false; + uint64_t long_time = + static_cast(file_time.dwLowDateTime) | + (static_cast(file_time.dwHighDateTime) << 32); + long_time += static_cast(delta_seconds) * + 1e7; // long_time is in 100-nanosecond intervals. + file_time.dwLowDateTime = long_time & ((1ull << 32) - 1); + file_time.dwHighDateTime = long_time >> 32; + if (!FileTimeToSystemTime(&file_time, &time)) return false; + if (!SetSystemTime(&time)) return false; +#else + auto time = std::chrono::system_clock::now(); + auto modified_time = time - std::chrono::seconds(seconds); + ; + timespec time_spec; + time_spec.tv_sec = std::chrono::duration_cast( + modified_time.time_since_epoch()) + .count(); + time_spec.tv_nsec = std::chrono::duration_cast( + modified_time.time_since_epoch()) + .count() % + (1000 * 1000 * 1000); + if (clock_settime(CLOCK_REALTIME, &time_spec)) { + LOGE("Error setting clock: %s", strerror(errno)); + return false; + } +#endif + } // end if(real_sleep_)... + + // For both real and fake sleep we still update the callback and we still keep + // track of the total amount of time slept. + total_clock_rollback_ += seconds; + if (callback_ != nullptr) callback_->ElapseTime(-1000 * seconds); + return true; +} + +bool TestSleep::CanChangeSystemTime() { + // If we are using a fake clock, then we can move the clock backwards by + // just going backwards. + // ElapseTime. + if (!real_sleep_) { + return true; + } +#ifdef _WIN32 + LUID desired_id; + if (!LookupPrivilegeValue(nullptr, SE_SYSTEMTIME_NAME, &desired_id)) { + LOGE("Win32 time rollback: no SYSTEMTIME permission."); + return false; + } + HANDLE token; + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &token)) { + LOGE("Win32 time rollback: cannot access process token."); + return false; + } + std::unique_ptr safe_token(token, &CloseHandle); + + // This queries all the permissions given to the token to determine if we can + // change the system time. Note this is subtly different from PrivilegeCheck + // as that only checks "enabled" privileges; even with admin rights, the + // privilege is default disabled, even when granted. + + DWORD size = 0; + // Determine how big we need to allocate first. + GetTokenInformation(token, TokenPrivileges, nullptr, 0, &size); + // Since TOKEN_PRIVILEGES uses a variable-length array, we need to use malloc + std::unique_ptr privileges( + (TOKEN_PRIVILEGES*)malloc(size), &free); + if (privileges && GetTokenInformation(token, TokenPrivileges, + privileges.get(), size, &size)) { + for (int i = 0; i < privileges->PrivilegeCount; i++) { + if (privileges->Privileges[i].Luid.HighPart == desired_id.HighPart && + privileges->Privileges[i].Luid.LowPart == desired_id.LowPart) { + return true; + } + } + } + LOGE("Win32 time rollback: cannot set system time."); + return false; +#else + // Otherwise, the test needs to be run as root. + const uid_t uid = getuid(); + if (uid == 0) return true; + LOGE("Unix time rollback: not running as root (uid=%u.", uid); + return false; +#endif +} +} // namespace wvutil diff --git a/oemcrypto/test/test_sleep.h b/oemcrypto/test/test_sleep.h new file mode 100644 index 0000000..8c4d2cc --- /dev/null +++ b/oemcrypto/test/test_sleep.h @@ -0,0 +1,75 @@ +// 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. +// +// TestSleep - Controls sleep and clock adjustment during tests. +// +#ifndef WVCAS_UTIL_TEST_SLEEP_H_ +#define WVCAS_UTIL_TEST_SLEEP_H_ + +#include + +namespace wvutil { + +class TestSleep { + public: + // The callback is called when the clock should be advanced. + class CallBack { + public: + virtual void ElapseTime(int64_t milliseconds) = 0; + + protected: + virtual ~CallBack(){}; + }; + + // If real_sleep_ is true, then this sleeps for |seconds| of time. If + // real_sleep_ is false, then the fake clock is advanced by |seconds|. If the + // callback exists, this calls the callback. + static void Sleep(unsigned int seconds); + + // If we are using a real clock and a fake clock, then the real clock advances + // a little while we are doing work, but the fake one only advances when we + // sleep. This function advances the fake clock to be in sync with the real + // clock. This function should be called to prevent a slow flaky test from + // failing due to this drift. + static void SyncFakeClock(); + + // Roll the system clock back by |seconds|. Returns true on success. A well + // mannered test will call CanChangeSystemTime before attempting to call this + // function and then assert that this is true. This function should *NOT* roll + // back the clock used by OEMCrypto -- in fact, there are several tests that + // verify this function does not roll back the clock used by OEMCrypto. + static bool RollbackSystemTime(int seconds); + + // Roll the system clock forward to undo all previous calls to + // RollBackSystemTime. Returns true on success. + static bool ResetRollback() { + return total_clock_rollback_ == 0 || + RollbackSystemTime(-total_clock_rollback_); + } + + // Returns true if the system time can be rolled back. This is true on some + // devices if the tests are run as root. It is also true when using a fake + // clock with the reference version of OEMCrypto. This function is about the + // system clock, *NOT* the clock used by OEMCrypto. + static bool CanChangeSystemTime(); + + static void set_real_sleep(bool real_sleep) { real_sleep_ = real_sleep; } + static bool real_sleep() { return real_sleep_; } + + // The callback is notified whenever sleep is called. + static void set_callback(CallBack* callback) { callback_ = callback; } + + private: + // Controls if the test sleep should use real sleep. + static bool real_sleep_; + // Called when the clock should advance. + static CallBack* callback_; + // The sum of all calls to RollBackSystemTime. Kept so we can undo all changes + // at the end of a test. + static int total_clock_rollback_; +}; + +} // namespace wvutil + +#endif // WVCAS_UTIL_TEST_SLEEP_H_ diff --git a/oemcrypto/test/wvcrc.cpp b/oemcrypto/test/wvcrc.cpp new file mode 100644 index 0000000..3336c77 --- /dev/null +++ b/oemcrypto/test/wvcrc.cpp @@ -0,0 +1,108 @@ +// 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. +// +// Compute CRC32 Checksum. Needed for verification of WV Keybox. +// +#include "platform.h" +#include "wvcrc32.h" + +namespace wvoec { + +#define INIT_CRC32 0xffffffff + +uint32_t wvrunningcrc32(const uint8_t* p_begin, int i_count, uint32_t i_crc) { + static uint32_t CRC32[256] = { + 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, + 0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005, + 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, + 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, + 0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, + 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75, + 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, + 0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd, + 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, + 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, + 0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81, + 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d, + 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, + 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95, + 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, + 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, + 0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae, + 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072, + 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, + 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca, + 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, + 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, + 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066, + 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, + 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, + 0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692, + 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, + 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, + 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e, + 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, + 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, + 0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a, + 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, + 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, + 0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, + 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53, + 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, + 0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b, + 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, + 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, + 0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, + 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b, + 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, + 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3, + 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, + 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, + 0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f, + 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3, + 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, + 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c, + 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, + 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, + 0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30, + 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, + 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, + 0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654, + 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, + 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, + 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18, + 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, + 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, + 0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c, + 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, + 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4 + }; + + /* Calculate the CRC */ + while (i_count > 0) { + i_crc = (i_crc << 8) ^ CRC32[(i_crc >> 24) ^ ((uint32_t) * p_begin)]; + p_begin++; + i_count--; + } + + return(i_crc); +} + +uint32_t wvcrc32(const uint8_t* p_begin, int i_count) { + return(wvrunningcrc32(p_begin, i_count, INIT_CRC32)); +} + +uint32_t wvcrc32Init() { + return INIT_CRC32; +} + +uint32_t wvcrc32Cont(const uint8_t* p_begin, int i_count, uint32_t prev_crc) { + return(wvrunningcrc32(p_begin, i_count, prev_crc)); +} + +uint32_t wvcrc32n(const uint8_t* p_begin, int i_count) { + return htonl(wvrunningcrc32(p_begin, i_count, INIT_CRC32)); +} + +} // namespace wvoec diff --git a/oemcrypto/test/wvcrc32.h b/oemcrypto/test/wvcrc32.h new file mode 100644 index 0000000..2436211 --- /dev/null +++ b/oemcrypto/test/wvcrc32.h @@ -0,0 +1,23 @@ +// 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. +// +// Compute CRC32 Checksum. Needed for verification of WV Keybox. +// +#ifndef CDM_WVCRC32_H_ +#define CDM_WVCRC32_H_ + +#include + +namespace wvoec { + +uint32_t wvcrc32(const uint8_t* p_begin, int i_count); +uint32_t wvcrc32Init(); +uint32_t wvcrc32Cont(const uint8_t* p_begin, int i_count, uint32_t prev_crc); + +// Convert to network byte order +uint32_t wvcrc32n(const uint8_t* p_begin, int i_count); + +} // namespace wvoec + +#endif // CDM_WVCRC32_H_ diff --git a/plugin/Android.bp b/plugin/Android.bp new file mode 100644 index 0000000..b3ea5e1 --- /dev/null +++ b/plugin/Android.bp @@ -0,0 +1,33 @@ +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/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..6a2a4fa --- /dev/null +++ b/plugin/include/cas_events.h @@ -0,0 +1,56 @@ +// 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_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, + + // 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, + + SET_PARENTAL_CONTROL_AGE = CAS_PARENTAL_CONTROL_EVENT_START, + DEPRECATED_PARENTAL_CONTROL_AGE_UPDATED, + ACCESS_DENIED_BY_PARENTAL_CONTROL, + AGE_RESTRICTION_UPDATED, + + TEST_FOR_ECHO = + CAS_TEST_EVENT_START, // Request an ECHO response to test events passing. + ECHO, // Respond to TEST_FOR_ECHO. +} CasEventId; + +#endif // CAS_EVENTS_H diff --git a/plugin/include/cas_license.h b/plugin/include/cas_license.h new file mode 100644 index 0000000..e4e2c1f --- /dev/null +++ b/plugin/include/cas_license.h @@ -0,0 +1,150 @@ +// 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; + + // 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; + + // Query to see if the license is expired. + virtual bool IsExpired() const; + + // Notify the license that playback decryption has begun. + virtual void BeginDecryption(); + + 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_; +}; + +} // 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..23b557f --- /dev/null +++ b/plugin/include/cas_media_id.h @@ -0,0 +1,24 @@ +#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 const int group_ids_size() = 0; + virtual const std::string group_id() = 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..fc68dc2 --- /dev/null +++ b/plugin/include/cas_status.h @@ -0,0 +1,52 @@ +// 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, +}; + +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..111f623 --- /dev/null +++ b/plugin/include/cas_types.h @@ -0,0 +1,98 @@ +// 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 int32_t 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, +}; + +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; + + 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..72bcf7c --- /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_key.h b/plugin/include/crypto_key.h new file mode 100644 index 0000000..d266ff0 --- /dev/null +++ b/plugin/include/crypto_key.h @@ -0,0 +1,50 @@ +// 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_KEY_H_ +#define CRYPTO_KEY_H_ + +namespace wvcas { + +class CryptoKey { + public: + CryptoKey() {}; + ~CryptoKey() {}; + + const std::string& key_id() const { return key_id_; } + const std::string& key_data() const { return key_data_; } + const std::string& key_data_iv() const { return key_data_iv_; } + const std::string& key_control() const { return key_control_; } + const std::string& key_control_iv() const { return key_control_iv_; } + const std::string& entitlement_key_id() const {return entitlement_key_id_;} + const std::string& track_label() const { return track_label_; } + void set_key_id(const std::string& key_id) { key_id_ = key_id; } + void set_key_data(const std::string& key_data) { key_data_ = key_data; } + void set_key_data_iv(const std::string& iv) { key_data_iv_ = iv; } + void set_key_control(const std::string& ctl) { key_control_ = ctl; } + void set_key_control_iv(const std::string& ctl_iv) { + key_control_iv_ = ctl_iv; + } + void set_track_label(const std::string& track_label) { + track_label_ = track_label; + } + void set_entitlement_key_id(const std::string& entitlement_key_id) { + entitlement_key_id_ = entitlement_key_id; + } + + bool HasKeyControl() const { return key_control_.size() >= 16; } + + private: + std::string key_id_; + std::string key_data_iv_; + std::string key_data_; + std::string key_control_; + std::string key_control_iv_; + std::string track_label_; + std::string entitlement_key_id_; +}; + +} // namespace wvcas + +#endif // CRYPTO_KEY_H_ diff --git a/plugin/include/crypto_session.h b/plugin/include/crypto_session.h new file mode 100644 index 0000000..9caaed6 --- /dev/null +++ b/plugin/include/crypto_session.h @@ -0,0 +1,325 @@ +// 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 "OEMCryptoCAS.h" +#include "cas_status.h" +#include "cas_types.h" +#include "crypto_key.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 shared_mutex static_field_mutex_; + static 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_LoadKeys( + const LoadKeysParams& load_key_params); + 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_EntitledCasKeyObject* even_key, + const OEMCrypto_EntitledCasKeyObject* odd_key); + virtual OEMCryptoResult OEMCrypto_SelectKey( + OEMCrypto_SESSION session, const uint8_t* content_key_id, + size_t content_key_id_length, OEMCryptoCipherMode cipher_mode); + virtual OEMCryptoResult OEMCrypto_GetHDCPCapability( + OEMCrypto_HDCP_Capability* current, OEMCrypto_HDCP_Capability* max); + virtual 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); + 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); + + // 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); + 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); + 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 LoadKeys(const std::string& message, + const std::string& signature, + const std::string& mac_key_iv, + const std::string& mac_key, + const std::vector& key_array, + const std::string& pst, + const std::string& srm_requirement); + 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 CasStatus SelectKey(OEMCrypto_SESSION session, + const std::vector& key_id, + CryptoMode crypto_mode); + virtual bool GetHdcpCapabilities(HdcpCapability* current, + HdcpCapability* max); + virtual CasStatus RefreshKeys(const std::string& message, + const std::string& signature, + const std::vector& key_array); + 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); + + 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_; + // Map from entitled key session to current_key_id. Used to avoid repeated + // call to SelectKey(). + std::unordered_map> current_key_id_; + // Map from entitled key session to current_crypto_mode. Used to avoid + // repeated call to SelectKey(). + std::unordered_map current_crypto_mode_; + size_t max_buffer_size_{std::numeric_limits::max()}; +}; + +} // 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..17795b4 --- /dev/null +++ b/plugin/include/ecm_parser.h @@ -0,0 +1,66 @@ +// 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" + +namespace wvcas { + +enum class KeySlotId { kEvenKeySlot, kOddKeySlot }; + +struct EcmKeyData; + +// EcmParser allows random access to the fields of an ECM. +// The only validation performed is to ensure that the ecm +// passed in is large enough to hold a single key entry. +class EcmParser { + protected: + EcmParser() {} + + public: + virtual ~EcmParser() {} + + // The EcmParser factory method. + // Validates the ecm. If validations is successful returns true and constructs + // an EcmParser in |parser| using |ecm|. + static bool create(const CasEcm& ecm, + std::unique_ptr* parser); + + // Accessor methods. + virtual uint8_t version() const; + virtual uint8_t sequence_count() const; + virtual CryptoMode crypto_mode() const; + virtual bool rotation_enabled() const; + virtual size_t content_iv_size() const; + virtual uint8_t age_restriction() const; + virtual const std::vector entitlement_key_id(KeySlotId id) const; + virtual const std::vector content_key_id(KeySlotId id) const; + virtual const std::vector wrapped_key_data(KeySlotId id) const; + virtual const std::vector wrapped_key_iv(KeySlotId id) const; + virtual const std::vector content_iv(KeySlotId id) const; + + EcmParser(const EcmParser&) = delete; + EcmParser& operator=(const EcmParser&) = delete; + + private: + // Constructs an EcmParser using |ecm|. + explicit EcmParser(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_H 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..37919c6 --- /dev/null +++ b/plugin/include/oemcrypto_interface.h @@ -0,0 +1,156 @@ +// 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 "OEMCryptoCAS.h" +#include "crypto_key.h" + +namespace wvcas { + +// LoadKeysParams mirrors the parameters in the OEMCrypto API. It's purpose is +// to allow OEMCrypto_LoadKeys to be mocked. OEMCrypto_LoadKeys takes 13 +// parameters as of API V14. GoogleMock allows a maximum of 10. +struct LoadKeysParams { + OEMCrypto_SESSION session = 0; + const uint8_t* message = nullptr; + size_t message_length = 0; + const uint8_t* signature = nullptr; + size_t signature_length = 0; + OEMCrypto_Substring enc_mac_keys_iv = {}; + OEMCrypto_Substring enc_mac_keys = {}; + size_t num_keys = 0; + const OEMCrypto_KeyObject* key_array = nullptr; + OEMCrypto_Substring pst = {}; + OEMCrypto_Substring srm_requirement = {}; + OEMCrypto_LicenseType license_type = OEMCrypto_ContentLicense; +}; + +// 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_LoadKeys( + const LoadKeysParams& load_key_params) 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_EntitledCasKeyObject* even_key, + const OEMCrypto_EntitledCasKeyObject* odd_key) const; + virtual OEMCryptoResult OEMCrypto_SelectKey( + OEMCrypto_SESSION session, const uint8_t* content_key_id, + size_t content_key_id_length, OEMCryptoCipherMode cipher_mode) const; + virtual OEMCryptoResult OEMCrypto_GetHDCPCapability( + OEMCrypto_HDCP_Capability* current, OEMCrypto_HDCP_Capability* max) const; + virtual 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); + virtual OEMCryptoResult OEMCrypto_GetDeviceID(uint8_t* deviceID, + size_t* idLength); + virtual OEMCryptoResult OEMCrypto_LoadTestKeybox(const uint8_t* buffer, + size_t length); + virtual const char* OEMCrypto_SecurityLevel() const; + virtual OEMCryptoResult OEMCrypto_CreateEntitledKeySession( + OEMCrypto_SESSION oec_session, OEMCrypto_SESSION* key_session); + virtual OEMCryptoResult OEMCrypto_RemoveEntitledKeySession( + OEMCrypto_SESSION key_session); + + 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..d17d358 --- /dev/null +++ b/plugin/include/widevine_cas_api.h @@ -0,0 +1,133 @@ +// 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 "cas_license.h" +#include "cas_media_id.h" +#include "cas_status.h" +#include "cas_types.h" +#include "crypto_session.h" +#include "file_store.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(WvCasSessionId sessionId); + + // Process an ECM from the ECM stream for this session’s elementary + // stream. + virtual CasStatus processEcm(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); + + // Processes the entitlement |response| to a entitlement license request. + virtual CasStatus handleEntitlementResponse( + const std::string& response, std::string& license_id); + + // 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); + + // 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(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 in used license. If successful content id is returned. + virtual CasStatus RemoveLicense(const std::string file_name); + + void OnTimerEvent() override; + + private: + virtual CasStatus HandleStoredDrmCert(const std::string& certificate); + virtual CasStatus HandleProcessEcm(const WvCasSessionId& sessionId, + const CasEcm& ecm); + virtual CasStatus HandleDeferredECMs(); + + virtual std::shared_ptr getCryptoSession(); + virtual std::unique_ptr getCasLicense(); + virtual std::unique_ptr getFileSystem(); + virtual std::shared_ptr newCasSession(); + + // 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; +}; // 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..05e3300 --- /dev/null +++ b/plugin/include/widevine_cas_session.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 Master +// License Agreement. + +#ifndef WIDEVINE_CAS_SESSION_H +#define WIDEVINE_CAS_SESSION_H + +#include +#include +#include + +#include "cas_types.h" +#include "crypto_session.h" +#include "ecm_parser.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, + uint32_t* session_id); + + // Get the current key information. This method will be used by a descrambler + // plugin to obtain the current key information. + const KeySlot& key(KeySlotId slot_id) const; + + // 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. + virtual CasStatus processEcm(const CasEcm& ecm, uint8_t parental_control_age); + + // Returns the security level retrieved from OEMCrypto. + const char* securityLevel(); + + // Returns current ecm age restriction value. + uint8_t GetEcmAgeRestriction() { return ecm_age_restriction_; } + + 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 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_; +}; + +} // 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..7abc68a --- /dev/null +++ b/plugin/include/widevine_cas_session_map.h @@ -0,0 +1,52 @@ +// 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(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(WvCasSessionId cas_session_id) const; + // Remove an entry in the map. + void RemoveSession(WvCasSessionId cas_session_id); + + // 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..86cab4d --- /dev/null +++ b/plugin/include/widevine_media_cas_plugin.h @@ -0,0 +1,140 @@ +// 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::status_t; +using android::CasSessionId; +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 {} + + virtual status_t initialize(); + + // Returns true if the device has been provisioned with a device certificate. + bool is_provisioned(); + + // 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; + + private: + virtual std::shared_ptr getCryptoSession(); + // |sessionId| is nullptr if the event is not a session event. + virtual CasStatus processEvent(int32_t event, + int32_t arg, + const CasData& eventData, + const CasSessionId* sessionId); + virtual CasStatus HandleIndividualizationResponse(const CasData& response); + virtual CasStatus HandleEntitlementResponse(const CasData& response); + virtual status_t requestLicense(const std::string& init_data); + virtual CasStatus HandleEntitlementRenewalResponse(const CasData& response); + virtual CasStatus HandleUniqueIDQuery(); + virtual CasStatus HandleSetParentalControlAge(const CasData& data); + virtual CasStatus HandleLicenseRemoval(const CasData& license_id); + + // 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; + + // Choose to use |callback_| or |callback_ext_| to send back information. + // |sessionId| is ignored if |callback_ext_| is null, + 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; + std::string provision_data_; + WidevineCas widevine_cas_; +}; + +} // 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..8bc9df0 --- /dev/null +++ b/plugin/src/cas_license.cpp @@ -0,0 +1,1012 @@ +// 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 +#include + +#include "cas_license.h" +#include "cas_properties.h" +#include "cas_util.h" +#include "crypto_key.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::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 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 { + +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"; + +// 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; +} + +} // namespace + +std::vector ExtractEntitlementKeys(const License& license) { + std::vector key_array; + key_array.reserve(license.key_size()); + // Extract entitlement type key(s). + for (const auto& license_key : license.key()) { + if (license_key.type() == License_KeyContainer::ENTITLEMENT) { + key_array.emplace_back(); + auto& key = key_array.back(); + // Strip off PKCS#5 padding - since we know the key is 32 or 48 bytes, + // the padding will always be 16 bytes. + size_t length = 0; + if (license_key.key().size() > 32) { + length = license_key.key().size() - 16; + } + key.set_key_data(license_key.key().substr(0, length)); + key.set_key_data_iv(license_key.iv()); + key.set_key_id(license_key.id()); + key.set_track_label(license_key.track_label()); + if (license_key.has_key_control()) { + key.set_key_control(license_key.key_control().key_control_block()); + key.set_key_control_iv(license_key.key_control().iv()); + } + } + } + return key_array; +} + +std::vector ExtractKeyControlKeys(const License& license) { + std::vector key_array; + key_array.reserve(license.key_size()); + // Extract key control type key(s). + for (const auto& license_key : license.key()) { + if (license_key.type() == License_KeyContainer::KEY_CONTROL) { + key_array.emplace_back(); + auto& key = key_array.back(); + if (license_key.has_key_control()) { + key.set_key_control(license_key.key_control().key_control_block()); + key.set_key_control_iv(license_key.key_control().iv()); + } + key.set_track_label(license_key.track_label()); + } + } + return key_array; +} + +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; +} + +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()); + + 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); + } + + 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? + + // TODO(jfore): Read from oemcrypto and fail if the version is < 14. + client_capabilities->set_oem_crypto_api_version(14); + + // 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; + status = crypto_session_->PrepareAndSignLicenseRequest( + 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"); + 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); + 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::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; + status = crypto_session_->PrepareAndSignProvisioningRequest( + *serialized_message, &core_message, &provisioning_request_signature); + 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); + 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()); + // Strip off PKCS#5 padding + mac_key_str.assign(license.key(i).key().data(), kMacKeySizeBytes); + } + } + if (license.policy().can_renew() || + (mac_key_iv_str.size() != 0 || mac_key_str.size() != 0)) { + if (mac_key_iv_str.size() != kIvSizeBytes || + mac_key_str.size() != kMacKeySizeBytes) { + std::ostringstream err_string; + err_string << "mac key/iv size error (key/iv size expected: " + << kMacKeySizeBytes << "/" << kIvSizeBytes + << " actual: " << mac_key_str.size() << "/" + << mac_key_iv_str.size(); + return CasStatus(CasStatusCode::kCryptoSessionError, err_string.str()); + } + } + + std::vector key_array = ExtractEntitlementKeys(license); + if (key_array.empty()) { + return CasStatus(CasStatusCode::kCasLicenseError, + "the entitlement contains no keys"); + } + + 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(); +} + +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); + } +} +} // namespace wvcas diff --git a/plugin/src/crypto_session.cpp b/plugin/src/crypto_session.cpp new file mode 100644 index 0000000..65afda8 --- /dev/null +++ b/plugin/src/crypto_session.cpp @@ -0,0 +1,1183 @@ +// 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 { + +// Find the offset of the |field| within |message|. Notice that if |field| is +// empty, 0 will be returned as |pos|. +size_t GetOffset(const std::string& message, const std::string& field) { + size_t pos = message.find(field); + if (pos == std::string::npos) { + LOGE("GetOffset : Cannot find offset for %s", field.c_str()); + pos = 0; + } + return pos; +} + +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; + 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_EntitledCasKeyObject* 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); +} + +// Increment the IV based on the number of encrypted block. IV size is assumed +// to be 16 bytes. +static constexpr uint32_t kIvSizeBytesBytes = 16; +static constexpr uint32_t kCencIvSize = 8; +static const uint32_t kAesBlockSizeBytes = 16; +bool Ctr128Add(size_t block_count, uint8_t* counter) { + if (counter == nullptr) return false; + if (0 == block_count) return true; + + uint8_t carry = 0; + uint8_t n = kIvSizeBytesBytes - 1; + // Update the counter one byte at a time. + while (n >= kCencIvSize) { + // Grab a single byte of the block_count. + uint32_t temp = block_count & 0xff; + // Add the corresponding byte from the counter value. + temp += counter[n]; + // Add in the carry. + temp += carry; + // Write back the updated counter byte and set the carry value as needed. + counter[n] = temp & 0xff; + carry = (temp & 0x100) ? 1 : 0; + // Update block_count and set the counter index for the next byte. + block_count = block_count >> 8; + n--; + // Early exit if nothing to do. + if (!block_count && !carry) { + break; + } + } + return true; +} + +} // namespace + +shared_mutex CryptoLock::static_field_mutex_; +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); + 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); + 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); + 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_LoadKeys( + const LoadKeysParams& load_key_params) { + return lock_->WithOecSessionLock("LoadKeys", [&] { + return oemcrypto_interface_->OEMCrypto_LoadKeys(load_key_params); + }); +} + +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_EntitledCasKeyObject* even_key, + const OEMCrypto_EntitledCasKeyObject* odd_key) { + return lock_->WithOecSessionLock("LoadCasECMKeys", [&] { + return oemcrypto_interface_->OEMCrypto_LoadCasECMKeys( + session, message, message_length, even_key, odd_key); + }); +} + +OEMCryptoResult CryptoInterface::OEMCrypto_SelectKey( + OEMCrypto_SESSION session, const uint8_t* content_key_id, + size_t content_key_id_length, OEMCryptoCipherMode cipher_mode) { + return lock_->WithOecSessionLock("SelectKey", [&] { + return oemcrypto_interface_->OEMCrypto_SelectKey( + session, content_key_id, content_key_id_length, cipher_mode); + }); +} + +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_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) { + return lock_->WithOecSessionLock("RefreshKeys", [&] { + return oemcrypto_interface_->OEMCrypto_RefreshKeys( + session, message, message_length, signature, signature_length, num_keys, + key_array); + }); +} + +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::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() {} + +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) { + 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_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) { + 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_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::LoadKeys(const std::string& message, + const std::string& signature, + const std::string& mac_key_iv, + const std::string& mac_key, + const std::vector& key_array, + const std::string& pst, + const std::string& srm_requirement) { + if (key_array.empty()) { + return CasStatus::OkStatus(); + } + + LoadKeysParams load_key_params; + load_key_params.session = session_; + load_key_params.message = reinterpret_cast(message.data()); + load_key_params.message_length = message.size(); + load_key_params.signature = + reinterpret_cast(signature.data()); + load_key_params.signature_length = signature.size(); + load_key_params.enc_mac_keys_iv.offset = GetOffset(message, mac_key_iv); + load_key_params.enc_mac_keys_iv.length = mac_key_iv.size(); + load_key_params.enc_mac_keys.offset = GetOffset(message, mac_key); + load_key_params.enc_mac_keys.length = mac_key.size(); + load_key_params.pst.offset = GetOffset(message, pst); + load_key_params.pst.length = pst.size(); + load_key_params.srm_requirement.offset = GetOffset(message, srm_requirement); + load_key_params.srm_requirement.length = srm_requirement.size(); + std::vector key_objects; + key_objects.reserve(key_array.size()); + for (const auto& ki : key_array) { + key_objects.resize(key_objects.size() + 1); + OEMCrypto_KeyObject& ko = key_objects.back(); + ko.key_id.offset = GetOffset(message, ki.key_id()); + ko.key_id.length = ki.key_id().length(); + ko.key_data_iv.offset = GetOffset(message, ki.key_data_iv()); + ko.key_data_iv.length = ki.key_data_iv().length(); + ko.key_data.offset = GetOffset(message, ki.key_data()); + ko.key_data.length = ki.key_data().length(); + if (ki.HasKeyControl()) { + ko.key_control_iv.offset = GetOffset(message, ki.key_control_iv()); + ko.key_control_iv.length = ki.key_control_iv().length(); + ko.key_control.offset = GetOffset(message, ki.key_control()); + ko.key_control.length = ki.key_control().length(); + } + } + load_key_params.key_array = &key_objects[0]; + load_key_params.num_keys = key_objects.size(); + load_key_params.license_type = OEMCrypto_EntitlementLicense; + + OEMCryptoResult result = + crypto_interface_->OEMCrypto_LoadKeys(load_key_params); + if (result != OEMCrypto_SUCCESS) { + std::ostringstream err_string; + err_string << "OEMCrypto_LoadKeys 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_EntitledCasKeyObject even_ecko; + OEMCrypto_EntitledCasKeyObject 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(); +} + +CasStatus CryptoSession::SelectKey(OEMCrypto_SESSION session, + const std::vector& key_id, + CryptoMode crypto_mode) { + if (!crypto_interface_) { + return CasStatus(CasStatusCode::kCryptoSessionError, + "missing crypto interface"); + } + if (current_key_id_.find(session) != current_key_id_.end() && + key_id == current_key_id_.at(session) && + current_crypto_mode_.find(session) != current_crypto_mode_.end() && + crypto_mode == current_crypto_mode_.at(session)) { + return CasStatus::OkStatus(); + } + + OEMCryptoCipherMode oem_crypto_cipher_mode; + switch (crypto_mode) { + case CryptoMode::kAesCBC: + oem_crypto_cipher_mode = OEMCrypto_CipherMode_CBC; + break; + case CryptoMode::kAesCTR: + oem_crypto_cipher_mode = OEMCrypto_CipherMode_CTR; + break; + case CryptoMode::kDvbCsa2: + oem_crypto_cipher_mode = OEMCrypto_CipherMode_CSA2; + break; + case CryptoMode::kDvbCsa3: + oem_crypto_cipher_mode = OEMCrypto_CipherMode_CSA3; + break; + case CryptoMode::kAesOFB: + oem_crypto_cipher_mode = OEMCrypto_CipherMode_OFB; + break; + case CryptoMode::kAesSCTE: + oem_crypto_cipher_mode = OEMCrypto_CipherMode_SCTE; + break; + default: + std::ostringstream err_string; + err_string << "unsupported crypto mode " << static_cast(crypto_mode); + return CasStatus(CasStatusCode::kCryptoSessionError, err_string.str()); + } + + OEMCryptoResult result = crypto_interface_->OEMCrypto_SelectKey( + session, key_id.data(), key_id.size(), oem_crypto_cipher_mode); + if (result != OEMCrypto_SUCCESS) { + std::ostringstream err_string; + err_string << "OEMCrypto_SelectKey returned " << result; + return CasStatus(CasStatusCode::kCryptoSessionError, err_string.str()); + } + + current_key_id_[session] = key_id; + current_crypto_mode_[session] = crypto_mode; + 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::RefreshKeys(const std::string& message, + const std::string& signature, + const std::vector& key_array) { + if (key_array.empty()) { + return CasStatus::OkStatus(); + } + // Assume the message and signature parameters are valid. + const uint8_t* message_ptr = reinterpret_cast(message.data()); + const uint8_t* signature_ptr = + reinterpret_cast(signature.data()); + std::vector load_key_array; + load_key_array.reserve(key_array.size()); + for (const auto& key : key_array) { + load_key_array.emplace_back(); + auto& key_object = load_key_array.back(); + key_object.key_id.offset = GetOffset(message, key.key_id()); + key_object.key_id.length = key.key_id().length(); + if (!key.key_control().empty()) { + key_object.key_control_iv.offset = + GetOffset(message, key.key_control_iv()); + key_object.key_control.length = key.key_control().length(); + } + } + OEMCryptoResult result = crypto_interface_->OEMCrypto_RefreshKeys( + session_, message_ptr, message.size(), signature_ptr, signature.size(), + load_key_array.size(), &load_key_array[0]); + if (result != OEMCrypto_SUCCESS) { + std::ostringstream err_string; + err_string << "OEMCrypto_RefreshKeys returned " << result; + return CasStatus(CasStatusCode::kCryptoSessionError, err_string.str()); + } + return CasStatus::OkStatus(); +} + +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(); +} + +} // namespace wvcas diff --git a/plugin/src/device_files.cpp b/plugin/src/device_files.cpp new file mode 100644 index 0000000..e69de29 diff --git a/plugin/src/ecm_parser.cpp b/plugin/src/ecm_parser.cpp new file mode 100644 index 0000000..d99b538 --- /dev/null +++ b/plugin/src/ecm_parser.cpp @@ -0,0 +1,221 @@ +// 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 + +namespace wvcas { + +namespace { + +// ECM constants +static constexpr uint8_t kSequenceCountMask = 0xF8; // Mode bits 3..7 +static constexpr uint8_t kCryptoModeFlags = (0x3 << 1); // Mode bits 1..2 +static constexpr uint8_t kCryptoModeFlagsV2 = (0xF << 1); // Mode bits 1..4 +static constexpr uint8_t kRotationFlag = (0x1 << 0); // Mode bit 0 +static constexpr uint8_t kContentIVSizeFlag = (0x1 << 6); +static constexpr uint8_t kAgeRestrictionMask = (0x1F << 1); + +static constexpr size_t kEntitlementKeyIDSizeBytes = 16; +static constexpr size_t kContentKeyIDSizeBytes = 16; +static constexpr size_t kContentKeyDataSizeBytes = 16; +static constexpr size_t kWrappedKeyIVSizeBytes = 16; +// Size is either 8 or 16 bytes, depending on ContentIVSize flag. +static constexpr size_t kContentKeyMaxIVSizeBytes = 16; +static constexpr uint16_t kWidevineCaId = 0x4AD4; + +// 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. +static constexpr uint16_t kSectionHeader1 = 0x80; +static constexpr uint16_t kSectionHeader2 = 0x81; + +static 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. + +EcmParser::EcmParser(const CasEcm& ecm) : ecm_(ecm) {} + +size_t EcmParser::key_data_size() const { + return sizeof(EcmKeyData) + content_iv_size() - kContentKeyMaxIVSizeBytes; +} + +bool EcmParser::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* EcmParser::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 EcmParser::create(const CasEcm& cas_ecm, + std::unique_ptr* parser) { + if (nullptr == parser || (cas_ecm.size() < sizeof(EcmDescriptor))) { + return false; + } + // Detect and strip optional section header. + // Use the index of kWidevineCaId to identify the header size. + const CasEcm::const_iterator loc_WVCaId_firstByte = + std::find(cas_ecm.begin(), cas_ecm.end(), kWidevineCaId >> 8); + const CasEcm::const_iterator loc_WVCaId_secondByte = + std::find(cas_ecm.begin(), cas_ecm.end(), kWidevineCaId & 0xFF); + + if (loc_WVCaId_firstByte >= cas_ecm.end() - 1 || + loc_WVCaId_firstByte + 1 != loc_WVCaId_secondByte) { + return false; + } + const CasEcm& ecm = std::vector(loc_WVCaId_firstByte, cas_ecm.end()); + if (ecm.size() < sizeof(EcmDescriptor)) { + return false; + } + //reconfirm ecm data should start with kWidevineCaId + uint16_t ca_id_val = ntohs(*reinterpret_cast(ecm.data())); + if (ca_id_val != kWidevineCaId) { + return false; + } + // Using 'new' to access a non-public constructor. + std::unique_ptr new_parser = + std::unique_ptr(new EcmParser(ecm)); + if (!new_parser->is_valid_size()) { + return false; + } + *parser = std::move(new_parser); + return true; +} + +uint8_t EcmParser::version() const { + const EcmDescriptor* ecm = reinterpret_cast(&ecm_[0]); + return ecm->version; +} + +uint8_t EcmParser::sequence_count() const { + if (version() == 1) { + const EcmDescriptor* ecm = reinterpret_cast(&ecm_[0]); + return (ecm->flags_cipher_rotation & kSequenceCountMask) >> 3; + } + return 0; +} + +CryptoMode EcmParser::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 EcmParser::rotation_enabled() const { + const EcmDescriptor* ecm = reinterpret_cast(&ecm_[0]); + return (ecm->flags_cipher_rotation & kRotationFlag); +} + +size_t EcmParser::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 EcmParser::age_restriction() const { + if (version() == 1) { + return 0; + } + const EcmDescriptor* ecm = reinterpret_cast(&ecm_[0]); + return (ecm->flags_iv_age & kAgeRestrictionMask) >> 1; +} + +const std::vector EcmParser::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; +} + +const std::vector EcmParser::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; +} + +const std::vector EcmParser::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; +} + +const std::vector EcmParser::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; +} + +const std::vector EcmParser::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/license_key_status.cpp b/plugin/src/license_key_status.cpp new file mode 100644 index 0000000..701fcfa --- /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..3ba92cd --- /dev/null +++ b/plugin/src/oemcrypto_interface.cpp @@ -0,0 +1,411 @@ +// 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 (*LoadKeys_t)( + OEMCrypto_SESSION, const uint8_t*, size_t, const uint8_t*, size_t, + OEMCrypto_Substring, OEMCrypto_Substring, size_t, + const OEMCrypto_KeyObject*, OEMCrypto_Substring, OEMCrypto_Substring, + OEMCrypto_LicenseType); + 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_EntitledCasKeyObject*, + const OEMCrypto_EntitledCasKeyObject*); + typedef OEMCryptoResult (*SelectKey_t)(OEMCrypto_SESSION, const uint8_t*, + size_t, OEMCryptoCipherMode); + typedef OEMCryptoResult (*GetHDCPCapability_t)(OEMCrypto_HDCP_Capability*, + OEMCrypto_HDCP_Capability*); + typedef OEMCryptoResult (*RefreshKeys_t)( + 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); + 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); + + 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; + LoadKeys_t LoadKeys = nullptr; + LoadLicense_t LoadLicense = nullptr; + LoadRenewal_t LoadRenewal = nullptr; + LoadCasECMKeys_t LoadCasECMKeys = nullptr; + SelectKey_t SelectKey = nullptr; + GetHDCPCapability_t GetHDCPCapability = nullptr; + RefreshKeys_t RefreshKeys = nullptr; + GetDeviceID_t GetDeviceID = nullptr; + LoadTestKeybox_t LoadTestKeybox = nullptr; + SecurityLevel_t SecurityLevel = nullptr; + CreateEntitledKeySession_t CreateEntitledKeySession = nullptr; + RemoveEntitledKeySession_t RemoveEntitledKeySession = 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(LoadKeys); + LOAD_SYM(LoadLicense); + LOAD_SYM(LoadRenewal); + LOAD_SYM(LoadCasECMKeys); + LOAD_SYM(SelectKey); + LOAD_SYM(GetHDCPCapability); + LOAD_SYM(RefreshKeys); + LOAD_SYM(GetDeviceID); + LOAD_SYM(SecurityLevel); + LOAD_SYM(CreateEntitledKeySession); + LOAD_SYM(RemoveEntitledKeySession); + + // 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_LoadKeys( + const LoadKeysParams& load_key_params) const { + return impl_->LoadKeys( + load_key_params.session, load_key_params.message, + load_key_params.message_length, load_key_params.signature, + load_key_params.signature_length, load_key_params.enc_mac_keys_iv, + load_key_params.enc_mac_keys, load_key_params.num_keys, + load_key_params.key_array, load_key_params.pst, + load_key_params.srm_requirement, load_key_params.license_type); +} + +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_EntitledCasKeyObject* even_key, + const OEMCrypto_EntitledCasKeyObject* odd_key) const { + return impl_->LoadCasECMKeys(session, message, message_length, even_key, + odd_key); +} + +OEMCryptoResult OEMCryptoInterface::OEMCrypto_SelectKey( + OEMCrypto_SESSION session, const uint8_t* content_key_id, + size_t content_key_id_length, OEMCryptoCipherMode cipher_mode) const { + return impl_->SelectKey(session, content_key_id, content_key_id_length, + cipher_mode); +} + +OEMCryptoResult OEMCryptoInterface::OEMCrypto_GetHDCPCapability( + OEMCrypto_HDCP_Capability* current, OEMCrypto_HDCP_Capability* max) const { + return impl_->GetHDCPCapability(current, max); +} + +OEMCryptoResult OEMCryptoInterface::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) { + return impl_->RefreshKeys(session, message, message_length, signature, + signature_length, num_keys, key_array); +} + +OEMCryptoResult OEMCryptoInterface::OEMCrypto_GetDeviceID(uint8_t* deviceID, + size_t* idLength) { + return impl_->GetDeviceID(deviceID, idLength); +} + +OEMCryptoResult OEMCryptoInterface::OEMCrypto_LoadTestKeybox( + const uint8_t* buffer, size_t length) { + // 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) { + return impl_->CreateEntitledKeySession(oec_session, key_session); +} + +OEMCryptoResult OEMCryptoInterface::OEMCrypto_RemoveEntitledKeySession( + OEMCrypto_SESSION key_session) { + return impl_->RemoveEntitledKeySession(key_session); +} + +} // namespace wvcas diff --git a/plugin/src/policy_engine.cpp b/plugin/src/policy_engine.cpp new file mode 100644 index 0000000..8f69bba --- /dev/null +++ b/plugin/src/policy_engine.cpp @@ -0,0 +1,366 @@ +// 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; + 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..3a5d5a0 --- /dev/null +++ b/plugin/src/widevine_cas_api.cpp @@ -0,0 +1,535 @@ +#include + +#include "ca_descriptor.pb.h" +#include "cas_events.h" +#include "cas_util.h" +#include "license_protocol.pb.h" +#include "log.h" +#include "string_conversions.h" +#include "widevine_cas_api.h" +#include "widevine_cas_session_map.h" + +static constexpr char kBasePathPrefix[] = "/data/vendor/mediacas/IDM/widevine/"; +static constexpr char kCertFileBase[] = "cert.bin"; + +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& id_data_from_media_id, + const std::string& provider_id) { + std::string data(id_data_from_media_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(".lic")); +} +} // 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(); } + const int group_ids_size() override { return pssh_.group_ids_size(); } + const std::string group_id() override { + if (group_ids_size() > 0) { + return pssh_.group_ids(0); + } + return ""; + } + + 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(); +} + +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; + if (media_id_->group_ids_size() > 0) { + filename = GenerateLicenseFilename(media_id_->group_id(), + media_id_->provider_id()); + } else { + filename = GenerateLicenseFilename(media_id_->content_id(), + media_id_->provider_id()); + } + 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_, reinterpret_cast(sessionId)); + if (CasStatusCode::kNoError != status.status_code()) { + return status; + } + WidevineCasSessionMap::instance().AddSession(*sessionId, session); + return CasStatusCode::kNoError; +} + +CasStatus WidevineCas::closeSession(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; +} + +// TODO(jfore): Add unit test to widevine_cas_api_test.cpp that is added in +// another cl. +CasStatus WidevineCas::processEcm(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_) { + 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_); + 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 (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) { + media_id_ = CasMediaId::create(); + CasStatus status = media_id_->initialize(init_data); + if (!status.ok()) { + return status; + } + std::string license_file; + std::string filename; + if (media_id_->group_ids_size() > 0) { + filename = GenerateLicenseFilename(media_id_->group_id(), + media_id_->provider_id()); + } else { + filename = GenerateLicenseFilename(media_id_->content_id(), + media_id_->provider_id()); + } + if (ReadFileFromStorage(*file_system_, filename, &license_file)) { + CasStatus status = + cas_license_->HandleStoredLicense(wrapped_rsa_key_, license_file); + if (status.ok()) { + // 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"); + } + policy_timer_.Start(this, 1); + has_license_ = true; + return HandleDeferredECMs(); + } + LOGI("Fallthru"); + } + + 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) { + if (response.empty()) { + return CasStatus(CasStatusCode::kCasLicenseError, + "empty entitlement response"); + } + 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. + has_license_ = true; + status = HandleDeferredECMs(); + if (!status.ok()) { + return status; + } + + policy_timer_.Start(this, 1); + + if (device_file.empty()) { + return status; + } + + if (!device_file.empty()) { + std::string filename; + if (media_id_->group_ids_size() > 0) { + filename = GenerateLicenseFilename(media_id_->group_id(), + media_id_->provider_id()); + } else { + 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() - std::string(".lic").size()); + } + } + 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()) { + std::string filename; + if (media_id_->group_ids_size() > 0) { + filename = GenerateLicenseFilename(media_id_->group_id(), + media_id_->provider_id()); + } else { + 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() - std::string(".lic").size()); + } + return CasStatusCode::kNoError; +} + + +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 (nullptr == media_id_.get() ) { + 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 used_license_filename; + if (media_id_->group_ids_size() > 0) { + used_license_filename = GenerateLicenseFilename(media_id_->group_id(), + media_id_->provider_id()); + } else { + used_license_filename = GenerateLicenseFilename(media_id_->content_id(), + media_id_->provider_id()); + } + if (file_name.compare(used_license_filename) == 0) { + // 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, content id and group 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()); + // group id is optional in ca_descriptor. + if (descriptor.has_group_id()) { + pssh.add_group_ids(descriptor.group_id()); + } + pssh.set_type(video_widevine::WidevinePsshData::ENTITLEMENT); + pssh.SerializeToString(init_data); + return CasStatusCode::kNoError; +} + +CasStatus WidevineCas::ProcessSessionCAPrivateData(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: ", parental_control_age_); + return CasStatusCode::kNoError; +} + +} // namespace wvcas diff --git a/plugin/src/widevine_cas_session.cpp b/plugin/src/widevine_cas_session.cpp new file mode 100644 index 0000000..cf363a2 --- /dev/null +++ b/plugin/src/widevine_cas_session.cpp @@ -0,0 +1,132 @@ +// 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 "log.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, uint32_t* session_id) { + if (crypto_session == nullptr || session_id == nullptr) { + LOGE("WidevineCasSession::initialize: missing input parameters"); + return CasStatus(CasStatusCode::kInvalidParameter, + "missing input parameters"); + } + crypto_session_ = std::move(crypto_session); + crypto_session_->CreateEntitledKeySession(&key_session_id_); + *session_id = key_session_id_; + return CasStatusCode::kNoError; +} + +const KeySlot& WidevineCasSession::key(KeySlotId slot_id) const { + // TODO(): Make this function private and assume the mutex is locked. + // std::unique_lock lock(lock_); + const KeySlot& key_slot = keys_[slot_id]; + return key_slot; +} + +CasStatus WidevineCasSession::processEcm(const CasEcm& ecm, + uint8_t parental_control_age) { + if (ecm != current_ecm_) { + LOGD("WidevineCasSession::processEcm: received new ecm"); + std::unique_ptr ecm_parser = getEcmParser(ecm); + if (!ecm_parser) { + return CasStatus(CasStatusCode::kInvalidParameter, "invalid ecm"); + } + + 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); + } + + bool load_even = false; + bool load_odd = false; + + std::unique_lock lock(lock_); + KeySlotId keyslot_id = KeySlotId::kEvenKeySlot; + // Temporary key slots to only have successfully loaded keys in |keys_|. + CasKeySlotData keys; + do { + if (keys_[keyslot_id].key_id != ecm_parser->content_key_id(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 { + std::unique_ptr new_ecm_parser; + if (!EcmParser::create(ecm, &new_ecm_parser)) { + return std::unique_ptr(); + } + return new_ecm_parser; +} + +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..c78c91d --- /dev/null +++ b/plugin/src/widevine_cas_session_map.cpp @@ -0,0 +1,37 @@ +// 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(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( + 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(WvCasSessionId cas_session_id) { + std::unique_lock lock(lock_); + map_.erase(cas_session_id); +} + +} // namespace wvcas diff --git a/plugin/src/widevine_media_cas.cpp b/plugin/src/widevine_media_cas.cpp new file mode 100644 index 0000000..98e1598 --- /dev/null +++ b/plugin/src/widevine_media_cas.cpp @@ -0,0 +1,86 @@ +// 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 const int32_t kWidevineCAID = 0x4AD4; +static const 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 { + if (kWidevineCAID != CA_system_id) { + return false; + } + return true; +} + +status_t WidevineCasFactory::queryPlugins( + std::vector* descriptors) const { + if (nullptr == descriptors) { + return BAD_VALUE; + } + descriptors->clear(); + descriptors->push_back({kWidevineCAID, 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..5c9086b --- /dev/null +++ b/plugin/src/widevine_media_cas_plugin.cpp @@ -0,0 +1,480 @@ +// 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 "cas_events.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 { + +static const char kLicenseFileExtension[] = ".lic"; + +// Convert android::CasSessionId type to a Widevine CAS session. +// Widevine cas session ids are currently defined to be an int32_t. This method +// converts android type to an int32_t. +// The use of int32_t is used to enable use of per session events by passing +// session id to this class's sendEvent method. This usage allows data passed in +// events to be simple data objects. Adding the session id to the event data +// would complicate the event handling. +// Planned new framework functionality will eliminate the need for this to be an +// int32_t and the underlying type can be changed. +WvCasSessionId androidSessionIdToWidevine( + const CasSessionId& android_session_id) { + std::string session_id_str(android_session_id.begin(), + android_session_id.end()); + WvCasSessionId wv_session_id = std::atoi(session_id_str.c_str()); + return wv_session_id; +} + +CasSessionId widevineSessionIdToAndroid( + const WvCasSessionId& wv_session_id) { + auto wv_session_id_begin = reinterpret_cast(&wv_session_id); + CasSessionId android_session_id + (wv_session_id_begin, wv_session_id_begin + sizeof(wv_session_id)); + return android_session_id; +} + +WidevineCasPlugin::WidevineCasPlugin(void* appData, CasPluginCallback callback) + : app_data_(appData), callback_(callback), callback_ext_(nullptr) {} + +WidevineCasPlugin::WidevineCasPlugin(void* appData, + CasPluginCallbackExt callback) + : app_data_(appData), callback_(nullptr), callback_ext_(callback) {} + +status_t WidevineCasPlugin::initialize() { + CasStatus status = widevine_cas_.initialize(this); + if (!status.ok()) { + return android::ERROR_CAS_UNKNOWN; + } + return OK; +} + +bool WidevineCasPlugin::is_provisioned() { + return widevine_cas_.is_provisioned(); +} + +status_t WidevineCasPlugin::setStatusCallback( + CasPluginStatusCallback /*callback*/) { + // TODO(chelu): support status callback. + return OK; +} + +// Will be called directly by player starting Android Q, see b/119039060. +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_.ProcessCAPrivateData(privateData, &provision_data_); + if (!status.ok()) { + return android::ERROR_CAS_UNKNOWN; + } + if (widevine_cas_.is_provisioned()) { + return requestLicense(provision_data_); + } + return OK; +} + +status_t WidevineCasPlugin::openSession(CasSessionId* sessionId) { + if (nullptr == sessionId) { + return BAD_VALUE; + } + WvCasSessionId new_session_id; + CasStatus status = widevine_cas_.openSession(&new_session_id); + if (!status.ok()) { + return android::ERROR_CAS_SESSION_NOT_OPENED; + } + std::string session_id_str = std::to_string(new_session_id); + *sessionId = CasSessionId(session_id_str.begin(), session_id_str.end()); + // Not a session event, so CasSessionId in callback is null. + CallBack(app_data_, CAS_SESSION_ID, new_session_id, nullptr, 0, 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) { + WvCasSessionId wv_session_id = androidSessionIdToWidevine(sessionId); + CasStatus status = widevine_cas_.closeSession(wv_session_id); + 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. + WvCasSessionId wv_session_id = androidSessionIdToWidevine(sessionId); + CasStatus status = widevine_cas_.ProcessSessionCAPrivateData( + wv_session_id, privateData, &provision_data_); + if (!status.ok()) { + return android::ERROR_CAS_SESSION_NOT_OPENED; + } + if (widevine_cas_.is_provisioned()) { + return requestLicense(provision_data_); + } + return OK; +} + +status_t WidevineCasPlugin::processEcm(const CasSessionId& sessionId, + const CasEcm& ecm) { + LOGI("WidevineCasPlugin::processEcm"); + WvCasSessionId wv_session_id = androidSessionIdToWidevine(sessionId); + CasStatus status = widevine_cas_.processEcm(wv_session_id, 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, wv_session_id, + &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*/) { + // TODO(jfore): This is used for inband emm. Currently unsupported. + return INVALID_OPERATION; +} + +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. + provision_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 (!provision_data_.empty()) { + return requestLicense(provision_data_); + } + return OK; + } + + std::string provisioning_request; + CasStatus status = + widevine_cas_.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; + CasStatus status = widevine_cas_.generateEntitlementRequest( + init_data, &signed_license_request); + if (!status.ok()) { + 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, nullptr, 0, nullptr); + } + is_emm_request_sent_ = true; + return OK; +} + +status_t WidevineCasPlugin::refreshEntitlements( + int32_t /*refreshType*/, const CasData& /*refreshData*/) { + return OK; +} + +std::shared_ptr WidevineCasPlugin::getCryptoSession() { + return std::make_shared(); +} + +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); + 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_.handleProvisioningResponse(resp_string); + if (!status.ok()) { + return status; + } + + CallBack(reinterpret_cast(app_data_), INDIVIDUALIZATION_COMPLETE, 0, + nullptr, 0, nullptr); + + if (!provision_data_.empty() && !is_emm_request_sent_) { + LOGD("Making license request with provisioned PSSH"); + if (requestLicense(provision_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; + CasStatus + status = widevine_cas_.handleEntitlementResponse(resp_string, license_id); + 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); + 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_.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::HandleUniqueIDQuery() { + std::string buffer; + CasStatus status = widevine_cas_.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_.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_.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; +} + +void WidevineCasPlugin::OnSessionRenewalNeeded() { + LOGI("OnSessionRenewalNeeded"); + std::string renewal_request; + CasStatus status = + widevine_cas_.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); + } +} + +void WidevineCasPlugin::OnExpirationUpdate(int64_t new_expiry_time_seconds) { + LOGI("OnExpirationUpdate"); +} + +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"); + const CasSessionId android_session_id = widevineSessionIdToAndroid(sessionId); + CallBack(reinterpret_cast(app_data_), AGE_RESTRICTION_UPDATED, + sessionId, &ecm_age_restriction, 1, &android_session_id); +} + +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..151ed5e --- /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: [ + "ca_descriptor.proto", + "device_files.proto", + "license_protocol.proto", + ], + + proto: { + export_proto_headers: true, + }, +} diff --git a/protos/ca_descriptor.proto b/protos/ca_descriptor.proto new file mode 100644 index 0000000..20ebf43 --- /dev/null +++ b/protos/ca_descriptor.proto @@ -0,0 +1,21 @@ +// 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; + + // Group ID indicates which group the content comes from. + optional bytes group_id = 3; +} diff --git a/protos/device_files.proto b/protos/device_files.proto new file mode 100644 index 0000000..ed82219 --- /dev/null +++ b/protos/device_files.proto @@ -0,0 +1,117 @@ +// ---------------------------------------------------------------------------- +// 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..eb2ae73 --- /dev/null +++ b/protos/license_protocol.proto @@ -0,0 +1,1153 @@ +// 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; +} + +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]; + } + + 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; + } + + 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; +} + +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; +} + +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; + EPHERMERAL_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 + // EPHERMERAL_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; +} + +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. + 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; +} + +// ---------------------------------------------------------------------------- +// 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; + + //////////////////////////// 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; + } + 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; +} + +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; +} + +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/tests/Android.bp b/tests/Android.bp new file mode 100644 index 0000000..1ab0d50 --- /dev/null +++ b/tests/Android.bp @@ -0,0 +1,34 @@ +cc_binary { + name: "wv_cas_tests", + proprietary: true, + srcs: [ + "src/wv_cas_test_main.cpp", + "src/cas_license_test.cpp", + "src/crypto_session_test.cpp", + "src/ecm_parser_test.cpp", + "src/test_properties.cpp", + "src/widevine_cas_session_test.cpp", + "src/cas_session_map_test.cpp", + "src/license_key_status_test.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", + ], + 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..fb95f91 --- /dev/null +++ b/tests/src/cas_license_test.cpp @@ -0,0 +1,629 @@ +// 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 + +#include + +#include "cas_license.h" +#include "cas_status.h" +#include "cas_util.h" +#include "crypto_key.h" +#include "device_files.pb.h" +#include "license_protocol.pb.h" +#include "mock_crypto_session.h" +#include "string_conversions.h" + +// Prototype for ExtractEntitlementKeys. This prototype is added here to allow +// this method to be unit tested without being added to CasLicense header. +namespace wvcas { +std::vector ExtractEntitlementKeys( + const video_widevine::License& license); +} // namespace wvcas + +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::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(CasLicenseUtilityTest, ExtractEntitlementKeys) { + video_widevine::License license; + + auto* key = license.add_key(); + key->set_type(video_widevine::License::KeyContainer::ENTITLEMENT); + key->set_id(kKeyIDVideo); + key->set_iv(kKeyVideoIV); + key->mutable_key_control()->set_key_control_block(kKeyControlVideo); + key->mutable_key_control()->set_iv(kKeyControlIVVideo); + key->set_track_label(kTrackTypeVideo); + + key = license.add_key(); + key->set_type(video_widevine::License::KeyContainer::ENTITLEMENT); + key->set_id(kKeyIDAudio); + key->set_iv(kKeyAudioIV); + key->mutable_key_control()->set_key_control_block(kKeyControlAudio); + key->mutable_key_control()->set_iv(kKeyControlIVAudio); + key->set_track_label(kTrackTypeAudio); + + std::vector keys = wvcas::ExtractEntitlementKeys(license); + ASSERT_EQ(2, keys.size()); + + EXPECT_EQ(kKeyIDVideo, keys[0].key_id()); + EXPECT_EQ(kKeyVideoIV, keys[0].key_data_iv()); + EXPECT_EQ(kKeyControlVideo, keys[0].key_control()); + EXPECT_EQ(kKeyControlIVVideo, keys[0].key_control_iv()); + EXPECT_EQ(kTrackTypeVideo, keys[0].track_label()); + + EXPECT_EQ(kKeyIDAudio, keys[1].key_id()); + EXPECT_EQ(kKeyAudioIV, keys[1].key_data_iv()); + EXPECT_EQ(kKeyControlAudio, keys[1].key_control()); + EXPECT_EQ(kKeyControlIVAudio, keys[1].key_control_iv()); + EXPECT_EQ(kTrackTypeAudio, keys[1].track_label()); +} + +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), + 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_, GenerateNonce(_)) + .WillOnce(Return(wvcas::CasStatusCode::kNoError)); + EXPECT_CALL(*strict_mock_, + PrepareAndSignLicenseRequest(_, NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<2>(kExpectedSignature), + 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_, GenerateNonce(_)) + .WillOnce(Return(wvcas::CasStatusCode::kNoError)); + EXPECT_CALL(*strict_mock_, + PrepareAndSignLicenseRequest(_, NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<2>(kExpectedSignature), + 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, nullptr); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, status.status_code()); + + // 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()); +} \ 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..cf296d6 --- /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) { + int base_key = rand(); + std::string key = std::to_string(base_key); + 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..d12dd18 --- /dev/null +++ b/tests/src/crypto_session_test.cpp @@ -0,0 +1,1038 @@ +// 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" + +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"); + +// Defined in cas_license.cpp. +namespace wvcas { +extern std::vector ExtractKeyControlKeys( + const video_widevine::License& license); +} // namespace wvcas + +// 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_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_EntitledCasKeyObject* even_key, + const OEMCrypto_EntitledCasKeyObject* odd_key)); + MOCK_CONST_METHOD4(OEMCrypto_SelectKey, + OEMCryptoResult(OEMCrypto_SESSION session, + const uint8_t* content_key_id, + size_t content_key_id_length, + OEMCryptoCipherMode cipher_mode)); + MOCK_METHOD7(OEMCrypto_RefreshKeys, + OEMCryptoResult(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)); + MOCK_METHOD2(OEMCrypto_GetDeviceID, + OEMCryptoResult(uint8_t* deviceID, size_t* idLength)); + MOCK_METHOD2(OEMCrypto_CreateEntitledKeySession, + OEMCryptoResult(OEMCrypto_SESSION session, + OEMCrypto_SESSION* entitled_key_session_id)); + MOCK_METHOD1(OEMCrypto_RemoveEntitledKeySession, + OEMCryptoResult(OEMCrypto_SESSION entitled_key_session_id)); +}; + +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()); + + EXPECT_CALL(nice_oemcrypto_interface_, OEMCrypto_CloseSession(kOemcSessionId)) + .WillOnce(Return(OEMCrypto_SUCCESS)); + crypto_session_one.close(); + + EXPECT_CALL(nice_oemcrypto_interface_, + OEMCrypto_CloseSession(kOemcSessionId2)) + .WillOnce(Return(OEMCrypto_SUCCESS)); + crypto_session_two.close(); +} + +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)); + + 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, 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; + 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) + .status_code()); + EXPECT_EQ(core_message, std::string(kExpectedCoreMessage)); + EXPECT_EQ(signature, std::string(kExpectedSignature)); +} + +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; + 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) + .status_code()); + EXPECT_EQ(core_message, std::string(kExpectedCoreMessage)); + EXPECT_EQ(signature, std::string(kExpectedSignature)); +} + +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, SelectKeys) { + 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()); + std::vector key_id(kTestKeyID); + { + InSequence select_key_test_sequence; + EXPECT_CALL( + nice_oemcrypto_interface_, + OEMCrypto_SelectKey(kEntitledKeySessionId, NotNull(), Eq(key_id.size()), + OEMCrypto_CipherMode_CTR)) + .WillOnce(Return(OEMCrypto_SUCCESS)); + EXPECT_CALL( + nice_oemcrypto_interface_, + OEMCrypto_SelectKey(kEntitledKeySessionId, NotNull(), Eq(key_id.size()), + OEMCrypto_CipherMode_CBC)) + .WillOnce(Return(OEMCrypto_SUCCESS)); + EXPECT_CALL( + nice_oemcrypto_interface_, + OEMCrypto_SelectKey(kEntitledKeySessionId, NotNull(), Eq(key_id.size()), + OEMCrypto_CipherMode_CSA2)) + .WillOnce(Return(OEMCrypto_SUCCESS)); + EXPECT_CALL( + nice_oemcrypto_interface_, + OEMCrypto_SelectKey(kEntitledKeySessionId, NotNull(), Eq(key_id.size()), + OEMCrypto_CipherMode_CSA3)) + .WillOnce(Return(OEMCrypto_SUCCESS)); + EXPECT_CALL( + nice_oemcrypto_interface_, + OEMCrypto_SelectKey(kEntitledKeySessionId, NotNull(), Eq(key_id.size()), + OEMCrypto_CipherMode_OFB)) + .WillOnce(Return(OEMCrypto_SUCCESS)); + EXPECT_CALL( + nice_oemcrypto_interface_, + OEMCrypto_SelectKey(kEntitledKeySessionId, NotNull(), Eq(key_id.size()), + OEMCrypto_CipherMode_SCTE)) + .WillOnce(Return(OEMCrypto_SUCCESS)); + + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + crypto_session + .SelectKey(kEntitledKeySessionId, key_id, + wvcas::CryptoMode::kAesCTR) + .status_code()); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + crypto_session + .SelectKey(kEntitledKeySessionId, key_id, + wvcas::CryptoMode::kAesCBC) + .status_code()); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + crypto_session + .SelectKey(kEntitledKeySessionId, key_id, + wvcas::CryptoMode::kDvbCsa2) + .status_code()); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + crypto_session + .SelectKey(kEntitledKeySessionId, key_id, + wvcas::CryptoMode::kDvbCsa3) + .status_code()); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + crypto_session + .SelectKey(kEntitledKeySessionId, key_id, + wvcas::CryptoMode::kAesOFB) + .status_code()); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + crypto_session + .SelectKey(kEntitledKeySessionId, key_id, + wvcas::CryptoMode::kAesSCTE) + .status_code()); + } +} + +TEST_F(CryptoSessionTest, RefreshKeys) { + 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))); + + ASSERT_EQ(wvcas::CasStatusCode::kNoError, + crypto_session.initialize().status_code()); + + video_widevine::License license; + + // Empty key array - no keys. + ASSERT_EQ(wvcas::CasStatusCode::kNoError, + crypto_session + .RefreshKeys(license.SerializeAsString(), "signature", + std::vector()) + .status_code()); + + auto* key_1 = license.add_key(); + key_1->set_type(video_widevine::License_KeyContainer::KEY_CONTROL); + auto* key_2 = license.add_key(); + key_2->set_type(video_widevine::License_KeyContainer::KEY_CONTROL); + auto* key_3 = license.add_key(); + key_3->set_type(video_widevine::License_KeyContainer::KEY_CONTROL); + + std::vector key_array = + wvcas::ExtractKeyControlKeys(license); + + EXPECT_CALL(strict_oemcrypto_interface_, + OEMCrypto_RefreshKeys(_, _, _, _, _, 3, _)) + .WillOnce(Return(OEMCrypto_ERROR_SIGNATURE_FAILURE)) + .WillOnce(Return(OEMCrypto_SUCCESS)); + + // OEMCrypto error. + ASSERT_EQ( + wvcas::CasStatusCode::kCryptoSessionError, + crypto_session + .RefreshKeys(license.SerializeAsString(), "signature", key_array) + .status_code()); + + // Valid. + ASSERT_EQ( + wvcas::CasStatusCode::kNoError, + crypto_session + .RefreshKeys(license.SerializeAsString(), "signature", key_array) + .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))); + + 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))); + + 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()); +} diff --git a/tests/src/ecm_parser_test.cpp b/tests/src/ecm_parser_test.cpp new file mode 100644 index 0000000..186b716 --- /dev/null +++ b/tests/src/ecm_parser_test.cpp @@ -0,0 +1,292 @@ +// 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 + +namespace { + +static constexpr int kCaIdSizeBytes = 2; +static constexpr int kModeSizeBytes = 1; +static constexpr int kVersionSizeBytes = 1; +static constexpr int kIVFlagsSizeBytes = 1; +static constexpr int kEntitlementKeyIDSizeBytes = 16; +static constexpr int kContentKeyIDSizeBytes = 16; +static constexpr int kContentKeyDataSize = 16; +static constexpr int kWrappedKeyIVSizeBytes = 16; + +static constexpr int kEcmDescriptorSizeBytes = + kCaIdSizeBytes + kModeSizeBytes + kVersionSizeBytes + kIVFlagsSizeBytes; + +static constexpr int kECMVersion = 2; +// The cipher mode flags field in the ECM V2 is 4 bits. +static constexpr uint8_t kAESCBCCryptoModeFlagsVal = (0x0 << 1); +static constexpr uint8_t kAESCTRCryptoModeFlagsVal = (0x1 << 1); +static constexpr uint8_t kDvbCsa2CryptoModeFlagsVal = (0x2 << 1); +static constexpr uint8_t kDvbCsa3CryptoModeFlagsVal = (0x3 << 1); +static constexpr uint8_t kDvbOFBCryptoModeFlagsVal = (0x4 << 1); +static constexpr uint8_t kDvbSCTECryptoModeFlagsVal = (0x5 << 1); +static constexpr uint8_t kRotationFlag = (0x1 << 0); +static constexpr uint8_t kContentIVSizeFlag = (0x1 << 6); + +static constexpr uint8_t kEntitlementKeyIDFill = '1'; +static constexpr uint8_t kEvenContentKeyIDFill = '2'; +static constexpr uint8_t kEvenContentKeyDataFill = '3'; +static constexpr uint8_t kEvenWrappedKeyIVFill = '4'; +static constexpr uint8_t kEvenContentKeyIVFill = '5'; +static constexpr uint8_t kOddContentKeyIDFill = '6'; +static constexpr uint8_t kOddContentKeyDataFill = '7'; +static constexpr uint8_t kOddWrappedKeyIVFill = '8'; +static constexpr uint8_t kOddContentKeyIVFill = '9'; + +static constexpr size_t kMaxEcmSizeBytes = 184; + +static constexpr uint16_t kSectionHeader1 = 0x80; +static constexpr uint16_t kSectionHeader2 = 0x81; +static constexpr size_t kSectionHeaderSize = 4; +} // namespace + +class EcmParserTest : public testing::Test { + public: + void SetUp() { BuildEcm(); } + size_t ContentKeyIVSize(bool content_iv_flag); + size_t CalculateEcmSize(bool with_rotation, bool content_iv_flag = false); + std::vector ecm_data_; + + private: + void BuildEcm(bool with_rotation = true, bool content_iv_flag = false); +}; + +size_t EcmParserTest::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 EcmParserTest::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 EcmParserTest::BuildEcm(bool with_rotation, bool content_iv_flag) { + ecm_data_.clear(); + ecm_data_.reserve(CalculateEcmSize(with_rotation, content_iv_flag)); + ecm_data_.resize(kCaIdSizeBytes, 0); + ecm_data_[0] = 0x4A; + ecm_data_[1] = 0xD4; + 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(EcmParserTest, FieldsWithoutKeyRotation) { + bool content_key_iv_16b = false; + ecm_data_.resize(CalculateEcmSize(false, content_key_iv_16b)); + std::unique_ptr parser; + ASSERT_TRUE(wvcas::EcmParser::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(EcmParserTest, FieldsWithKeyRotation) { + ecm_data_[3] |= kRotationFlag; + std::unique_ptr parser; + ASSERT_TRUE(wvcas::EcmParser::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(EcmParserTest, create) { + std::unique_ptr parser; + EXPECT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser)); + + ecm_data_.resize(4); + EXPECT_FALSE(wvcas::EcmParser::create(ecm_data_, &parser)); + + ecm_data_.resize(4 + CalculateEcmSize(false)); + EXPECT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser)); + + ecm_data_.resize(kMaxEcmSizeBytes); + EXPECT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser)); + + ecm_data_.resize(CalculateEcmSize(true)); + EXPECT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser)); + EXPECT_FALSE(wvcas::EcmParser::create(ecm_data_, nullptr)); + + ecm_data_.resize(CalculateEcmSize(true)); + EXPECT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser)); + EXPECT_FALSE(wvcas::EcmParser::create(ecm_data_, nullptr)); +} + +TEST_F(EcmParserTest, crypto_mode) { + std::unique_ptr parser; + ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser)); + EXPECT_EQ(parser->crypto_mode(), wvcas::CryptoMode::kAesCBC); + + ecm_data_[3] = kAESCTRCryptoModeFlagsVal; + ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser)); + EXPECT_EQ(parser->crypto_mode(), wvcas::CryptoMode::kAesCTR); + + ecm_data_[3] = kDvbCsa2CryptoModeFlagsVal; + ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser)); + EXPECT_EQ(parser->crypto_mode(), wvcas::CryptoMode::kDvbCsa2); + + ecm_data_[3] = kDvbCsa3CryptoModeFlagsVal; + ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser)); + EXPECT_EQ(parser->crypto_mode(), wvcas::CryptoMode::kDvbCsa3); + + ecm_data_[3] = kDvbOFBCryptoModeFlagsVal; + ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser)); + EXPECT_EQ(parser->crypto_mode(), wvcas::CryptoMode::kAesOFB); + + ecm_data_[3] = kDvbSCTECryptoModeFlagsVal; + ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser)); + EXPECT_EQ(parser->crypto_mode(), wvcas::CryptoMode::kAesSCTE); +} + +TEST_F(EcmParserTest, ContentKeyIVSizes) { + std::unique_ptr parser; + bool with_rotation = true; + bool iv_flag = false; + ecm_data_.resize(CalculateEcmSize(with_rotation, iv_flag)); + ASSERT_TRUE(wvcas::EcmParser::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::EcmParser::create(ecm_data_, &parser)); + EXPECT_EQ(parser->content_iv_size(), ContentKeyIVSize(iv_flag)); +} + +TEST_F(EcmParserTest, EcmWithSectionHeader) { + std::unique_ptr parser; + std::vector section_header; + section_header.resize(kSectionHeaderSize); + *reinterpret_cast(section_header.data()) = htons(kSectionHeader1); + // If ECM is prepended with section header, parsing must still work. + ecm_data_.insert(ecm_data_.begin(), section_header.begin(), + section_header.end()); + ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser)); + + *((uint16_t*)ecm_data_.data()) = htons(kSectionHeader2); + ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser)); + + // Change header size, parsing should still work + ecm_data_.erase(ecm_data_.begin()); + ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser)); +} + +TEST_F(EcmParserTest, AgeRestriction) { + std::unique_ptr parser; + ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser)); + EXPECT_EQ(0, parser->age_restriction()); + + uint8_t age_restriction = 16; + ecm_data_[4] |= age_restriction << 1; + ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser)); + EXPECT_EQ(age_restriction, parser->age_restriction()); +} diff --git a/tests/src/license_key_status_test.cpp b/tests/src/license_key_status_test.cpp new file mode 100644 index 0000000..b742a1d --- /dev/null +++ b/tests/src/license_key_status_test.cpp @@ -0,0 +1,934 @@ +// 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 +#include "cas_types.h" +#include "license_key_status.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..4374e36 --- /dev/null +++ b/tests/src/mediacas_integration_test.cpp @@ -0,0 +1,143 @@ +// Dynamically generated header created during build. The Runtest entry point is +// defined in gopkg_carchive.go + +#include "gowvcas_carchive.h" +#include "gtest/gtest.h" + +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, TestDescramblerFactoryCreation) { + EXPECT_EQ(kIntegrationTestPassed, + RunNamedTest("TestDescramblerFactoryCreation")); +} + +TEST(IntegrationTests, TestCreateCasPlugin) { + EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestCreateCasPlugin")); +} + +TEST(IntegrationTests, TestCreateCasPluginWithSessionEvent) { + EXPECT_EQ(kIntegrationTestPassed, + RunNamedTest("TestCreateCasPluginWithSessionEvent")); +} + +TEST(IntegrationTests, TestCasPluginEventPassing) { + EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestCasPluginEventPassing")); +} + +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, TestCasPrivateDataWithGroupLicense) { + EXPECT_EQ(kIntegrationTestPassed, + RunNamedTest("TestCasPrivateDataWithGroupLicense")); +} + +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, TestDescrambler) { + EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestDescrambler")); +} + +TEST(IntegrationTests, TestSession) { + EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestSession")); +} + +TEST(IntegrationTests, TestPESDecrypt) { + EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestPESDecrypt")); +} + +TEST(IntegrationTests, TestConcatentatedPESDecrypt) { + EXPECT_EQ(kIntegrationTestPassed, + RunNamedTest("TestConcatentatedPESDecrypt")); +} + +TEST(IntegrationTests, TestPlaybackDecrypt) { + EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestPlaybackDecrypt")); +} + +TEST(IntegrationTests, TestCasRenewal) { + EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestCasRenewal")); +} + +TEST(IntegrationTests, TestRestoreRenewalAndExpiredLicense) { + EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestRestoreRenewalAndExpiredLicense")); +} + +TEST(IntegrationTests, TestPesHeaderDecrypt) { + EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestPesHeaderDecrypt")); +} + +TEST(IntegrationTests, TestPesHeaderDecryptInTSPacket) { + EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestPesHeaderDecryptInTSPacket")); +} + +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, TestTSDecrypt) { + EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestTSDecrypt")); +} + +TEST(IntegrationTests, TestTSDecryptWithKeyRotation) { + EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestTSDecryptWithKeyRotation")); +} \ No newline at end of file diff --git a/tests/src/mock_crypto_session.h b/tests/src/mock_crypto_session.h new file mode 100644 index 0000000..428c1ff --- /dev/null +++ b/tests/src/mock_crypto_session.h @@ -0,0 +1,86 @@ +// 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(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_METHOD3(PrepareAndSignLicenseRequest, + wvcas::CasStatus(const std::string& message, + std::string* core_message, + std::string* signature)); + MOCK_METHOD3(PrepareAndSignRenewalRequest, + wvcas::CasStatus(const std::string& message, + std::string* core_message, + std::string* signature)); + MOCK_METHOD3(PrepareAndSignProvisioningRequest, + wvcas::CasStatus(const std::string& message, + std::string* core_message, + std::string* signature)); + 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_METHOD3(SelectKey, wvcas::CasStatus(OEMCrypto_SESSION session, + const std::vector& key_id, + wvcas::CryptoMode crypto_mode)); + 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)); +}; + +#endif // MOCK_CRYPTO_SESSION_H diff --git a/tests/src/policy_engine_test.cpp b/tests/src/policy_engine_test.cpp new file mode 100644 index 0000000..5103906 --- /dev/null +++ b/tests/src/policy_engine_test.cpp @@ -0,0 +1,356 @@ +#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" + +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 MockEventListener : public wvcas::CasEventListener { + public: + MockEventListener() {} + virtual ~MockEventListener() {} + + MOCK_METHOD0(OnSessionRenewalNeeded, void()); + MOCK_METHOD2(OnSessionKeysChange, + void(const KeyStatusMap& keys_status, bool has_new_usable_key)); + MOCK_METHOD1(OnExpirationUpdate, void(int64_t new_expiry_time_seconds)); + MOCK_METHOD1(OnNewRenewalServerUrl, + void(const std::string& renewal_server_url)); + MOCK_METHOD0(OnLicenseExpiration, void()); + MOCK_METHOD2(OnAgeRestrictionUpdated, + void(const wvcas::WvCasSessionId& sessionId, + uint8_t ecm_age_restriction)); +}; + +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..204b18c --- /dev/null +++ b/tests/src/test_properties.cpp @@ -0,0 +1,66 @@ +// 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; +} + +} // namespace wvcas diff --git a/tests/src/timer_test.cpp b/tests/src/timer_test.cpp new file mode 100644 index 0000000..43e9a5a --- /dev/null +++ b/tests/src/timer_test.cpp @@ -0,0 +1,34 @@ +// 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 "clock.h" +#include "timer.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..73a9ea5 --- /dev/null +++ b/tests/src/widevine_cas_api_test.cpp @@ -0,0 +1,562 @@ +// 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 +#include + +#include "cas_util.h" +#include "cas_license.h" +#include "ecm_parser.h" +#include "mock_crypto_session.h" +#include "string_conversions.h" +#include "widevine_cas_api.h" +#include "widevine_cas_session_map.h" + +using ::testing::_; +using ::testing::DoAll; +using ::testing::NiceMock; +using ::testing::Return; +using ::testing::SetArgPointee; +using ::testing::StrictMock; + +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_METHOD2(HandleStoredLicense, + wvcas::CasStatus(const std::string& wrapped_rsa_key, + const std::string& license_file)); + 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_METHOD2(HandleEntitlementResponse, + wvcas::CasStatus(const std::string& entitlement_response, + std::string* device_file)); + MOCK_METHOD0(BeginDecryption, void()); + MOCK_METHOD0(UpdateLicenseForLicenseRemove, void()); +}; +typedef StrictMock StrictMockLicense; + +class MockEventListener : public wvcas::CasEventListener { + public: + MockEventListener() {} + ~MockEventListener() override {} + MOCK_METHOD0(OnSessionRenewalNeeded, void()); + MOCK_METHOD2(OnSessionKeysChange, void(const wvcas::KeyStatusMap& keys_status, + bool has_new_usable_key)); + MOCK_METHOD1(OnExpirationUpdate, void(int64_t new_expiry_time_seconds)); + MOCK_METHOD1(OnNewRenewalServerUrl, + void(const std::string& renewal_server_url)); + MOCK_METHOD0(OnLicenseExpiration, void()); + MOCK_METHOD2(OnAgeRestrictionUpdated, + void(const wvcas::WvCasSessionId& sessionId, + uint8_t ecm_age_restriction)); +}; +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 { + class EcmParser : public wvcas::EcmParser { + public: + EcmParser() {} + ~EcmParser() override {} + }; + + public: + MockWidevineSession() {} + ~MockWidevineSession() override {} + std::unique_ptr getEcmParser( + const wvcas::CasEcm& ecm) const override { + return make_unique(); + } + MOCK_METHOD2(processEcm, wvcas::CasStatus(const wvcas::CasEcm& ecm, + uint8_t parental_control_age)); + MOCK_METHOD2(HandleProcessEcm, + wvcas::CasStatus(const wvcas::WvCasSessionId& sessionId, + const wvcas::CasEcm& ecm)); +}; + +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_); + } + + std::unique_ptr getCasLicense() override { + return std::move(pass_thru_license_); + } + + std::unique_ptr getFileSystem() override { + return std::move(pass_thru_file_system_); + } + + std::shared_ptr newCasSession() override { + return std::make_shared >(); + } + + std::unique_ptr pass_thru_license_; + std::unique_ptr pass_thru_crypto_session_; + std::unique_ptr pass_thru_file_system_; + + StrictMockLicense* license_ = nullptr; + StrictMockCryptoSession* crypto_session_ = nullptr; + NiceMockFileSystem* file_system_ = 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())); + 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()); + + // Invalid parameter. + std::string request, init_data; + EXPECT_NE( + wvcas::CasStatusCode::kNoError, + cas_api.generateEntitlementRequest(init_data, nullptr).status_code()); + + // GenerateEntitlementRequest returns an error. + EXPECT_CALL(*cas_api.license_, GenerateEntitlementRequest(_, _, _, _, _)) + .WillRepeatedly(Return(wvcas::CasStatus( + wvcas::CasStatusCode::kCasLicenseError, "forced failure"))); + EXPECT_NE( + wvcas::CasStatusCode::kNoError, + cas_api.generateEntitlementRequest(init_data, &request).status_code()); + + // 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_NE( + wvcas::CasStatusCode::kNoError, + cas_api.generateEntitlementRequest(init_data, &request).status_code()); + + 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).status_code()); + + // 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).status_code()); + + // 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).status_code()); + + // 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 + 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).status_code()); +} + +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; + EXPECT_EQ( + wvcas::CasStatusCode::kNoError, + cas_api.generateEntitlementRequest(init_data, &request).status_code()); + + // Valid: License is expired then start to remove. + 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_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()); + + wvcas::WvCasSessionId video_sid; + wvcas::WvCasSessionId audio_sid; + // In real implementation, these ids are generated by OEMCrypto. + uint32_t video_session_id = 1; + uint32_t audio_session_id = 2; + EXPECT_CALL(*(cas_api.crypto_session_), CreateEntitledKeySession(_)) + .WillOnce(DoAll(SetArgPointee<0>(video_session_id), + Return(wvcas::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_session_id), + Return(wvcas::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; + std::string file("license_file"); + + EXPECT_CALL(*cas_api.file_system_, Exists(_)).WillOnce(Return(true)); + EXPECT_CALL(*cas_api.file_system_, FileSize(_)) + .WillOnce(Return(file.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.size())); + EXPECT_CALL(*cas_api.license_, HandleStoredLicense(_, _)); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + cas_api.generateEntitlementRequest("init_data", &request) + .status_code()); + } else { + // Empty response. + std::string init_data; + EXPECT_CALL(*cas_api.license_, HandleEntitlementResponse(_, _)) + .WillOnce(Return(wvcas::CasStatusCode::kNoError)); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + cas_api.handleEntitlementResponse("response", init_data) + .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_session_id)); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + cas_api.closeSession(video_sid).status_code()); + EXPECT_CALL(*(cas_api.crypto_session_), + RemoveEntitledKeySession(audio_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; + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + cas_api.generateEntitlementRequest(init_data, &request) + .status_code()); + + // 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()); + + // Happy case: remove the in used license file + std::string hash; + std::string kBasePathPrefix = "/data/vendor/mediacas/IDM/widevine/"; + 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); + std::string full_file_name = kBasePathPrefix + wvutil::b2a_hex(hash) + + std::string(".lic"); + + EXPECT_CALL(*cas_api.license_, UpdateLicenseForLicenseRemove()).Times(1); + EXPECT_EQ(wvcas::CasStatusCode::kNoError, + cas_api.RemoveLicense(full_file_name).status_code()); +} \ No newline at end of file diff --git a/tests/src/widevine_cas_session_test.cpp b/tests/src/widevine_cas_session_test.cpp new file mode 100644 index 0000000..31ae621 --- /dev/null +++ b/tests/src/widevine_cas_session_test.cpp @@ -0,0 +1,283 @@ +// 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 +#include +#include + +#include "cas_util.h" +#include "mock_crypto_session.h" +#include "string_conversions.h" +#include "widevine_cas_session.h" + +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; + +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 MockEcmParser : public wvcas::EcmParser { + public: + MOCK_CONST_METHOD0(sequence_count, uint8_t()); + MOCK_CONST_METHOD0(age_restriction, uint8_t()); + MOCK_CONST_METHOD0(crypto_mode, wvcas::CryptoMode()); + MOCK_CONST_METHOD0(rotation_enabled, bool()); + MOCK_CONST_METHOD1(entitlement_key_id, + const std::vector(wvcas::KeySlotId id)); + MOCK_CONST_METHOD1(content_key_id, + const std::vector(wvcas::KeySlotId id)); + MOCK_CONST_METHOD1(wrapped_key_data, + const std::vector(wvcas::KeySlotId id)); + MOCK_CONST_METHOD1(wrapped_key_iv, + const std::vector(wvcas::KeySlotId id)); + MOCK_CONST_METHOD1(content_iv, + const std::vector(wvcas::KeySlotId id)); +}; + +class CasSessionTest : public ::testing::Test { + public: + CasSessionTest() {} + virtual ~CasSessionTest() {} +}; + +// 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; + + 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; + } + + private: + uint8_t age_restriction_ = 0; +}; + +std::unique_ptr TestCasSession::getEcmParser( + const wvcas::CasEcm& ecm) const { + std::unique_ptr> mock_ecm_parser( + new NiceMock); + ON_CALL(*mock_ecm_parser, sequence_count()).WillByDefault(Return(0)); + 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)); + return std::unique_ptr(mock_ecm_parser.release()); +} + +TEST_F(CasSessionTest, processEcm) { + TestCasSession session; + std::shared_ptr> mock = + std::make_shared>(); + + uint32_t session_id; + EXPECT_CALL(*mock, CreateEntitledKeySession(NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(kEntitledKeySessionId), + Return(wvcas::CasStatusCode::kNoError))); + ASSERT_EQ(wvcas::CasStatusCode::kNoError, + session.initialize(mock, &session_id).status_code()); + EXPECT_EQ(session_id, kEntitledKeySessionId); + + wvcas::CasEcm ecm(184); + EXPECT_CALL(*mock, + LoadCasECMKeys(session_id, IsValidKeyEvenSlotData(), + IsValidKeyOddSlotData())); + session.processEcm(ecm, 0); + EXPECT_CALL(*mock, RemoveEntitledKeySession(session_id)); +} + +TEST_F(CasSessionTest, parentalControl) { + TestCasSession session; + std::shared_ptr> mock = + std::make_shared>(); + uint32_t session_id; + EXPECT_CALL(*mock, CreateEntitledKeySession(NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(kEntitledKeySessionId), + Return(wvcas::CasStatusCode::kNoError))); + ASSERT_EQ(wvcas::CasStatusCode::kNoError, + session.initialize(mock, &session_id).status_code()); + EXPECT_EQ(session_id, kEntitledKeySessionId); + + EXPECT_CALL(*mock, LoadCasECMKeys(session_id, 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).status_code()); + std::generate(ecm.begin(), ecm.end(), std::rand); + ASSERT_EQ(wvcas::CasStatusCode::kNoError, + session.processEcm(ecm, 13).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).status_code()); + std::generate(ecm.begin(), ecm.end(), std::rand); + ASSERT_EQ(wvcas::CasStatusCode::kNoError, + session.processEcm(ecm, 10).status_code()); + std::generate(ecm.begin(), ecm.end(), std::rand); + ASSERT_EQ(wvcas::CasStatusCode::kNoError, + session.processEcm(ecm, 13).status_code()); + std::generate(ecm.begin(), ecm.end(), std::rand); + ASSERT_EQ(wvcas::CasStatusCode::kAccessDeniedByParentalControl, + session.processEcm(ecm, 3).status_code()); + EXPECT_CALL(*mock, RemoveEntitledKeySession(session_id)); +} diff --git a/tests/src/wv_cas_test_main.cpp b/tests/src/wv_cas_test_main.cpp new file mode 100644 index 0000000..e1597f6 --- /dev/null +++ b/tests/src/wv_cas_test_main.cpp @@ -0,0 +1,19 @@ +// 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 "OEMCryptoCAS.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::LOG_INFO; + return RUN_ALL_TESTS(); +} diff --git a/wvutil/Android.bp b/wvutil/Android.bp new file mode 100644 index 0000000..7cfc209 --- /dev/null +++ b/wvutil/Android.bp @@ -0,0 +1,32 @@ +// Builds libcasutil.a +cc_library_static { + + name: "libcasutil", + + proprietary: true, + + local_include_dirs: [ + "include", + ], + + // Filestore support is needed for building unit tests. + + 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", + ], + + 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..05ebba3 --- /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 Master +// License Agreement. + +#ifndef WV_UTIL_ADVANCE_IV_CTR_H_ +#define WV_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 // WV_UTIL_ADVANCE_IV_CTR_H_ diff --git a/wvutil/include/arraysize.h b/wvutil/include/arraysize.h new file mode 100644 index 0000000..3e02553 --- /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 Master +// License Agreement. + +#ifndef WVCDM_UTIL_ARRAYSIZE_H_ +#define WVCDM_UTIL_ARRAYSIZE_H_ + +#include + +namespace wvcdm { + +// Returns the size of a fixed-length array. +template +constexpr size_t ArraySize(const T (&)[N]) { + return N; +} + +} // namespace wvcdm + +#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..1312640 --- /dev/null +++ b/wvutil/include/cas_properties.h @@ -0,0 +1,43 @@ +// 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); +}; + +} // 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..bc97840 --- /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 Master +// License Agreement. +#ifndef WVCDM_CORE_CDM_RANDOM_H_ +#define WVCDM_CORE_CDM_RANDOM_H_ + +#include +#include +#include + +namespace wvcdm { + +// 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 wvcdm + +#endif // WVCDM_CORE_CDM_RANDOM_H_ diff --git a/wvutil/include/clock.h b/wvutil/include/clock.h new file mode 100644 index 0000000..9514f61 --- /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 Master +// License Agreement. +// +// Clock - Platform independent interface for a time library +// +#ifndef WV_UTIL_CLOCK_H_ +#define WV_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 // WV_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..034b224 --- /dev/null +++ b/wvutil/include/disallow_copy_and_assign.h @@ -0,0 +1,17 @@ +// 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 WV_UTIL_DISALLOW_COPY_AND_ASSIGN_H_ +#define WV_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 // WVCAS_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..e1c7f85 --- /dev/null +++ b/wvutil/include/file_store.h @@ -0,0 +1,77 @@ +// 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. +// +// File - Platform independent interface for a File class +// +#ifndef WV_UTIL_FILE_STORE_H_ +#define WV_UTIL_FILE_STORE_H_ + +#include +#include +#include +#include + +#include "disallow_copy_and_assign.h" +#include "platform.h" +#include "util_common.h" + +namespace wvutil { + +// File class. The implementation is platform dependent. +class CORE_UTIL_EXPORT 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 CORE_UTIL_EXPORT 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 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 // WV_UTIL_FILE_STORE_H_ diff --git a/wvutil/include/file_utils.h b/wvutil/include/file_utils.h new file mode 100644 index 0000000..cb83af5 --- /dev/null +++ b/wvutil/include/file_utils.h @@ -0,0 +1,28 @@ +// 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 + +namespace wvutil { + +const char kCurrentDirectory[] = "."; +const char kParentDirectory[] = ".."; +const char kDirectoryDelimiter = '/'; +const char kWildcard[] = "*"; +bool IsCurrentOrParentDirectory(char* dir); + +class FileUtils { + public: + static bool Exists(const std::string& src); + // 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..a41174b --- /dev/null +++ b/wvutil/include/log.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. +// +// Log - Platform independent interface for a Logging class +// +#ifndef WV_UTIL_LOG_H_ +#define WV_UTIL_LOG_H_ + +#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. + LOG_SILENT = -1, + + LOG_ERROR = 0, + LOG_WARN = 1, + LOG_INFO = 2, + LOG_DEBUG = 3, + LOG_VERBOSE = 4, +} LogPriority; + +extern LogPriority g_cutoff; + +// 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. +CORE_UTIL_EXPORT void InitLogging(); + +CORE_UTIL_EXPORT void Log(const char* file, const char* function, int line, + LogPriority level, const char* fmt, ...); + +// Log APIs +#ifndef LOGE +#define LOGE(...) Log(__FILE__, __func__, __LINE__, \ + wvutil::LOG_ERROR, __VA_ARGS__) +#define LOGW(...) Log(__FILE__, __func__, __LINE__, \ + wvutil::LOG_WARN, __VA_ARGS__) +#define LOGI(...) Log(__FILE__, __func__, __LINE__, \ + wvutil::LOG_INFO, __VA_ARGS__) +#define LOGD(...) Log(__FILE__, __func__, __LINE__, \ + wvutil::LOG_DEBUG, __VA_ARGS__) +#define LOGV(...) Log(__FILE__, __func__, __LINE__, \ + wvutil::LOG_VERBOSE, __VA_ARGS__) +#endif +} // namespace wvutil + +#endif // WV_UTIL_LOG_H_ diff --git a/wvutil/include/platform.h b/wvutil/include/platform.h new file mode 100644 index 0000000..45fd147 --- /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 Master +// License Agreement. +// +// Platform - Abstracts some utilities between platforms. +// +#ifndef WV_UTIL_PLATFORM_H_ +#define WV_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); } +CORE_UTIL_EXPORT int setenv(const char* key, const char* value, int overwrite); +#else +# include +# include +# include +#endif + +#endif // WV_UTIL_PLATFORM_H_ diff --git a/wvutil/include/rw_lock.h b/wvutil/include/rw_lock.h new file mode 100644 index 0000000..0c4595e --- /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 Master +// License Agreement. + +#ifndef WVCAS_UTIL_RW_LOCK_H_ +#define WVCAS_UTIL_RW_LOCK_H_ + +#include + +#include +#include + +#include "disallow_copy_and_assign.h" +#include "util_common.h" + +namespace wvcas { + +// A simple reader-writer mutex implementation that mimics the one from C++17 +class CORE_UTIL_EXPORT 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 wvcas + +#endif // WVCAS_UTIL_RW_LOCK_H_ diff --git a/wvutil/include/string_conversions.h b/wvutil/include/string_conversions.h new file mode 100644 index 0000000..a68f491 --- /dev/null +++ b/wvutil/include/string_conversions.h @@ -0,0 +1,44 @@ +// 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 WVCAS_UTIL_STRING_CONVERSIONS_H_ +#define WVCAS_UTIL_STRING_CONVERSIONS_H_ + +#include +#include + +#include +#include + +#include "util_common.h" + +namespace wvutil { + +CORE_UTIL_EXPORT std::vector a2b_hex(const std::string& b); +CORE_UTIL_EXPORT std::vector a2b_hex(const std::string& label, + const std::string& b); +CORE_UTIL_EXPORT std::string a2bs_hex(const std::string& b); +CORE_UTIL_EXPORT std::string b2a_hex(const std::vector& b); +CORE_UTIL_EXPORT std::string b2a_hex(const std::string& b); +CORE_UTIL_EXPORT std::string Base64Encode( + const std::vector& bin_input); +CORE_UTIL_EXPORT std::vector Base64Decode( + const std::string& bin_input); +CORE_UTIL_EXPORT std::string Base64SafeEncode( + const std::vector& bin_input); +CORE_UTIL_EXPORT std::string Base64SafeEncodeNoPad( + const std::vector& bin_input); +CORE_UTIL_EXPORT std::vector Base64SafeDecode( + const std::string& bin_input); +CORE_UTIL_EXPORT std::string HexEncode(const uint8_t* bytes, unsigned size); +CORE_UTIL_EXPORT std::string IntToString(int value); +CORE_UTIL_EXPORT int64_t htonll64(int64_t x); +CORE_UTIL_EXPORT inline int64_t ntohll64(int64_t x) { return htonll64(x); } +CORE_UTIL_EXPORT std::string BytesToString(const uint8_t* bytes, unsigned size); +// Encode unsigned integer into a big endian formatted string +CORE_UTIL_EXPORT std::string EncodeUint32(unsigned int u); + +} // namespace wvutil + +#endif // WVCAS_UTIL_STRING_CONVERSIONS_H_ diff --git a/wvutil/include/timer.h b/wvutil/include/timer.h new file mode 100644 index 0000000..f414455 --- /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 wvcas + +#endif // TIMER_H_ diff --git a/wvutil/include/util_common.h b/wvutil/include/util_common.h new file mode 100644 index 0000000..9390618 --- /dev/null +++ b/wvutil/include/util_common.h @@ -0,0 +1,22 @@ +// 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 WV_UTIL_UTIL_COMMON_H_ +#define WV_UTIL_UTIL_COMMON_H_ + +#ifdef _WIN32 +# ifdef CORE_UTIL_IMPLEMENTATION +# define CORE_UTIL_EXPORT __declspec(dllexport) +# else +# define CORE_UTIL_EXPORT __declspec(dllimport) +# endif +#else +# ifdef CORE_UTIL_IMPLEMENTATION +# define CORE_UTIL_EXPORT __attribute__((visibility("default"))) +# else +# define CORE_UTIL_EXPORT +# endif +#endif + +#endif // WV_UTIL_UTIL_COMMON_H_ diff --git a/wvutil/src/android_properties.cpp b/wvutil/src/android_properties.cpp new file mode 100644 index 0000000..b78c8dc --- /dev/null +++ b/wvutil/src/android_properties.cpp @@ -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. + +#include "cas_properties.h" +#include "log.h" + +#include + +namespace { + +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; + } + char value[PROPERTY_VALUE_MAX]; + constexpr char key[] = "cas.widevine.oemcrypto.path"; + if (property_get(key, value, "libcasoemcrypto.so") <= 0) { + return false; + } + *path = value; + return true; +} + +} // namespace wvcas diff --git a/wvutil/src/cdm_random.cpp b/wvutil/src/cdm_random.cpp new file mode 100644 index 0000000..751836e --- /dev/null +++ b/wvutil/src/cdm_random.cpp @@ -0,0 +1,107 @@ +// 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. + +#include "cdm_random.h" + +#include + +#include + +#include "log.h" + +// This type alias is for convenience. +using CdmRandomLock = std::unique_lock; + +namespace wvcdm { + +namespace { +// More information about C++11's random number generators can be found +// from the introductory paper https://isocpp.org/files/papers/n3551.pdf + +// Attemps to get random data in a device specific manner. If the device +// does not support true random data, then a pseudo-random sequence might +// be used instead. The exact behaviour depends on the compiler and +// platform combination. +unsigned int GetDeviceRandomSeed() { + static std::random_device rdev; + static std::mutex rdev_mutex; + CdmRandomLock rdev_lock(rdev_mutex); + return rdev(); +} + +} // namespace + +// CdmRandomGenerator. + +CdmRandomGenerator::CdmRandomGenerator() : generator_(GetDeviceRandomSeed()) {} + +void CdmRandomGenerator::Seed() { + CdmRandomLock lock(generator_lock_); + generator_.seed(GetDeviceRandomSeed()); +} + +void CdmRandomGenerator::Seed(unsigned int s) { + CdmRandomLock lock(generator_lock_); + generator_.seed(s); +} + +unsigned int CdmRandomGenerator::Rand() { + CdmRandomLock lock(generator_lock_); + std::uniform_int_distribution dist(0, RAND_MAX); + return dist(generator_); +} + +uint64_t CdmRandomGenerator::RandomInRange(uint64_t lower, uint64_t upper) { + if (lower == upper) { + return lower; + } + CdmRandomLock lock(generator_lock_); + if (lower > upper) { + LOGW( + "Lower bound is larger than upper bound, swapping bounds: " + "lower = %llu, upper = %llu", + // Casting to insure this will work on 32-bit systems. + static_cast(lower), + static_cast(upper)); + std::swap(lower, upper); + } + std::uniform_int_distribution dist(lower, upper); + return dist(generator_); +} + +std::string CdmRandomGenerator::RandomData(size_t length) { + if (length > kMaxRandomDataLength) { + LOGE("Maximum random data length exceeded: length = %zu, max_length = %zu", + length, kMaxRandomDataLength); + return std::string(); + } + CdmRandomLock lock(generator_lock_); + std::uniform_int_distribution dist; // Range of [0, 255]. + std::string random_data(length, '\0'); + std::generate(random_data.begin(), random_data.end(), + [&]() { return dist(generator_); }); + return random_data; +} + +bool CdmRandomGenerator::RandomBool() { + CdmRandomLock lock(generator_lock_); + std::bernoulli_distribution dist; // 50/50. + return dist(generator_); +} + +// CdmRandom. + +// static +CdmRandomGenerator* CdmRandom::GetInstance() { + static std::mutex g_instance_lock; + static CdmRandomGenerator* g_instance = nullptr; + CdmRandomLock lock(g_instance_lock); + if (g_instance == nullptr) { + LOGV("Initalizing CDM random number generator"); + g_instance = new CdmRandomGenerator(GetDeviceRandomSeed()); + } + return g_instance; +} + +} // namespace wvcdm diff --git a/wvutil/src/clock.cpp b/wvutil/src/clock.cpp new file mode 100644 index 0000000..a7344cd --- /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 Master +// 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/dllmain.cpp b/wvutil/src/dllmain.cpp new file mode 100644 index 0000000..8edea4a --- /dev/null +++ b/wvutil/src/dllmain.cpp @@ -0,0 +1,11 @@ +// 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. +// +// dllmain - A dummy DllMain method for Windows DLLs. +// +#include + +BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved) { + return TRUE; +} \ No newline at end of file diff --git a/wvutil/src/file_store.cpp b/wvutil/src/file_store.cpp new file mode 100644 index 0000000..8a41015 --- /dev/null +++ b/wvutil/src/file_store.cpp @@ -0,0 +1,191 @@ +// 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. +// +// File class - provides a simple android specific file implementation + +#include "file_store.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "file_utils.h" +#include "log.h" +#include "string_conversions.h" + +#include +#include + +namespace wvutil { + +namespace { +const char kCertificateFileNamePrefix[] = "cert"; +const char kCertificateFileNameExt[] = ".bin"; +const char kCertificateFileName[] = "cert.bin"; + +std::string GetFileNameSafeHash(const std::string& input) { + std::vector hash(MD5_DIGEST_LENGTH); + const unsigned char* input_ptr = + reinterpret_cast(input.data()); + MD5(input_ptr, input.size(), &hash[0]); + return 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; + } + + if (dir_path.empty()) + return file_name; + else + return dir_path + kDirectoryDelimiter + file_name; +} +} // namespace + +class FileImpl : public File { + public: + FileImpl(FILE* file, const std::string& file_path) + : file_(file), file_path_(file_path) {} + + void FlushFile() { + fflush(file_); + fsync(fileno(file_)); + } + + ~FileImpl() { + if (file_) { + FlushFile(); + fclose(file_); + file_ = nullptr; + } + } + + ssize_t Read(char* buffer, size_t bytes) override { + if (!buffer) { + LOGW("File::Read: buffer is empty"); + return -1; + } + if (!file_) { + LOGW("File::Read: file not open"); + return -1; + } + size_t len = fread(buffer, sizeof(char), bytes, file_); + if (len != bytes) { + LOGW("File::Read: fread failed: %d, %s", errno, strerror(errno)); + } + return len; + } + + ssize_t Write(const char* buffer, size_t bytes) override { + if (!buffer) { + LOGW("File::Write: buffer is empty"); + return -1; + } + if (!file_) { + LOGW("File::Write: file not open"); + return -1; + } + size_t len = fwrite(buffer, sizeof(char), bytes, file_); + if (len != bytes) { + LOGW("File::Write: fwrite failed: %d, %s", errno, strerror(errno)); + } + FlushFile(); + return len; + } + + FILE* file_; + std::string file_path_; +}; + +class FileSystem::Impl {}; + +FileSystem::FileSystem() : FileSystem("", nullptr) {} +FileSystem::FileSystem(const std::string& origin, void* /* extra_data */) + : origin_(origin) {} + +FileSystem::~FileSystem() {} + +std::unique_ptr FileSystem::Open(const std::string& in_name, int flags) { + std::string open_flags; + + std::string name = GetFileNameForIdentifier(in_name, identifier_); + + // create the enclosing directory if it does not exist + size_t delimiter_pos = name.rfind(kDirectoryDelimiter); + if (delimiter_pos != std::string::npos) { + std::string dir_path = name.substr(0, delimiter_pos); + if ((flags & FileSystem::kCreate) && !Exists(dir_path)) + FileUtils::CreateDirectory(dir_path); + } + + // ensure only owners has access + mode_t old_mask = umask(077); + if (((flags & FileSystem::kTruncate) && Exists(name)) || + ((flags & FileSystem::kCreate) && !Exists(name))) { + FILE* fp = fopen(name.c_str(), "w+"); + if (fp) { + fclose(fp); + } + } + + open_flags = (flags & FileSystem::kReadOnly) ? "rb" : "rb+"; + + FILE* file = fopen(name.c_str(), open_flags.c_str()); + umask(old_mask); + if (!file) { + LOGW("File::Open: fopen failed: %d, %s", errno, strerror(errno)); + return nullptr; + } + + return std::unique_ptr(new FileImpl(file, name)); +} + +bool FileSystem::Exists(const std::string& path) { + return FileUtils::Exists(GetFileNameForIdentifier(path, identifier_)); +} + +bool FileSystem::Remove(const std::string& path) { + return FileUtils::Remove(GetFileNameForIdentifier(path, identifier_)); +} + +ssize_t FileSystem::FileSize(const std::string& in_path) { + std::string path = GetFileNameForIdentifier(in_path, identifier_); + struct stat buf; + if (stat(path.c_str(), &buf) == 0) + return buf.st_size; + else + 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..17050b0 --- /dev/null +++ b/wvutil/src/file_utils.cpp @@ -0,0 +1,228 @@ +// 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 "file_utils.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "file_store.h" +#include "log.h" + +namespace wvutil { + +bool IsCurrentOrParentDirectory(char* dir) { + return strcmp(dir, kCurrentDirectory) == 0 || + strcmp(dir, kParentDirectory) == 0; +} + +bool FileUtils::Exists(const std::string& path) { + struct stat buf; + int res = stat(path.c_str(), &buf) == 0; + if (!res) { + LOGV("File::Exists: stat failed: %d, %s", errno, strerror(errno)); + } + 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); + 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..c83cf97 --- /dev/null +++ b/wvutil/src/log.cpp @@ -0,0 +1,80 @@ +// 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. +// +// 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 1024 + +#include "log.h" +#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 { + +LogPriority g_cutoff = LOG_INFO; + +void InitLogging() {} + +void Log(const char* file, const char* function, int line, LogPriority level, + const char* format, ...) { + if (level > g_cutoff) return; + + const char* filename = strrchr(file, '/'); + filename = filename == nullptr ? file : filename + 1; + + 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 LOG_ERROR: prio = ANDROID_LOG_ERROR; break; + case LOG_WARN: prio = ANDROID_LOG_WARN; break; + case LOG_INFO: prio = ANDROID_LOG_INFO; break; + case LOG_DEBUG: prio = ANDROID_LOG_DEBUG; break; +#if LOG_NDEBUG + case LOG_VERBOSE: return; +#else + case LOG_VERBOSE: prio = ANDROID_LOG_VERBOSE; break; +#endif + } + + __android_log_write(prio, LOG_TAG, buf); +} + +} // namespace wvutil diff --git a/wvutil/src/platform.cpp b/wvutil/src/platform.cpp new file mode 100644 index 0000000..820a487 --- /dev/null +++ b/wvutil/src/platform.cpp @@ -0,0 +1,21 @@ +// 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 "platform.h" + +#include "stdlib.h" + +#ifdef _WIN32 + +int setenv(const char* key, const char* value, int overwrite) { + if (!overwrite) { + size_t size; + errno_t err = getenv_s(&size, nullptr, 0, key); + if (err != 0 || size != 0) + return err; // Return 0 if it exists, but don't change. + } + return _putenv_s(key, value); +} + +#endif diff --git a/wvutil/src/rw_lock.cpp b/wvutil/src/rw_lock.cpp new file mode 100644 index 0000000..8a4a24c --- /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 Master +// License Agreement. + +#include "rw_lock.h" + +#include "log.h" + +namespace wvcas { + +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 wvcas diff --git a/wvutil/src/string_conversions.cpp b/wvutil/src/string_conversions.cpp new file mode 100644 index 0000000..3558e88 --- /dev/null +++ b/wvutil/src/string_conversions.cpp @@ -0,0 +1,313 @@ +// 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 "string_conversions.h" + +#include +#include +#include +#include + +#include +#include + +#include "log.h" +#include "platform.h" + +namespace wvutil { + +static const char kBase64Codes[] = + "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) + +int DecodeBase64Char(char c) { + const char* it = strchr(kBase64Codes, c); + if (it == nullptr) return -1; + return it - kBase64Codes; +} + +bool DecodeHexChar(char ch, unsigned char* digit) { + if (ch >= '0' && ch <= '9') { + *digit = ch - '0'; + } else { + ch = tolower(ch); + if ((ch >= 'a') && (ch <= 'f')) { + *digit = ch - 'a' + 10; + } else { + return false; + } + } + return true; +} + +// 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; + unsigned int count = byte.size(); + if (count == 0 || (count % 2) != 0) { + LOGE("Invalid input size %u for string %s", count, byte.c_str()); + return array; + } + + for (unsigned int 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 %d", 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 b2a_hex(const std::string& byte) { + if (byte.empty()) return ""; + return HexEncode(reinterpret_cast(byte.data()), + byte.length()); +} + +// 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 -----------------> | +std::string Base64Encode(const std::vector& bin_input) { + if (bin_input.empty()) { + return std::string(); + } + + // |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(bin_input.size(), 3) * 4; + std::string result(out_size, '\0'); + for (size_t i = 0; i < bin_input.size(); i++) { + // "insert" 8-bits of data + temp |= (bin_input[i] << ((2 - (i % 3)) * 8)); + + if (i % 3 == 2) { + result[out_index++] = kBase64Codes[GET_BITS(temp, 18, 24)]; + result[out_index++] = kBase64Codes[GET_BITS(temp, 12, 18)]; + result[out_index++] = kBase64Codes[GET_BITS(temp, 6, 12)]; + result[out_index++] = kBase64Codes[GET_BITS(temp, 0, 6)]; + temp = 0; + } + } + + if (bin_input.size() % 3 == 1) { + result[out_index++] = kBase64Codes[GET_BITS(temp, 18, 24)]; + result[out_index++] = kBase64Codes[GET_BITS(temp, 12, 18)]; + result[out_index++] = '='; + result[out_index++] = '='; + } else if (bin_input.size() % 3 == 2) { + result[out_index++] = kBase64Codes[GET_BITS(temp, 18, 24)]; + result[out_index++] = kBase64Codes[GET_BITS(temp, 12, 18)]; + result[out_index++] = kBase64Codes[GET_BITS(temp, 6, 12)]; + result[out_index++] = '='; + } + + return result; +} + +// Filename-friendly base64 encoding (RFC4648), commonly referred to +// as Base64WebSafeEncode. +// +// 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(); + } + + std::string ret = Base64Encode(bin_input); + for (size_t i = 0; i < ret.size(); i++) { + if (ret[i] == '+') + ret[i] = '-'; + else if (ret[i] == '/') + ret[i] = '_'; + } + return ret; +} + +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; +} + +// Decode for standard base64 encoding (RFC4648). +std::vector Base64Decode(const std::string& b64_input) { + if (b64_input.empty()) { + return std::vector(); + } + + const size_t out_size_max = CEIL_DIVIDE(b64_input.size() * 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 < b64_input.size(); i++) { + if (b64_input[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 < b64_input.size(); j++) { + if (b64_input[j] != '=') { + LOGE("base64Decode failed"); + return std::vector(); + } + } + break; + } + + const int decoded = DecodeBase64Char(b64_input[i]); + 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; +} + +// Decode for Filename-friendly base64 encoding (RFC4648), commonly referred +// as Base64WebSafeDecode. Add padding if needed. +std::vector Base64SafeDecode(const std::string& b64_input) { + if (b64_input.empty()) { + return std::vector(); + } + + // Make a copy so we can modify it to replace the web-safe special characters + // with the normal ones. + std::string input_copy = b64_input; + for (size_t i = 0; i < input_copy.size(); i++) { + if (input_copy[i] == '-') + input_copy[i] = '+'; + else if (input_copy[i] == '_') + input_copy[i] = '/'; + } + return Base64Decode(input_copy); +} + +std::string HexEncode(const uint8_t* in_buffer, unsigned int 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; +} + +std::string IntToString(int value) { + // log10(2) ~= 0.3 bytes needed per bit or per byte log10(2**8) ~= 2.4. + // So round up to allocate 3 output characters per byte, plus 1 for '-'. + const int kOutputBufSize = 3 * sizeof(int) + 1; + char buffer[kOutputBufSize]; + memset(buffer, 0, kOutputBufSize); + snprintf(buffer, kOutputBufSize, "%d", value); + + std::string out_string(buffer); + return out_string; +} + +int64_t htonll64(int64_t x) { // Convert to big endian (network-byte-order) + 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; + } +} + +std::string BytesToString(const uint8_t* bytes, unsigned size) { + if (!bytes || !size) return ""; + const char* char_bytes = reinterpret_cast(bytes); + return std::string(char_bytes, char_bytes + size); +} + +// Encode unsigned integer into a big endian formatted string +std::string EncodeUint32(unsigned int 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; +} + +} // 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