V18.4.0 CAS plugin
Note that this version does not have Widevine Provisioning 4.0 support. It is only suitable for device upgrades. A new patch with provisioning 4.0 support will be made later.
This commit is contained in:
41
Android.bp
Normal file
41
Android.bp
Normal file
@@ -0,0 +1,41 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// CAS top level makefile
|
||||
//
|
||||
|
||||
subdirs = ["wvutil", "protos", "plugin"]
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Builds libwvmediacas.so
|
||||
// Generates *.a, *.pb.h and *.pb.cc for *.proto files.
|
||||
//
|
||||
cc_library_shared {
|
||||
|
||||
name: "libwvmediacas",
|
||||
|
||||
proprietary: true,
|
||||
relative_install_path: "mediacas",
|
||||
|
||||
shared_libs: [
|
||||
"libcrypto",
|
||||
"libcutils",
|
||||
"liblog",
|
||||
"libprotobuf-cpp-lite",
|
||||
"libutils",
|
||||
"libhidlbase",
|
||||
],
|
||||
|
||||
header_libs: ["media_plugin_headers"],
|
||||
|
||||
static_libs: [
|
||||
"//vendor/widevine/libwvmediacas/protos:libcas_protos",
|
||||
"//vendor/widevine/libwvmediacas/wvutil:libcasutil",
|
||||
],
|
||||
|
||||
whole_static_libs: [
|
||||
"//vendor/widevine/libwvmediacas/plugin:libwvcasplugins",
|
||||
],
|
||||
|
||||
proto: {
|
||||
type: "lite",
|
||||
},
|
||||
}
|
||||
1
README
1
README
@@ -0,0 +1 @@
|
||||
This repo contains Widevine MediaCas client code that works with Android U.
|
||||
|
||||
5
oemcrypto/Android.bp
Normal file
5
oemcrypto/Android.bp
Normal file
@@ -0,0 +1,5 @@
|
||||
cc_library_headers {
|
||||
name: "oemcastroheaders",
|
||||
export_include_dirs: ["include"],
|
||||
proprietary: true,
|
||||
}
|
||||
5869
oemcrypto/include/OEMCryptoCENC.h
Normal file
5869
oemcrypto/include/OEMCryptoCENC.h
Normal file
File diff suppressed because it is too large
Load Diff
262
oemcrypto/include/OEMCryptoCENCCommon.h
Normal file
262
oemcrypto/include/OEMCryptoCENCCommon.h
Normal file
@@ -0,0 +1,262 @@
|
||||
// Copyright 2019 Google LLC. All rights reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine
|
||||
// License Agreement.
|
||||
|
||||
/*********************************************************************
|
||||
* OEMCryptoCENCCommon.h
|
||||
*
|
||||
* Common structures and error codes between WV servers and OEMCrypto.
|
||||
*
|
||||
*********************************************************************/
|
||||
|
||||
#ifndef WIDEVINE_ODK_INCLUDE_OEMCRYPTOCENCCOMMON_H_
|
||||
#define WIDEVINE_ODK_INCLUDE_OEMCRYPTOCENCCOMMON_H_
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/// @addtogroup common_types
|
||||
/// @{
|
||||
|
||||
/* clang-format off */
|
||||
/** Error and result codes returned by OEMCrypto functions. */
|
||||
typedef enum OEMCryptoResult {
|
||||
OEMCrypto_SUCCESS = 0,
|
||||
OEMCrypto_ERROR_INIT_FAILED = 1,
|
||||
OEMCrypto_ERROR_TERMINATE_FAILED = 2,
|
||||
OEMCrypto_ERROR_OPEN_FAILURE = 3,
|
||||
OEMCrypto_ERROR_CLOSE_FAILURE = 4,
|
||||
OEMCrypto_ERROR_ENTER_SECURE_PLAYBACK_FAILED = 5, /* deprecated */
|
||||
OEMCrypto_ERROR_EXIT_SECURE_PLAYBACK_FAILED = 6, /* deprecated */
|
||||
OEMCrypto_ERROR_SHORT_BUFFER = 7,
|
||||
OEMCrypto_ERROR_NO_DEVICE_KEY = 8, /* no keybox device key. */
|
||||
OEMCrypto_ERROR_NO_ASSET_KEY = 9,
|
||||
OEMCrypto_ERROR_KEYBOX_INVALID = 10,
|
||||
OEMCrypto_ERROR_NO_KEYDATA = 11,
|
||||
OEMCrypto_ERROR_NO_CW = 12,
|
||||
OEMCrypto_ERROR_DECRYPT_FAILED = 13,
|
||||
OEMCrypto_ERROR_WRITE_KEYBOX = 14,
|
||||
OEMCrypto_ERROR_WRAP_KEYBOX = 15,
|
||||
OEMCrypto_ERROR_BAD_MAGIC = 16,
|
||||
OEMCrypto_ERROR_BAD_CRC = 17,
|
||||
OEMCrypto_ERROR_NO_DEVICEID = 18,
|
||||
OEMCrypto_ERROR_RNG_FAILED = 19,
|
||||
OEMCrypto_ERROR_RNG_NOT_SUPPORTED = 20,
|
||||
OEMCrypto_ERROR_SETUP = 21,
|
||||
OEMCrypto_ERROR_OPEN_SESSION_FAILED = 22,
|
||||
OEMCrypto_ERROR_CLOSE_SESSION_FAILED = 23,
|
||||
OEMCrypto_ERROR_INVALID_SESSION = 24,
|
||||
OEMCrypto_ERROR_NOT_IMPLEMENTED = 25,
|
||||
OEMCrypto_ERROR_NO_CONTENT_KEY = 26,
|
||||
OEMCrypto_ERROR_CONTROL_INVALID = 27,
|
||||
OEMCrypto_ERROR_UNKNOWN_FAILURE = 28,
|
||||
OEMCrypto_ERROR_INVALID_CONTEXT = 29,
|
||||
OEMCrypto_ERROR_SIGNATURE_FAILURE = 30,
|
||||
OEMCrypto_ERROR_TOO_MANY_SESSIONS = 31,
|
||||
OEMCrypto_ERROR_INVALID_NONCE = 32,
|
||||
OEMCrypto_ERROR_TOO_MANY_KEYS = 33,
|
||||
OEMCrypto_ERROR_DEVICE_NOT_RSA_PROVISIONED = 34,
|
||||
OEMCrypto_ERROR_INVALID_RSA_KEY = 35, /* deprecated */
|
||||
OEMCrypto_ERROR_KEY_EXPIRED = 36,
|
||||
OEMCrypto_ERROR_INSUFFICIENT_RESOURCES = 37,
|
||||
OEMCrypto_ERROR_INSUFFICIENT_HDCP = 38,
|
||||
OEMCrypto_ERROR_BUFFER_TOO_LARGE = 39,
|
||||
OEMCrypto_WARNING_GENERATION_SKEW = 40, /* Warning, not error. */
|
||||
OEMCrypto_ERROR_GENERATION_SKEW = 41,
|
||||
OEMCrypto_LOCAL_DISPLAY_ONLY = 42, /* Info, not an error. */
|
||||
OEMCrypto_ERROR_ANALOG_OUTPUT = 43,
|
||||
OEMCrypto_ERROR_WRONG_PST = 44,
|
||||
OEMCrypto_ERROR_WRONG_KEYS = 45,
|
||||
OEMCrypto_ERROR_MISSING_MASTER = 46,
|
||||
OEMCrypto_ERROR_LICENSE_INACTIVE = 47,
|
||||
OEMCrypto_ERROR_ENTRY_NEEDS_UPDATE = 48,
|
||||
OEMCrypto_ERROR_ENTRY_IN_USE = 49,
|
||||
OEMCrypto_ERROR_USAGE_TABLE_UNRECOVERABLE = 50, /* Obsolete. Don't use. */
|
||||
/* Use OEMCrypto_ERROR_NO_CONTENT_KEY instead of KEY_NOT_LOADED. */
|
||||
OEMCrypto_KEY_NOT_LOADED = 51, /* Obsolete. */
|
||||
OEMCrypto_KEY_NOT_ENTITLED = 52,
|
||||
OEMCrypto_ERROR_BAD_HASH = 53,
|
||||
OEMCrypto_ERROR_OUTPUT_TOO_LARGE = 54,
|
||||
OEMCrypto_ERROR_SESSION_LOST_STATE = 55,
|
||||
OEMCrypto_ERROR_SYSTEM_INVALIDATED = 56,
|
||||
OEMCrypto_ERROR_LICENSE_RELOAD = 57,
|
||||
OEMCrypto_ERROR_MULTIPLE_USAGE_ENTRIES = 58,
|
||||
OEMCrypto_WARNING_MIXED_OUTPUT_PROTECTION = 59,
|
||||
OEMCrypto_ERROR_INVALID_ENTITLED_KEY_SESSION = 60,
|
||||
OEMCrypto_ERROR_NEEDS_KEYBOX_PROVISIONING = 61,
|
||||
OEMCrypto_ERROR_UNSUPPORTED_CIPHER = 62,
|
||||
OEMCrypto_ERROR_DVR_FORBIDDEN = 63,
|
||||
OEMCrypto_ERROR_INSUFFICIENT_PRIVILEGE = 64,
|
||||
OEMCrypto_ERROR_INVALID_KEY = 65,
|
||||
/* ODK return values */
|
||||
ODK_ERROR_BASE = 1000,
|
||||
ODK_ERROR_CORE_MESSAGE = ODK_ERROR_BASE,
|
||||
ODK_SET_TIMER = ODK_ERROR_BASE + 1,
|
||||
ODK_DISABLE_TIMER = ODK_ERROR_BASE + 2,
|
||||
ODK_TIMER_EXPIRED = ODK_ERROR_BASE + 3,
|
||||
ODK_UNSUPPORTED_API = ODK_ERROR_BASE + 4,
|
||||
ODK_STALE_RENEWAL = ODK_ERROR_BASE + 5,
|
||||
/* OPK return values */
|
||||
OPK_ERROR_BASE = 2000,
|
||||
OPK_ERROR_REMOTE_CALL = OPK_ERROR_BASE,
|
||||
OPK_ERROR_INCOMPATIBLE_VERSION = OPK_ERROR_BASE + 1,
|
||||
OPK_ERROR_NO_PERSISTENT_DATA = OPK_ERROR_BASE + 2,
|
||||
OPK_ERROR_PREHOOK_FAILURE = OPK_ERROR_BASE + 3,
|
||||
OPK_ERROR_POSTHOOK_FAILURE = OPK_ERROR_BASE + 4,
|
||||
} OEMCryptoResult;
|
||||
/* clang-format on */
|
||||
|
||||
/**
|
||||
* Valid values for status in the usage table.
|
||||
*/
|
||||
typedef enum OEMCrypto_Usage_Entry_Status {
|
||||
kUnused = 0,
|
||||
kActive = 1,
|
||||
kInactive = 2, /* Deprecated. Use kInactiveUsed or kInactiveUnused. */
|
||||
kInactiveUsed = 3,
|
||||
kInactiveUnused = 4,
|
||||
} OEMCrypto_Usage_Entry_Status;
|
||||
|
||||
/* Not used publicly. Not documented with Doxygen. */
|
||||
typedef enum OEMCrypto_ProvisioningRenewalType {
|
||||
OEMCrypto_NoRenewal = 0,
|
||||
OEMCrypto_RenewalACert = 1,
|
||||
} OEMCrypto_ProvisioningRenewalType;
|
||||
|
||||
/**
|
||||
* OEMCrypto_LicenseType is used in the license message to indicate if the key
|
||||
* objects are for content keys, or for entitlement keys.
|
||||
*/
|
||||
typedef enum OEMCrypto_LicenseType {
|
||||
OEMCrypto_ContentLicense = 0,
|
||||
OEMCrypto_EntitlementLicense = 1,
|
||||
OEMCrypto_LicenseType_MaxValue = OEMCrypto_EntitlementLicense,
|
||||
} OEMCrypto_LicenseType;
|
||||
|
||||
/**
|
||||
* Private key type used in the provisioning response.
|
||||
*/
|
||||
typedef enum OEMCrypto_PrivateKeyType {
|
||||
OEMCrypto_RSA_Private_Key = 0,
|
||||
OEMCrypto_ECC_Private_Key = 1,
|
||||
OEMCrypto_PrivateKeyType_MaxValue = OEMCrypto_ECC_Private_Key,
|
||||
} OEMCrypto_PrivateKeyType;
|
||||
|
||||
/**
|
||||
* The base for (delayed) timers, i.e. from what time the (delayed) timer
|
||||
* starts.
|
||||
*/
|
||||
typedef enum OEMCrypto_TimerDelayBase {
|
||||
OEMCrypto_License_Start = 0,
|
||||
OEMCrypto_License_Load = 1,
|
||||
OEMCrypto_First_Decrypt = 2,
|
||||
OEMCrypto_TimerDelayBase_MaxValue = OEMCrypto_First_Decrypt,
|
||||
} OEMCrypto_TimerDelayBase;
|
||||
|
||||
/**
|
||||
* Used to indicate a substring of a signed message in ODK_ParseLicense
|
||||
* and other functions which must verify that a parameter is contained within a
|
||||
* signed message.
|
||||
*/
|
||||
typedef struct {
|
||||
size_t offset;
|
||||
size_t length;
|
||||
} OEMCrypto_Substring;
|
||||
|
||||
/**
|
||||
* Used to specify information about CMI Descriptor 0.
|
||||
* @param id: ID value of CMI Descriptor assigned by DTLA.
|
||||
* @param length: byte length of the usage rules field.
|
||||
* @param data: usage rules data.
|
||||
*/
|
||||
typedef struct {
|
||||
uint8_t id; // 0x00
|
||||
uint8_t extension; // 0x00
|
||||
uint16_t length; // 0x01
|
||||
uint8_t data;
|
||||
} OEMCrypto_DTCP2_CMI_Descriptor_0;
|
||||
|
||||
/**
|
||||
* Used to specify information about CMI Descriptor 1.
|
||||
* @param id: ID value of CMI Descriptor assigned by DTLA.
|
||||
* @param extension: specified by the CMI descriptor
|
||||
* @param length: byte length of the usage rules field.
|
||||
* @param data: usage rules data.
|
||||
*/
|
||||
typedef struct {
|
||||
uint8_t id; // 0x01
|
||||
uint8_t extension; // 0x00
|
||||
uint16_t length; // 0x03
|
||||
uint8_t data[3];
|
||||
} OEMCrypto_DTCP2_CMI_Descriptor_1;
|
||||
|
||||
/**
|
||||
* Used to specify information about CMI Descriptor 2.
|
||||
* @param id: ID value of CMI Descriptor assigned by DTLA.
|
||||
* @param extension: specified by the CMI descriptor
|
||||
* @param length: byte length of the usage rules field.
|
||||
* @param data: usage rules data.
|
||||
*/
|
||||
typedef struct {
|
||||
uint8_t id; // 0x02
|
||||
uint8_t extension; // 0x00
|
||||
uint16_t length; // 0x03
|
||||
uint8_t data[3];
|
||||
} OEMCrypto_DTCP2_CMI_Descriptor_2;
|
||||
|
||||
/**
|
||||
* Used to specify the required DTCP2 level. If dtcp2_required is 0, there are
|
||||
* no requirements on any of the keys. If dtcp2_required is 1, any key with the
|
||||
* kControlHDCPRequired bit set requires DTCP2 in its output.
|
||||
* @param dtcp2_required: specifies whether dtcp2 is required. 0 = not required,
|
||||
* 1 = DTCP2 required.
|
||||
* @param cmi_descriptor_1: three bytes of CMI descriptor 1
|
||||
*/
|
||||
typedef struct {
|
||||
uint8_t dtcp2_required; // 0 = not required. 1 = DTCP2 v1 required.
|
||||
OEMCrypto_DTCP2_CMI_Descriptor_0 cmi_descriptor_0;
|
||||
OEMCrypto_DTCP2_CMI_Descriptor_1 cmi_descriptor_1;
|
||||
OEMCrypto_DTCP2_CMI_Descriptor_2 cmi_descriptor_2;
|
||||
} OEMCrypto_DTCP2_CMI_Packet;
|
||||
|
||||
/**
|
||||
* Points to the relevant fields for a content key. The fields are extracted
|
||||
* from the License Response message offered to ODK_ParseLicense(). Each
|
||||
* field points to one of the components of the key. Key data, key control,
|
||||
* and both IV fields are 128 bits (16 bytes):
|
||||
* @param key_id: the unique id of this key.
|
||||
* @param key_id_length: the size of key_id. OEMCrypto may assume this is at
|
||||
* most 16. However, OEMCrypto shall correctly handle key id lengths
|
||||
* from 1 to 16 bytes.
|
||||
* @param key_data_iv: the IV for performing AES-128-CBC decryption of the
|
||||
* key_data field.
|
||||
* @param key_data - the key data. It is encrypted (AES-128-CBC) with the
|
||||
* session's derived encrypt key and the key_data_iv.
|
||||
* @param key_control_iv: the IV for performing AES-128-CBC decryption of the
|
||||
* key_control field.
|
||||
* @param key_control: the key control block. It is encrypted (AES-128-CBC) with
|
||||
* the content key from the key_data field.
|
||||
*
|
||||
* The memory for the OEMCrypto_KeyObject fields is allocated and freed
|
||||
* by the caller of ODK_ParseLicense().
|
||||
*/
|
||||
typedef struct {
|
||||
OEMCrypto_Substring key_id;
|
||||
OEMCrypto_Substring key_data_iv;
|
||||
OEMCrypto_Substring key_data;
|
||||
OEMCrypto_Substring key_control_iv;
|
||||
OEMCrypto_Substring key_control;
|
||||
} OEMCrypto_KeyObject;
|
||||
|
||||
/// @}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // WIDEVINE_ODK_INCLUDE_OEMCRYPTOCENCCOMMON_H_
|
||||
612
oemcrypto/include/level3.h
Normal file
612
oemcrypto/include/level3.h
Normal file
@@ -0,0 +1,612 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine
|
||||
// License Agreement.
|
||||
|
||||
/*********************************************************************
|
||||
* level3.h
|
||||
*
|
||||
* Reference APIs needed to support Widevine's crypto algorithms.
|
||||
*********************************************************************/
|
||||
|
||||
#ifndef LEVEL3_OEMCRYPTO_H_
|
||||
#define LEVEL3_OEMCRYPTO_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "OEMCryptoCENC.h"
|
||||
#include "level3_file_system.h"
|
||||
|
||||
// clang-format off
|
||||
#ifdef DYNAMIC_ADAPTER
|
||||
#define Level3_IsInApp _lcc00
|
||||
#define Level3_Initialize _lcc01
|
||||
#define Level3_Terminate _lcc02
|
||||
#define Level3_InstallKeyboxOrOEMCert _lcc03
|
||||
#define Level3_GetKeyData _lcc04
|
||||
#define Level3_IsKeyboxOrOEMCertValid _lcc05
|
||||
#define Level3_GetDeviceID _lcc07
|
||||
#define Level3_WrapKeyboxOrOEMCert _lcc08
|
||||
#define Level3_OpenSession _lcc09
|
||||
#define Level3_CloseSession _lcc10
|
||||
#define Level3_GenerateSignature _lcc13
|
||||
#define Level3_GenerateNonce _lcc14
|
||||
#define Level3_RewrapDeviceRSAKey _lcc18
|
||||
#define Level3_LoadDeviceRSAKey _lcc19
|
||||
#define Level3_DeriveKeysFromSessionKey _lcc21
|
||||
#define Level3_APIVersion _lcc22
|
||||
#define Level3_Generic_Encrypt_V17 _lcc24
|
||||
#define Level3_Generic_Decrypt_V17 _lcc25
|
||||
#define Level3_Generic_Sign_V17 _lcc26
|
||||
#define Level3_Generic_Verify_V17 _lcc27
|
||||
#define Level3_SupportsUsageTable _lcc29
|
||||
#define Level3_ReportUsage _lcc32
|
||||
#define Level3_GetMaxNumberOfSessions _lcc37
|
||||
#define Level3_GetNumberOfOpenSessions _lcc38
|
||||
#define Level3_IsAntiRollbackHwPresent _lcc39
|
||||
#define Level3_QueryKeyControl _lcc41
|
||||
#define Level3_GetHDCPCapability _lcc44
|
||||
#define Level3_LoadTestRSAKey _lcc45
|
||||
#define Level3_SecurityPatchLevel _lcc46
|
||||
#define Level3_GetProvisioningMethod _lcc49
|
||||
#define Level3_RewrapDeviceRSAKey30 _lcc51
|
||||
#define Level3_SupportedCertificates _lcc52
|
||||
#define Level3_IsSRMUpdateSupported _lcc53
|
||||
#define Level3_GetCurrentSRMVersion _lcc54
|
||||
#define Level3_LoadSRM _lcc55
|
||||
#define Level3_RemoveSRM _lcc57
|
||||
#define Level3_CreateUsageTableHeader _lcc61
|
||||
#define Level3_LoadUsageTableHeader _lcc62
|
||||
#define Level3_CreateNewUsageEntry _lcc63
|
||||
#define Level3_LoadUsageEntry _lcc64
|
||||
#define Level3_UpdateUsageEntry _lcc65
|
||||
#define Level3_ShrinkUsageTableHeader _lcc67
|
||||
#define Level3_MoveEntry _lcc68
|
||||
#define Level3_GetAnalogOutputFlags _lcc71
|
||||
#define Level3_LoadTestKeybox _lcc78
|
||||
#define Level3_SelectKey _lcc81
|
||||
#define Level3_LoadKeys _lcc83
|
||||
#define Level3_SetSandbox _lcc84
|
||||
#define Level3_ResourceRatingTier _lcc85
|
||||
#define Level3_SupportsDecryptHash _lcc86
|
||||
#define Level3_SetDecryptHash _lcc88
|
||||
#define Level3_GetHashErrorCode _lcc89
|
||||
#define Level3_RefreshKeys _lcc91
|
||||
#define Level3_LoadEntitledContentKeys_V16 _lcc92
|
||||
#define Level3_CopyBuffer _lcc93
|
||||
#define Level3_MaximumUsageTableHeaderSize _lcc94
|
||||
#define Level3_GenerateDerivedKeys _lcc95
|
||||
#define Level3_PrepAndSignLicenseRequest _lcc96
|
||||
#define Level3_PrepAndSignRenewalRequest _lcc97
|
||||
#define Level3_PrepAndSignProvisioningRequest _lcc98
|
||||
#define Level3_LoadLicense _lcc99
|
||||
#define Level3_LoadRenewal _lcc101
|
||||
#define Level3_LoadProvisioning _lcc102
|
||||
#define Level3_LoadOEMPrivateKey _lcc103
|
||||
#define Level3_GetOEMPublicCertificate _lcc104
|
||||
#define Level3_DecryptCENC_V17 _lcc105
|
||||
#define Level3_LoadDRMPrivateKey _lcc107
|
||||
#define Level3_MinorAPIVersion _lcc108
|
||||
#define Level3_AllocateSecureBuffer _lcc109
|
||||
#define Level3_FreeSecureBuffer _lcc110
|
||||
#define Level3_CreateEntitledKeySession _lcc111
|
||||
#define Level3_RemoveEntitledKeySession _lcc112
|
||||
#define Level3_GetBootCertificateChain _lcc116
|
||||
#define Level3_GenerateCertificateKeyPair _lcc117
|
||||
#define Level3_InstallOemPrivateKey _lcc118
|
||||
#define Level3_ReassociateEntitledKeySession _lcc119
|
||||
#define Level3_LoadCasECMKeys _lcc120
|
||||
#define Level3_LoadEntitledContentKeys _lcc121 // place holder for v17.
|
||||
#define Level3_ProductionReady _lcc122
|
||||
#define Level3_Idle _lcc123
|
||||
#define Level3_Wake _lcc124
|
||||
#define Level3_BuildInformation _lcc125
|
||||
#define Level3_SecurityLevel _lcc126
|
||||
#define Level3_ReuseUsageEntry _lcc127
|
||||
#define Level3_GetDTCP2Capability _lcc128
|
||||
#define Level3_GetWatermarkingSupport _lcc129
|
||||
#define Level3_GetOEMKeyToken _lcc130
|
||||
#define Level3_GetDeviceInformation _lcc131
|
||||
#define Level3_SetMaxAPIVersion _lcc132
|
||||
#define Level3_GetKeyHandle _lcc133
|
||||
#define Level3_DecryptCENC _lcc134
|
||||
#define Level3_Generic_Encrypt _lcc135
|
||||
#define Level3_Generic_Decrypt _lcc136
|
||||
#define Level3_Generic_Sign _lcc137
|
||||
#define Level3_Generic_Verify _lcc138
|
||||
#define Level3_GetSignatureHashAlgorithm _lcc139
|
||||
#define Level3_EnterTestMode _lcc140
|
||||
#define Level3_GetDeviceSignedCsrPayload _lcc141
|
||||
#define Level3_GetEmbeddedDrmCertificate _lcc143
|
||||
#else
|
||||
#define Level3_Initialize _oecc01
|
||||
#define Level3_Terminate _oecc02
|
||||
#define Level3_InstallKeyboxOrOEMCert _oecc03
|
||||
#define Level3_GetKeyData _oecc04
|
||||
#define Level3_IsKeyboxOrOEMCertValid _oecc05
|
||||
#define Level3_GetDeviceID _oecc07
|
||||
#define Level3_WrapKeyboxOrOEMCert _oecc08
|
||||
#define Level3_OpenSession _oecc09
|
||||
#define Level3_CloseSession _oecc10
|
||||
#define Level3_GenerateSignature _oecc13
|
||||
#define Level3_GenerateNonce _oecc14
|
||||
#define Level3_RewrapDeviceRSAKey _oecc18
|
||||
#define Level3_LoadDeviceRSAKey _oecc19
|
||||
#define Level3_DeriveKeysFromSessionKey _oecc21
|
||||
#define Level3_APIVersion _oecc22
|
||||
#define Level3_Generic_Encrypt_V17 _oecc24
|
||||
#define Level3_Generic_Decrypt_V17 _oecc25
|
||||
#define Level3_Generic_Sign_V17 _oecc26
|
||||
#define Level3_Generic_Verify_V17 _oecc27
|
||||
#define Level3_SupportsUsageTable _oecc29
|
||||
#define Level3_ReportUsage _oecc32
|
||||
#define Level3_GenerateRSASignature _oecc36
|
||||
#define Level3_GetMaxNumberOfSessions _oecc37
|
||||
#define Level3_GetNumberOfOpenSessions _oecc38
|
||||
#define Level3_IsAntiRollbackHwPresent _oecc39
|
||||
#define Level3_QueryKeyControl _oecc41
|
||||
#define Level3_GetHDCPCapability _oecc44
|
||||
#define Level3_LoadTestRSAKey _oecc45
|
||||
#define Level3_SecurityPatchLevel _oecc46
|
||||
#define Level3_GetProvisioningMethod _oecc49
|
||||
#define Level3_RewrapDeviceRSAKey30 _oecc51
|
||||
#define Level3_SupportedCertificates _oecc52
|
||||
#define Level3_IsSRMUpdateSupported _oecc53
|
||||
#define Level3_GetCurrentSRMVersion _oecc54
|
||||
#define Level3_LoadSRM _oecc55
|
||||
#define Level3_RemoveSRM _oecc57
|
||||
#define Level3_CreateUsageTableHeader _oecc61
|
||||
#define Level3_LoadUsageTableHeader _oecc62
|
||||
#define Level3_CreateNewUsageEntry _oecc63
|
||||
#define Level3_LoadUsageEntry _oecc64
|
||||
#define Level3_UpdateUsageEntry _oecc65
|
||||
#define Level3_DeactivateUsageEntry _oecc66
|
||||
#define Level3_ShrinkUsageTableHeader _oecc67
|
||||
#define Level3_MoveEntry _oecc68
|
||||
#define Level3_GetAnalogOutputFlags _oecc71
|
||||
#define Level3_LoadTestKeybox _oecc78
|
||||
#define Level3_SelectKey _oecc81
|
||||
#define Level3_LoadKeys _oecc83
|
||||
#define Level3_SetSandbox _oecc84
|
||||
#define Level3_ResourceRatingTier _oecc85
|
||||
#define Level3_SupportsDecryptHash _oecc86
|
||||
#define Level3_SetDecryptHash _oecc88
|
||||
#define Level3_GetHashErrorCode _oecc89
|
||||
#define Level3_RefreshKeys _oecc91
|
||||
#define Level3_LoadEntitledContentKeys_V16 _oecc92
|
||||
#define Level3_CopyBuffer _oecc93
|
||||
#define Level3_MaximumUsageTableHeaderSize _oecc94
|
||||
#define Level3_GenerateDerivedKeys _oecc95
|
||||
#define Level3_PrepAndSignLicenseRequest _oecc96
|
||||
#define Level3_PrepAndSignRenewalRequest _oecc97
|
||||
#define Level3_PrepAndSignProvisioningRequest _oecc98
|
||||
#define Level3_LoadLicense _oecc99
|
||||
#define Level3_LoadRenewal _oecc101
|
||||
#define Level3_LoadProvisioning _oecc102
|
||||
#define Level3_LoadOEMPrivateKey _oecc103
|
||||
#define Level3_GetOEMPublicCertificate _oecc104
|
||||
#define Level3_DecryptCENC_V17 _oecc105
|
||||
#define Level3_LoadDRMPrivateKey _oecc107
|
||||
#define Level3_MinorAPIVersion _oecc108
|
||||
#define Level3_AllocateSecureBuffer _oecc109
|
||||
#define Level3_FreeSecureBuffer _oecc110
|
||||
#define Level3_CreateEntitledKeySession _oecc111
|
||||
#define Level3_RemoveEntitledKeySession _oecc112
|
||||
#define Level3_GetBootCertificateChain _oecc116
|
||||
#define Level3_GenerateCertificateKeyPair _oecc117
|
||||
#define Level3_InstallOemPrivateKey _oecc118
|
||||
#define Level3_ReassociateEntitledKeySession _oecc119
|
||||
#define Level3_LoadCasECMKeys _oecc120
|
||||
#define Level3_LoadEntitledContentKeys _oecc121 // place holder for v17.
|
||||
#define Level3_ProductionReady _oecc122
|
||||
#define Level3_Idle _oecc123
|
||||
#define Level3_Wake _oecc124
|
||||
#define Level3_BuildInformation _oecc125
|
||||
#define Level3_SecurityLevel _oecc126
|
||||
#define Level3_ReuseUsageEntry _oecc127
|
||||
#define Level3_GetDTCP2Capability _oecc128
|
||||
#define Level3_GetWatermarkingSupport _oecc129
|
||||
#define Level3_GetOEMKeyToken _oecc130
|
||||
#define Level3_GetDeviceInformation _oecc131
|
||||
#define Level3_SetMaxAPIVersion _oecc132
|
||||
#define Level3_GetKeyHandle _oecc133
|
||||
#define Level3_DecryptCENC _oecc134
|
||||
#define Level3_Generic_Encrypt _oecc135
|
||||
#define Level3_Generic_Decrypt _oecc136
|
||||
#define Level3_Generic_Sign _oecc137
|
||||
#define Level3_Generic_Verify _oecc138
|
||||
#define Level3_GetSignatureHashAlgorithm _oecc139
|
||||
#define Level3_EnterTestMode _oecc140
|
||||
#define Level3_GetDeviceSignedCsrPayload _oecc141
|
||||
// Internal-only.
|
||||
#define Level3_GetEmbeddedDrmCertificate _oecc143
|
||||
#endif
|
||||
|
||||
#define Level3_GetInitializationState _oecl3o01
|
||||
// clang-format on
|
||||
|
||||
extern "C" {
|
||||
|
||||
bool Level3_IsInApp();
|
||||
OEMCryptoResult Level3_Initialize(void);
|
||||
OEMCryptoResult Level3_Terminate(void);
|
||||
OEMCryptoResult Level3_OpenSession(OEMCrypto_SESSION* session);
|
||||
OEMCryptoResult Level3_CloseSession(OEMCrypto_SESSION session);
|
||||
OEMCryptoResult Level3_GenerateDerivedKeys(OEMCrypto_SESSION session,
|
||||
const uint8_t* mac_key_context,
|
||||
size_t mac_key_context_length,
|
||||
const uint8_t* enc_key_context,
|
||||
size_t enc_key_context_length);
|
||||
OEMCryptoResult Level3_GenerateNonce(OEMCrypto_SESSION session,
|
||||
uint32_t* nonce);
|
||||
OEMCryptoResult Level3_QueryKeyControl(OEMCrypto_SESSION session,
|
||||
const uint8_t* key_id,
|
||||
size_t key_id_length,
|
||||
uint8_t* key_control_block,
|
||||
size_t* key_control_block_length);
|
||||
OEMCryptoResult Level3_DecryptCENC_V17(
|
||||
OEMCrypto_SESSION session, const OEMCrypto_SampleDescription* samples,
|
||||
size_t samples_length, const OEMCrypto_CENCEncryptPatternDesc* pattern);
|
||||
OEMCryptoResult Level3_InstallKeyboxOrOEMCert(const uint8_t* rot,
|
||||
size_t rotLength);
|
||||
OEMCryptoResult Level3_IsKeyboxOrOEMCertValid(void);
|
||||
OEMCryptoResult Level3_WrapKeyboxOrOEMCert(const uint8_t* rot, size_t rotLength,
|
||||
uint8_t* wrappedRot,
|
||||
size_t* wrappedRotLength,
|
||||
const uint8_t* transportKey,
|
||||
size_t transportKeyLength);
|
||||
OEMCrypto_ProvisioningMethod Level3_GetProvisioningMethod();
|
||||
OEMCryptoResult Level3_GetOEMPublicCertificate(uint8_t* public_cert,
|
||||
size_t* public_cert_length);
|
||||
OEMCryptoResult Level3_GetDeviceID(uint8_t* deviceID, size_t* idLength);
|
||||
OEMCryptoResult Level3_GetKeyData(uint8_t* keyData, size_t* keyDataLength);
|
||||
OEMCryptoResult Level3_LoadOEMPrivateKey(OEMCrypto_SESSION session);
|
||||
OEMCryptoResult Level3_LoadDRMPrivateKey(OEMCrypto_SESSION session,
|
||||
OEMCrypto_PrivateKeyType key_type,
|
||||
const uint8_t* wrapped_rsa_key,
|
||||
size_t wrapped_rsa_key_length);
|
||||
OEMCryptoResult Level3_LoadProvisioning(
|
||||
OEMCrypto_SESSION session, const uint8_t* message, size_t message_length,
|
||||
size_t core_message_length, const uint8_t* signature,
|
||||
size_t signature_length, uint8_t* wrapped_private_key,
|
||||
size_t* wrapped_private_key_length);
|
||||
OEMCryptoResult Level3_RewrapDeviceRSAKey(
|
||||
OEMCrypto_SESSION session, const uint8_t* message, size_t message_length,
|
||||
const uint8_t* signature, size_t signature_length, const uint32_t* nonce,
|
||||
const uint8_t* enc_rsa_key, size_t enc_rsa_key_length,
|
||||
const uint8_t* enc_rsa_key_iv, uint8_t* wrapped_rsa_key,
|
||||
size_t* wrapped_rsa_key_length);
|
||||
OEMCryptoResult Level3_LoadTestRSAKey();
|
||||
OEMCryptoResult Level3_GenerateRSASignature(OEMCrypto_SESSION session,
|
||||
const uint8_t* message,
|
||||
size_t message_length,
|
||||
uint8_t* signature,
|
||||
size_t* signature_length,
|
||||
RSA_Padding_Scheme padding_scheme);
|
||||
OEMCryptoResult Level3_DeriveKeysFromSessionKey(OEMCrypto_SESSION session,
|
||||
const uint8_t* enc_session_key,
|
||||
size_t enc_session_key_length,
|
||||
const uint8_t* mac_key_context,
|
||||
size_t mac_key_context_length,
|
||||
const uint8_t* enc_key_context,
|
||||
size_t enc_key_context_length);
|
||||
uint32_t Level3_APIVersion();
|
||||
uint32_t Level3_MinorAPIVersion();
|
||||
uint8_t Level3_SecurityPatchLevel();
|
||||
OEMCrypto_Security_Level Level3_SecurityLevel();
|
||||
OEMCryptoResult Level3_GetHDCPCapability(OEMCrypto_HDCP_Capability* current,
|
||||
OEMCrypto_HDCP_Capability* maximum);
|
||||
bool Level3_SupportsUsageTable();
|
||||
bool Level3_IsAntiRollbackHwPresent();
|
||||
OEMCryptoResult Level3_GetNumberOfOpenSessions(size_t* count);
|
||||
OEMCryptoResult Level3_GetMaxNumberOfSessions(size_t* maximum);
|
||||
uint32_t Level3_SupportedCertificates();
|
||||
OEMCryptoResult Level3_Generic_Encrypt_V17(
|
||||
OEMCrypto_SESSION session, const uint8_t* in_buffer, size_t buffer_length,
|
||||
const uint8_t* iv, OEMCrypto_Algorithm algorithm, uint8_t* out_buffer);
|
||||
OEMCryptoResult Level3_Generic_Decrypt_V17(
|
||||
OEMCrypto_SESSION session, const uint8_t* in_buffer, size_t buffer_length,
|
||||
const uint8_t* iv, OEMCrypto_Algorithm algorithm, uint8_t* out_buffer);
|
||||
OEMCryptoResult Level3_Generic_Sign_V17(OEMCrypto_SESSION session,
|
||||
const uint8_t* in_buffer,
|
||||
size_t buffer_length,
|
||||
OEMCrypto_Algorithm algorithm,
|
||||
uint8_t* signature,
|
||||
size_t* signature_length);
|
||||
OEMCryptoResult Level3_Generic_Verify_V17(OEMCrypto_SESSION session,
|
||||
const uint8_t* in_buffer,
|
||||
size_t buffer_length,
|
||||
OEMCrypto_Algorithm algorithm,
|
||||
const uint8_t* signature,
|
||||
size_t signature_length);
|
||||
OEMCryptoResult Level3_DeactivateUsageEntry(OEMCrypto_SESSION session,
|
||||
const uint8_t* pst,
|
||||
size_t pst_length);
|
||||
OEMCryptoResult Level3_ReportUsage(OEMCrypto_SESSION session,
|
||||
const uint8_t* pst, size_t pst_length,
|
||||
uint8_t* buffer, size_t* buffer_length);
|
||||
bool Level3_IsSRMUpdateSupported();
|
||||
OEMCryptoResult Level3_GetCurrentSRMVersion(uint16_t* version);
|
||||
OEMCryptoResult Level3_LoadSRM(const uint8_t* buffer, size_t buffer_length);
|
||||
OEMCryptoResult Level3_RemoveSRM();
|
||||
OEMCryptoResult Level3_CreateUsageTableHeader(uint8_t* header_buffer,
|
||||
size_t* header_buffer_length);
|
||||
OEMCryptoResult Level3_LoadUsageTableHeader(const uint8_t* buffer,
|
||||
size_t buffer_length);
|
||||
OEMCryptoResult Level3_CreateNewUsageEntry(OEMCrypto_SESSION session,
|
||||
uint32_t* usage_entry_number);
|
||||
OEMCryptoResult Level3_LoadUsageEntry(OEMCrypto_SESSION session, uint32_t index,
|
||||
const uint8_t* buffer,
|
||||
size_t buffer_size);
|
||||
OEMCryptoResult Level3_UpdateUsageEntry(OEMCrypto_SESSION session,
|
||||
uint8_t* header_buffer,
|
||||
size_t* header_buffer_length,
|
||||
uint8_t* entry_buffer,
|
||||
size_t* entry_buffer_length);
|
||||
OEMCryptoResult Level3_ShrinkUsageTableHeader(uint32_t new_table_size,
|
||||
uint8_t* header_buffer,
|
||||
size_t* header_buffer_length);
|
||||
OEMCryptoResult Level3_MoveEntry(OEMCrypto_SESSION session, uint32_t new_index);
|
||||
uint32_t Level3_GetAnalogOutputFlags();
|
||||
OEMCryptoResult Level3_LoadTestKeybox(const uint8_t* buffer, size_t length);
|
||||
OEMCryptoResult Level3_SelectKey(const OEMCrypto_SESSION session,
|
||||
const uint8_t* key_id, size_t key_id_length,
|
||||
OEMCryptoCipherMode cipher_mode);
|
||||
OEMCryptoResult Level3_LoadLicense(OEMCrypto_SESSION session,
|
||||
const uint8_t* message,
|
||||
size_t message_length,
|
||||
size_t core_message_length,
|
||||
const uint8_t* signature,
|
||||
size_t signature_length);
|
||||
OEMCryptoResult Level3_LoadKeys(
|
||||
OEMCrypto_SESSION session, const uint8_t* message, size_t message_length,
|
||||
const uint8_t* signature, size_t signature_length,
|
||||
OEMCrypto_Substring enc_mac_keys_iv, OEMCrypto_Substring enc_mac_keys,
|
||||
size_t num_keys, const OEMCrypto_KeyObject* key_array,
|
||||
OEMCrypto_Substring pst, OEMCrypto_Substring srm_restriction_data,
|
||||
OEMCrypto_LicenseType license_type);
|
||||
OEMCryptoResult Level3_SetSandbox(const uint8_t* sandbox_id,
|
||||
size_t sandbox_id_length);
|
||||
uint32_t Level3_ResourceRatingTier();
|
||||
uint32_t Level3_SupportsDecryptHash();
|
||||
|
||||
OEMCryptoResult Level3_SetDecryptHash(OEMCrypto_SESSION session,
|
||||
uint32_t frame_number,
|
||||
const uint8_t* hash, size_t hash_length);
|
||||
OEMCryptoResult Level3_GetHashErrorCode(OEMCrypto_SESSION session,
|
||||
uint32_t* failed_frame_number);
|
||||
OEMCryptoResult Level3_BuildInformation(char* buffer, size_t* buffer_length);
|
||||
OEMCryptoResult Level3_LoadRenewal(OEMCrypto_SESSION session,
|
||||
const uint8_t* message,
|
||||
size_t message_length,
|
||||
size_t core_message_length,
|
||||
const uint8_t* signature,
|
||||
size_t signature_length);
|
||||
OEMCryptoResult Level3_RefreshKeys(OEMCrypto_SESSION session,
|
||||
const uint8_t* message,
|
||||
size_t message_length,
|
||||
const uint8_t* signature,
|
||||
size_t signature_length, size_t num_keys,
|
||||
const OEMCrypto_KeyRefreshObject* key_array);
|
||||
OEMCryptoResult Level3_LoadEntitledContentKeys(
|
||||
OEMCrypto_SESSION session, const uint8_t* message, size_t message_length,
|
||||
size_t num_keys, const OEMCrypto_EntitledContentKeyObject* key_array);
|
||||
OEMCryptoResult Level3_CopyBuffer(
|
||||
OEMCrypto_SESSION session, const uint8_t* data_addr, size_t data_length,
|
||||
const OEMCrypto_DestBufferDesc* out_buffer_descriptor,
|
||||
uint8_t subsample_flags);
|
||||
OEMCryptoResult Level3_PrepAndSignProvisioningRequest(
|
||||
OEMCrypto_SESSION session, uint8_t* message, size_t message_length,
|
||||
size_t* core_message_length, uint8_t* signature, size_t* signature_length);
|
||||
OEMCryptoResult Level3_PrepAndSignLicenseRequest(
|
||||
OEMCrypto_SESSION session, uint8_t* message, size_t message_length,
|
||||
size_t* core_message_length, uint8_t* signature, size_t* signature_length);
|
||||
OEMCryptoResult Level3_PrepAndSignRenewalRequest(
|
||||
OEMCrypto_SESSION session, uint8_t* message, size_t message_length,
|
||||
size_t* core_message_length, uint8_t* signature, size_t* signature_length);
|
||||
size_t Level3_MaximumUsageTableHeaderSize();
|
||||
OEMCryptoResult Level3_AllocateSecureBuffer(
|
||||
OEMCrypto_SESSION session, size_t buffer_size,
|
||||
OEMCrypto_DestBufferDesc* output_descriptor, int* secure_fd);
|
||||
OEMCryptoResult Level3_FreeSecureBuffer(
|
||||
OEMCrypto_SESSION session, OEMCrypto_DestBufferDesc* output_descriptor,
|
||||
int secure_fd);
|
||||
OEMCryptoResult Level3_CreateEntitledKeySession(OEMCrypto_SESSION oec_session,
|
||||
OEMCrypto_SESSION* key_session);
|
||||
OEMCryptoResult Level3_RemoveEntitledKeySession(OEMCrypto_SESSION key_session);
|
||||
OEMCryptoResult Level3_GetBootCertificateChain(
|
||||
uint8_t* bcc, size_t* bcc_size, uint8_t* additional_signature,
|
||||
size_t* additional_signature_size);
|
||||
OEMCryptoResult Level3_GenerateCertificateKeyPair(
|
||||
OEMCrypto_SESSION session, uint8_t* public_key, size_t* public_key_size,
|
||||
uint8_t* public_key_signature, size_t* public_key_signature_size,
|
||||
uint8_t* wrapped_private_key, size_t* wrapped_private_key_size,
|
||||
OEMCrypto_PrivateKeyType* key_type);
|
||||
OEMCryptoResult Level3_InstallOemPrivateKey(OEMCrypto_SESSION session,
|
||||
OEMCrypto_PrivateKeyType key_type,
|
||||
const uint8_t* wrapped_private_key,
|
||||
size_t wrapped_private_key_length);
|
||||
OEMCryptoResult Level3_ReassociateEntitledKeySession(
|
||||
OEMCrypto_SESSION key_session, OEMCrypto_SESSION oec_session);
|
||||
OEMCryptoResult Level3_LoadCasECMKeys(
|
||||
OEMCrypto_SESSION session, const uint8_t* message, size_t message_length,
|
||||
const OEMCrypto_EntitledContentKeyObject* even_key,
|
||||
const OEMCrypto_EntitledContentKeyObject* odd_key);
|
||||
OEMCryptoResult Level3_ProductionReady();
|
||||
OEMCryptoResult Level3_Idle(OEMCrypto_IdleState state,
|
||||
uint32_t os_specific_code);
|
||||
OEMCryptoResult Level3_Wake();
|
||||
OEMCryptoResult Level3_ReuseUsageEntry(OEMCrypto_SESSION session,
|
||||
uint32_t usage_entry_number);
|
||||
OEMCryptoResult Level3_GetDTCP2Capability(
|
||||
OEMCrypto_DTCP2_Capability* capability);
|
||||
OEMCrypto_WatermarkingSupport Level3_GetWatermarkingSupport();
|
||||
OEMCryptoResult Level3_GetOEMKeyToken(OEMCrypto_SESSION key_session,
|
||||
uint8_t* key_token,
|
||||
size_t* key_token_length);
|
||||
OEMCryptoResult Level3_GetDeviceInformation(uint8_t* device_info,
|
||||
size_t* device_info_length);
|
||||
OEMCryptoResult Level3_GetDeviceSignedCsrPayload(
|
||||
const uint8_t* challenge, size_t challenge_length,
|
||||
const uint8_t* encoded_device_info, size_t encoded_device_info_length,
|
||||
uint8_t* signed_csr_payload, size_t* signed_csr_payload_length);
|
||||
OEMCryptoResult Level3_SetMaxAPIVersion(uint32_t max_version);
|
||||
OEMCryptoResult Level3_GetKeyHandle(OEMCrypto_SESSION session,
|
||||
const uint8_t* content_key_id,
|
||||
size_t content_key_id_length,
|
||||
OEMCryptoCipherMode cipher_mode,
|
||||
uint8_t* key_handle,
|
||||
size_t* key_handle_length);
|
||||
OEMCryptoResult Level3_DecryptCENC(
|
||||
const uint8_t* key_handle, size_t key_handle_length,
|
||||
const OEMCrypto_SampleDescription* samples, size_t samples_length,
|
||||
const OEMCrypto_CENCEncryptPatternDesc* pattern);
|
||||
OEMCryptoResult Level3_Generic_Encrypt(const uint8_t* key_handle,
|
||||
size_t key_handle_length,
|
||||
const OEMCrypto_SharedMemory* in_buffer,
|
||||
size_t in_buffer_length,
|
||||
const uint8_t* iv,
|
||||
OEMCrypto_Algorithm algorithm,
|
||||
OEMCrypto_SharedMemory* out_buffer);
|
||||
OEMCryptoResult Level3_Generic_Decrypt(const uint8_t* key_handle,
|
||||
size_t key_handle_length,
|
||||
const OEMCrypto_SharedMemory* in_buffer,
|
||||
size_t in_buffer_length,
|
||||
const uint8_t* iv,
|
||||
OEMCrypto_Algorithm algorithm,
|
||||
OEMCrypto_SharedMemory* out_buffer);
|
||||
OEMCryptoResult Level3_Generic_Sign(const uint8_t* key_handle,
|
||||
size_t key_handle_length,
|
||||
const OEMCrypto_SharedMemory* buffer,
|
||||
size_t buffer_length,
|
||||
OEMCrypto_Algorithm algorithm,
|
||||
OEMCrypto_SharedMemory* signature,
|
||||
size_t* signature_length);
|
||||
OEMCryptoResult Level3_Generic_Verify(const uint8_t* key_handle,
|
||||
size_t key_handle_length,
|
||||
const OEMCrypto_SharedMemory* buffer,
|
||||
size_t buffer_length,
|
||||
OEMCrypto_Algorithm algorithm,
|
||||
const OEMCrypto_SharedMemory* signature,
|
||||
size_t signature_length);
|
||||
OEMCryptoResult Level3_GetSignatureHashAlgorithm(
|
||||
OEMCrypto_SESSION session, OEMCrypto_SignatureHashAlgorithm* algorithm);
|
||||
OEMCryptoResult Level3_EnterTestMode(void);
|
||||
OEMCryptoResult Level3_GetEmbeddedDrmCertificate(uint8_t* public_cert,
|
||||
size_t* public_cert_length);
|
||||
|
||||
// The following are specific to Google's Level 3 implementation and are not
|
||||
// required.
|
||||
|
||||
enum Level3InitializationState {
|
||||
LEVEL3_INITIALIZATION_SUCCESS = 0,
|
||||
LEVEL3_INITIALIZATION_UNKNOWN_FAILURE = 1,
|
||||
LEVEL3_SEED_FAILURE = 2,
|
||||
LEVEL3_SAVE_DEVICE_KEYS_FAILURE = 3,
|
||||
LEVEL3_READ_DEVICE_KEYS_FAILURE = 4,
|
||||
LEVEL3_VERIFY_DEVICE_KEYS_FAILURE = 5,
|
||||
};
|
||||
|
||||
enum Level3RunningMode {
|
||||
LEVEL3_MODE_HAYSTACK_ONLY = 0,
|
||||
LEVEL3_MODE_RIKERS_DEFAULT = 1,
|
||||
LEVEL3_MODE_RIKERS_ONLY = 2,
|
||||
};
|
||||
|
||||
/*
|
||||
* Level3_GetRunningMode
|
||||
*
|
||||
* Description:
|
||||
* Returns the current mode the Level3 is running in. This shouldn't change
|
||||
* while the processes is running.
|
||||
*
|
||||
* Parameters:
|
||||
* N/A
|
||||
*
|
||||
* Threading:
|
||||
* No other function calls will be made while this function is running.
|
||||
*
|
||||
* Version:
|
||||
* This method is new in API version 19.
|
||||
*/
|
||||
Level3RunningMode Level3_GetRunningMode(void);
|
||||
|
||||
/*
|
||||
* Level3_GetInitializationState
|
||||
*
|
||||
* Description:
|
||||
* Return any warning or error condition which occurred during
|
||||
* initialization. On some platforms, this value will be logged and metrics
|
||||
* will be gathered on production devices. This is an optional feature, and
|
||||
* OEMCrypto may always return 0, even if Level3_Initialize failed. This
|
||||
* function may be called whether Level3_Initialize succeeded or not.
|
||||
*
|
||||
* Parameters:
|
||||
* N/A
|
||||
*
|
||||
* Threading:
|
||||
* No other function calls will be made while this function is running.
|
||||
*
|
||||
* Returns:
|
||||
* LEVEL3_INITIALIZATION_SUCCESS - no warnings or errors during initialization
|
||||
* LEVEL3_SEED_FAILURE - error in seeding the software RNG
|
||||
* LEVEL3_SAVE_DEVICE_KEYS_FAILURE - failed to save device keys to file system
|
||||
* LEVEL3_READ_DEVICE_KEYS_FAILURE - failed to read device keys from file
|
||||
* system
|
||||
* LEVEL3_VERIFY_DEVICE_KEYS_FAILURE - failed to verify decrypted device keys
|
||||
*
|
||||
* Version:
|
||||
* This method is new in API version 14.
|
||||
*/
|
||||
Level3InitializationState Level3_GetInitializationState(void);
|
||||
|
||||
/*
|
||||
* Level3_OutputErrorLogs
|
||||
*
|
||||
* Description:
|
||||
* Call to output any errors in the Level 3 execution if the Level 3 has
|
||||
* failed. This method should only be called if the Level 3 has failed in
|
||||
* an unrecoverable state, and needs to be reinitialized.
|
||||
*
|
||||
* Parameters:
|
||||
* N/A
|
||||
*
|
||||
* Threading:
|
||||
* No other function calls will be made while this function is running.
|
||||
*
|
||||
* Returns:
|
||||
* N/A
|
||||
*
|
||||
* Version:
|
||||
* This method is new in API version 15.
|
||||
*/
|
||||
void Level3_OutputErrorLogs();
|
||||
|
||||
} // extern "C"
|
||||
|
||||
namespace wvoec3 {
|
||||
|
||||
// The following are interfaces needed for Google's Level 3 OEMCrypto
|
||||
// specifically, which partners are expected to implement.
|
||||
|
||||
// Returns a stable, unique identifier for the device. This could be a
|
||||
// serial number or any other character sequence representing that device.
|
||||
// The parameter |len| needs to be changed to reflect the length of the
|
||||
// unique identifier.
|
||||
const char* getUniqueID(size_t* len);
|
||||
|
||||
// Returns a 64-bit unsigned integer to be used as a random seed for RNG.
|
||||
// If the operation is unsuccessful, this function returns 0.
|
||||
// We provide a sample implementation under the name generate_entropy_linux.cpp
|
||||
// which partners should use if they can.
|
||||
uint64_t generate_entropy();
|
||||
|
||||
// Creates and returns an OEMCrypto_Level3FileSystem implementation.
|
||||
OEMCrypto_Level3FileSystem* createLevel3FileSystem();
|
||||
|
||||
// Deletes the pointer retrieved by the function above.
|
||||
void deleteLevel3FileSystem(OEMCrypto_Level3FileSystem* file_system);
|
||||
|
||||
} // namespace wvoec3
|
||||
|
||||
#endif // LEVEL3_OEMCRYPTO_H_
|
||||
32
oemcrypto/include/level3_file_system.h
Normal file
32
oemcrypto/include/level3_file_system.h
Normal file
@@ -0,0 +1,32 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine
|
||||
// License Agreement.
|
||||
|
||||
/*********************************************************************
|
||||
* level3_file_system.h
|
||||
*
|
||||
* File system for OEMCrypto Level3 file operations.
|
||||
*********************************************************************/
|
||||
|
||||
#ifndef LEVEL3_FILE_SYSTEM_H_
|
||||
#define LEVEL3_FILE_SYSTEM_H_
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "platform.h"
|
||||
|
||||
namespace wvoec3 {
|
||||
|
||||
class OEMCrypto_Level3FileSystem {
|
||||
public:
|
||||
virtual ~OEMCrypto_Level3FileSystem() {}
|
||||
virtual ssize_t Read(const char *filename, void *buffer, size_t size) = 0;
|
||||
virtual ssize_t Write(const char *filename, const void *buffer,
|
||||
size_t size) = 0;
|
||||
virtual bool Exists(const char *filename) = 0;
|
||||
virtual ssize_t FileSize(const char *filename) = 0;
|
||||
virtual bool Remove(const char *filename) = 0;
|
||||
};
|
||||
|
||||
} // namespace wvoec3
|
||||
|
||||
#endif
|
||||
96
oemcrypto/include/oemcrypto_types.h
Normal file
96
oemcrypto/include/oemcrypto_types.h
Normal file
@@ -0,0 +1,96 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine
|
||||
// License Agreement.
|
||||
|
||||
#ifndef WV_OEMCRYPTO_TYPES_H_
|
||||
#define WV_OEMCRYPTO_TYPES_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace wvoec {
|
||||
|
||||
// This is the format of a Widevine keybox.
|
||||
typedef struct WidevineKeybox { // 128 bytes total.
|
||||
// C character string identifying the device. Null terminated.
|
||||
uint8_t device_id_[32];
|
||||
// 128 bit AES key assigned to device. Generated by Widevine.
|
||||
uint8_t device_key_[16];
|
||||
// Key Data. Encrypted data.
|
||||
uint8_t data_[72];
|
||||
// Constant code used to recognize a valid keybox "kbox" = 0x6b626f78.
|
||||
uint8_t magic_[4];
|
||||
// The CRC checksum of the first 124 bytes of the keybox.
|
||||
uint8_t crc_[4];
|
||||
} WidevineKeybox;
|
||||
|
||||
// This is the format for a key control block.
|
||||
typedef struct {
|
||||
uint8_t verification[4];
|
||||
uint32_t duration;
|
||||
uint32_t nonce;
|
||||
uint32_t control_bits;
|
||||
} KeyControlBlock;
|
||||
|
||||
/*
|
||||
* SRM_Restriction_Data
|
||||
*
|
||||
* Structure passed into LoadKeys to specify required SRM version.
|
||||
*/
|
||||
typedef struct {
|
||||
uint8_t verification[8]; // must be "HDCPDATA"
|
||||
uint32_t minimum_srm_version; // version number.
|
||||
} SRM_Restriction_Data;
|
||||
|
||||
// clang-format off
|
||||
// Key Control Block Bit Masks:
|
||||
const uint32_t kControlObserveDataPath = (1u << 31);
|
||||
const uint32_t kControlObserveHDCP = (1u << 30);
|
||||
const uint32_t kControlObserveCGMS = (1u << 29);
|
||||
const uint32_t kControlRequireAntiRollbackHardware = (1u << 28);
|
||||
// The two bits kControlWhiteboxSecurityLevelMask are not used in
|
||||
// OEMCrypto; they are only used for whitebox testing.
|
||||
const uint32_t kControlWhiteboxSecurityLevelShift = 26;
|
||||
const uint32_t kControlWhiteboxSecurityLevelMask =
|
||||
(0x03u << kControlWhiteboxSecurityLevelShift);
|
||||
const uint32_t kControlAllowDVRRecording = (1u << 25);
|
||||
const uint32_t kControlAllowHashVerification = (1u << 24);
|
||||
const uint32_t kSharedLicense = (1u << 23);
|
||||
const uint32_t kControlSRMVersionRequired = (1u << 22);
|
||||
const uint32_t kControlDisableAnalogOutput = (1u << 21);
|
||||
const uint32_t kControlSecurityPatchLevelShift = 15;
|
||||
const uint32_t kControlSecurityPatchLevelMask =
|
||||
(0x3Fu << kControlSecurityPatchLevelShift);
|
||||
const uint32_t kControlReplayMask = (0x03u << 13);
|
||||
const uint32_t kControlNonceRequired = (0x01u << 13);
|
||||
const uint32_t kControlNonceOrEntry = (0x02u << 13);
|
||||
const uint32_t kControlHDCPVersionShift = 9;
|
||||
const uint32_t kControlHDCPVersionMask =
|
||||
(0x0Fu << kControlHDCPVersionShift);
|
||||
const uint32_t kControlAllowEncrypt = (1u << 8);
|
||||
const uint32_t kControlAllowDecrypt = (1u << 7);
|
||||
const uint32_t kControlAllowSign = (1u << 6);
|
||||
const uint32_t kControlAllowVerify = (1u << 5);
|
||||
const uint32_t kControlDataPathSecure = (1u << 4);
|
||||
const uint32_t kControlNonceEnabled = (1u << 3);
|
||||
const uint32_t kControlHDCPRequired = (1u << 2);
|
||||
const uint32_t kControlCGMSMask = (0x03);
|
||||
const uint32_t kControlCGMSCopyFreely = (0x00);
|
||||
const uint32_t kControlCGMSCopyOnce = (0x02);
|
||||
const uint32_t kControlCGMSCopyNever = (0x03);
|
||||
// clang-format on
|
||||
|
||||
// Various constants and sizes:
|
||||
constexpr size_t KEY_CONTROL_SIZE = 16;
|
||||
constexpr size_t KEY_ID_SIZE = 16;
|
||||
constexpr size_t KEY_IV_SIZE = 16;
|
||||
constexpr size_t KEY_PAD_SIZE = 16;
|
||||
constexpr size_t KEY_SIZE = 16;
|
||||
constexpr size_t AES_128_BLOCK_SIZE = 16;
|
||||
constexpr size_t MAC_KEY_SIZE = 32;
|
||||
constexpr size_t KEYBOX_KEY_DATA_SIZE = 72;
|
||||
constexpr size_t SRM_REQUIREMENT_SIZE = 12;
|
||||
constexpr size_t HMAC_SHA256_SIGNATURE_SIZE = 32;
|
||||
|
||||
} // namespace wvoec
|
||||
|
||||
#endif // WV_OEMCRYPTO_TYPES_H_
|
||||
148
oemcrypto/include/pst_report.h
Normal file
148
oemcrypto/include/pst_report.h
Normal file
@@ -0,0 +1,148 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine
|
||||
// License Agreement.
|
||||
|
||||
/*********************************************************************
|
||||
* pst_report.h
|
||||
*
|
||||
* Reference APIs needed to support Widevine's crypto algorithms.
|
||||
*********************************************************************/
|
||||
|
||||
#ifndef PST_REPORT_H_
|
||||
#define PST_REPORT_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "OEMCryptoCENC.h"
|
||||
#include "string_conversions.h" // needed for htonll64.
|
||||
|
||||
namespace wvutil {
|
||||
|
||||
class Unpacked_PST_Report {
|
||||
public:
|
||||
// This object does not own the buffer, and does not check that buffer
|
||||
// is not null.
|
||||
Unpacked_PST_Report(uint8_t *buffer) : buffer_(buffer) {}
|
||||
|
||||
// Copy and move semantics of this class is like that of a pointer.
|
||||
Unpacked_PST_Report(const Unpacked_PST_Report& other) :
|
||||
buffer_(other.buffer_) {}
|
||||
|
||||
Unpacked_PST_Report& operator=(const Unpacked_PST_Report& other) {
|
||||
buffer_ = other.buffer_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
size_t report_size() const {
|
||||
return pst_length() + kraw_pst_report_size;
|
||||
}
|
||||
|
||||
static size_t report_size(size_t pst_length) {
|
||||
return pst_length + kraw_pst_report_size;
|
||||
}
|
||||
|
||||
uint8_t status() const {
|
||||
return static_cast<uint8_t>(* (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<uint8_t>(* (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<uint8_t>(* (buffer_ + kpst_length_offset));
|
||||
}
|
||||
|
||||
void set_pst_length(uint8_t value) {
|
||||
buffer_[kpst_length_offset] = value;
|
||||
}
|
||||
|
||||
uint8_t padding() const {
|
||||
return static_cast<uint8_t>(* (buffer_ + kpadding_offset));
|
||||
}
|
||||
|
||||
void set_padding(uint8_t value) {
|
||||
buffer_[kpadding_offset] = value;
|
||||
}
|
||||
|
||||
// In host byte order.
|
||||
int64_t seconds_since_license_received() const {
|
||||
int64_t time;
|
||||
memcpy(&time, buffer_ + kseconds_since_license_received_offset,
|
||||
sizeof(int64_t));
|
||||
return wvutil::ntohll64(time);
|
||||
}
|
||||
|
||||
// Parameter time is in host byte order.
|
||||
void set_seconds_since_license_received(int64_t time) const {
|
||||
time = wvutil::ntohll64(time);
|
||||
memcpy(buffer_ + kseconds_since_license_received_offset, &time,
|
||||
sizeof(int64_t));
|
||||
}
|
||||
|
||||
// In host byte order.
|
||||
int64_t seconds_since_first_decrypt() const {
|
||||
int64_t time;
|
||||
memcpy(&time, buffer_ + kseconds_since_first_decrypt_offset,
|
||||
sizeof(int64_t));
|
||||
return ntohll64(time);
|
||||
}
|
||||
|
||||
// Parameter time is in host byte order.
|
||||
void set_seconds_since_first_decrypt(int64_t time) const {
|
||||
time = ntohll64(time);
|
||||
memcpy(buffer_ + kseconds_since_first_decrypt_offset, &time,
|
||||
sizeof(int64_t));
|
||||
}
|
||||
|
||||
// In host byte order.
|
||||
int64_t seconds_since_last_decrypt() const {
|
||||
int64_t time;
|
||||
memcpy(&time, buffer_ + kseconds_since_last_decrypt_offset,
|
||||
sizeof(int64_t));
|
||||
return ntohll64(time);
|
||||
}
|
||||
|
||||
// Parameter time is in host byte order.
|
||||
void set_seconds_since_last_decrypt(int64_t time) const {
|
||||
time = ntohll64(time);
|
||||
memcpy(buffer_ + kseconds_since_last_decrypt_offset, &time,
|
||||
sizeof(int64_t));
|
||||
}
|
||||
|
||||
uint8_t* pst() {
|
||||
return (buffer_ + kpst_offset);
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t *buffer_;
|
||||
|
||||
// Size of the PST_Report without the pst string.
|
||||
static const size_t kraw_pst_report_size = 48;
|
||||
static const size_t ksignature_offset = 0;
|
||||
static const size_t kstatus_offset = 20;
|
||||
static const size_t kclock_security_level_offset = 21;
|
||||
static const size_t kpst_length_offset = 22;
|
||||
static const size_t kpadding_offset = 23;
|
||||
static const size_t kseconds_since_license_received_offset = 24;
|
||||
static const size_t kseconds_since_first_decrypt_offset = 32;
|
||||
static const size_t kseconds_since_last_decrypt_offset = 40;
|
||||
static const size_t kpst_offset = 48;
|
||||
};
|
||||
} // namespace wvutil
|
||||
|
||||
#endif // PST_REPORT_H_
|
||||
36
plugin/Android.bp
Normal file
36
plugin/Android.bp
Normal file
@@ -0,0 +1,36 @@
|
||||
cc_library_static {
|
||||
name: "libwvcasplugins",
|
||||
local_include_dirs: [
|
||||
"include",
|
||||
],
|
||||
export_include_dirs: ["include"],
|
||||
srcs: [
|
||||
"src/crypto_session.cpp",
|
||||
"src/widevine_cas_session_map.cpp",
|
||||
"src/widevine_media_cas.cpp",
|
||||
"src/cas_license.cpp",
|
||||
"src/ecm_parser.cpp",
|
||||
"src/ecm_parser_v2.cpp",
|
||||
"src/ecm_parser_v3.cpp",
|
||||
"src/emm_parser.cpp",
|
||||
"src/license_key_status.cpp",
|
||||
"src/oemcrypto_interface.cpp",
|
||||
"src/policy_engine.cpp",
|
||||
"src/widevine_cas_session.cpp",
|
||||
"src/widevine_media_cas_plugin.cpp",
|
||||
"src/widevine_cas_api.cpp",
|
||||
],
|
||||
proprietary: true,
|
||||
shared_libs: [
|
||||
"libcrypto",
|
||||
"libutils",
|
||||
],
|
||||
static_libs: [
|
||||
"//vendor/widevine/libwvmediacas/wvutil:libcasutil",
|
||||
"//vendor/widevine/libwvmediacas/protos:libcas_protos",
|
||||
],
|
||||
header_libs: [
|
||||
"//vendor/widevine/libwvmediacas/oemcrypto:oemcastroheaders",
|
||||
"media_plugin_headers",
|
||||
],
|
||||
}
|
||||
128
plugin/include/cas_events.h
Normal file
128
plugin/include/cas_events.h
Normal file
@@ -0,0 +1,128 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#ifndef CAS_EVENTS_H
|
||||
#define CAS_EVENTS_H
|
||||
|
||||
#define PROVISIONING_EVENT_START 1000
|
||||
#define LICENSING_EVENT_START 2000
|
||||
#define CAS_SESSION_EVENT_START 3000
|
||||
#define CAS_QUERY_EVENT_START 4000
|
||||
#define CAS_ERROR_EVENT_START 5000
|
||||
#define CAS_PARENTAL_CONTROL_EVENT_START 6000
|
||||
#define CAS_FINGERPRINTING_EVENT_START 6100
|
||||
#define CAS_SERVICE_BLOCKING_EVENT_START 6200
|
||||
#define CAS_TEST_EVENT_START 10000
|
||||
|
||||
typedef enum {
|
||||
UNKNOWN = 0,
|
||||
|
||||
INDIVIDUALIZATION_REQUEST = PROVISIONING_EVENT_START,
|
||||
INDIVIDUALIZATION_RESPONSE,
|
||||
INDIVIDUALIZATION_COMPLETE,
|
||||
|
||||
LICENSE_REQUEST = LICENSING_EVENT_START,
|
||||
LICENSE_RESPONSE,
|
||||
CAS_ERROR_DEPRECATED,
|
||||
LICENSE_RENEWAL_REQUEST,
|
||||
LICENSE_RENEWAL_RESPONSE,
|
||||
LICENSE_RENEWAL_URL,
|
||||
LICENSE_CAS_READY,
|
||||
LICENSE_CAS_RENEWAL_READY,
|
||||
LICENSE_REMOVAL,
|
||||
LICENSE_REMOVED,
|
||||
ASSIGN_LICENSE_ID,
|
||||
LICENSE_ID_ASSIGNED,
|
||||
LICENSE_NEW_EXPIRY_TIME,
|
||||
MULTI_CONTENT_LICENSE_INFO,
|
||||
GROUP_LICENSE_INFO,
|
||||
LICENSE_ENTITLEMENT_PERIOD_UPDATE_REQUEST,
|
||||
LICENSE_ENTITLEMENT_PERIOD_UPDATE_RESPONSE,
|
||||
LICENSE_ENTITLEMENT_PERIOD_UPDATED,
|
||||
|
||||
// TODO(jfore): Evaluate removing this event in favor of return status codes
|
||||
// from
|
||||
// frameworks/av/media/libstagefright/include/media/stagefright/MediaErrors.h
|
||||
CAS_ERROR = CAS_ERROR_EVENT_START,
|
||||
|
||||
CAS_SESSION_ID = CAS_SESSION_EVENT_START,
|
||||
SET_CAS_SOC_ID,
|
||||
SET_CAS_SOC_DATA,
|
||||
|
||||
UNIQUE_ID = CAS_QUERY_EVENT_START,
|
||||
QUERY_UNIQUE_ID,
|
||||
WV_CAS_PLUGIN_VERSION,
|
||||
QUERY_WV_CAS_PLUGIN_VERSION,
|
||||
|
||||
SET_PARENTAL_CONTROL_AGE = CAS_PARENTAL_CONTROL_EVENT_START,
|
||||
DEPRECATED_PARENTAL_CONTROL_AGE_UPDATED,
|
||||
ACCESS_DENIED_BY_PARENTAL_CONTROL,
|
||||
AGE_RESTRICTION_UPDATED,
|
||||
|
||||
// The content of FINGERPRINTING_INFO events follows TLV (Type (1 byte) -
|
||||
// Length (2 bytes) - Value) format. See FingerprintingFieldType for possible
|
||||
// types. A FINGERPRINTING_INFO event contains {one or more CHANNEL, one
|
||||
// CONTROL}.
|
||||
FINGERPRINTING_INFO = CAS_FINGERPRINTING_EVENT_START,
|
||||
// Fingerprinting control info for a session. The content of the event follows
|
||||
// TLV (Type (1 byte) - Length (2 bytes) - Value) format. See
|
||||
// SessionFingerprintingFieldType for possible types. It will contain {one
|
||||
// FINGERPRINTING_CONTROL}.
|
||||
SESSION_FINGERPRINTING_INFO,
|
||||
|
||||
// The content of SERVICE_BLOCKING_INFO events follows TLV (Type (1 byte) -
|
||||
// Length (2 bytes) - Value) format. See ServiceBlockingFieldType for possible
|
||||
// types. A SERVICE_BLOCKING_INFO event contains {one or more CHANNEL, one or
|
||||
// more DEVICE_GROUP, zero or one START_TIME_SECONDS, one END_TIME_SECONDS}.
|
||||
SERVICE_BLOCKING_INFO = CAS_SERVICE_BLOCKING_EVENT_START,
|
||||
// Service blocking device group for a session. The content of the event
|
||||
// follows TLV (Type (1 byte) - Length (2 bytes) - Value) format. See
|
||||
// SessionServiceBlockingFieldType for possible types. It will contain {one or
|
||||
// more SERVICE_BLOCKING_DEVICE_GROUP}.
|
||||
SESSION_SERVICE_BLOCKING_INFO,
|
||||
|
||||
TEST_FOR_ECHO =
|
||||
CAS_TEST_EVENT_START, // Request an ECHO response to test events passing.
|
||||
ECHO, // Respond to TEST_FOR_ECHO.
|
||||
} CasEventId;
|
||||
|
||||
// Types used inside an FINGERPRINTING_INFO event.
|
||||
typedef enum {
|
||||
FINGERPRINTING_CHANNEL = 0,
|
||||
FINGERPRINTING_CONTROL,
|
||||
} FingerprintingFieldType;
|
||||
|
||||
// Types used inside an SERVICE_BLOCKING_INFO event.
|
||||
typedef enum {
|
||||
SERVICE_BLOCKING_CHANNEL = 0,
|
||||
SERVICE_BLOCKING_DEVICE_GROUP,
|
||||
// Epoch time in seconds. Missing of this field or a value of 0 means
|
||||
// immediate start.
|
||||
SERVICE_BLOCKING_START_TIME_SECONDS,
|
||||
SERVICE_BLOCKING_END_TIME_SECONDS, // Epoch time in seconds.
|
||||
} ServiceBlockingFieldType;
|
||||
|
||||
// Types used inside an SESSION_FINGERPRINTING_CONTROL event.
|
||||
typedef enum {
|
||||
SESSION_FINGERPRINTING_CONTROL = 0,
|
||||
} SessionFingerprintingFieldType;
|
||||
|
||||
// Types used inside an SESSION_SERVICE_BLOCKING_GROUPS event.
|
||||
typedef enum {
|
||||
SESSION_SERVICE_BLOCKING_DEVICE_GROUP = 0,
|
||||
} SessionServiceBlockingFieldType;
|
||||
|
||||
// Types used inside a MULTI_CONTENT_LICENSE_INFO event.
|
||||
typedef enum {
|
||||
MULTI_CONTENT_LICENSE_ID = 0,
|
||||
MULTI_CONTENT_LICENSE_CONTENT_ID,
|
||||
} MultiContentLicenseFieldType;
|
||||
|
||||
// Types used inside a GROUP_LICENSE_INFO event.
|
||||
typedef enum {
|
||||
GROUP_LICENSE_ID = 0,
|
||||
GROUP_LICENSE_GROUP_ID,
|
||||
} GroupLicenseFieldType;
|
||||
|
||||
#endif // CAS_EVENTS_H
|
||||
184
plugin/include/cas_license.h
Normal file
184
plugin/include/cas_license.h
Normal file
@@ -0,0 +1,184 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#ifndef CAS_LICENSE_H
|
||||
#define CAS_LICENSE_H
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#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<CryptoSession> crypto_session,
|
||||
CasEventListener* listener);
|
||||
|
||||
// Generate a request to obtain a device certificate for requesting
|
||||
// entitlements. The generated message is set in |provisioning_request|.
|
||||
virtual CasStatus GenerateDeviceProvisioningRequest(
|
||||
std::string* provisioning_request) const;
|
||||
|
||||
// Process a server response containing a device certificate for use in
|
||||
// requesting entitlements. The contained certificate data will be extracted
|
||||
// and wrapped for storage. The public key cert will be returned in
|
||||
// |device_certificate|. The private key information will be wrapped by the
|
||||
// crypto session and returned in |wrapped_rsa_key|.
|
||||
// A secure binary file image containing the device cert is returned in
|
||||
// |device_file| if not nullptr. This file is suitable for storage on a device
|
||||
virtual CasStatus HandleDeviceProvisioningResponse(
|
||||
const std::string& signed_provisioning_response,
|
||||
std::string* device_certificate, std::string* wrapped_rsa_key,
|
||||
std::string* device_file) const;
|
||||
|
||||
// Generate a request to obtain an EMM (Entitlement Management Message) to
|
||||
// use to enable processing of ECM(s) (Encryption Management Message).
|
||||
// |init_data| is widevine metadata about the stream needed in the request.
|
||||
// |wrapped_rsa_key| and |signed_license_request| are the device certificate
|
||||
// obtained by HandleDeviceProvisioningResponse.
|
||||
virtual CasStatus GenerateEntitlementRequest(
|
||||
const std::string& init_data, const std::string& device_certificate,
|
||||
const std::string& wrapped_rsa_key, LicenseType license_type,
|
||||
std::string* signed_license_request);
|
||||
|
||||
// Restores a stored license making the keys available for use.
|
||||
virtual CasStatus HandleStoredLicense(const std::string& wrapped_rsa_key,
|
||||
const std::string& license_file);
|
||||
|
||||
// Process a server response containing a EMM for use in the processing of
|
||||
// ECM(s).
|
||||
// If |device_file| is not nullptr and the license policy allows a license to
|
||||
// be stored |device_file| is populated with the bytes of the license secured
|
||||
// for storage.
|
||||
virtual CasStatus HandleEntitlementResponse(
|
||||
const std::string& entitlement_response, std::string* device_file);
|
||||
|
||||
// Process a previously stored device |certificate| and make it available
|
||||
// for use in an EMM request.
|
||||
virtual CasStatus HandleStoredDrmCert(const std::string& certificate,
|
||||
std::string* device_certificate,
|
||||
std::string* wrapped_rsa_key);
|
||||
|
||||
// Generate an entitlement renewal request message in
|
||||
// |signed_renewal_request|.
|
||||
virtual CasStatus GenerateEntitlementRenewalRequest(
|
||||
const std::string& device_certificate,
|
||||
std::string* signed_renewal_request);
|
||||
|
||||
// Process a server response containing a EMM renewal. If |device_file| is not
|
||||
// nullptr and the license policy allows a license renewal to be stored
|
||||
// |device_file| is populated with the bytes of the license secured for
|
||||
// storage.
|
||||
virtual CasStatus HandleEntitlementRenewalResponse(
|
||||
const std::string& renewal_response, std::string* device_file);
|
||||
|
||||
// Query the license to see if a key is usable.
|
||||
virtual bool CanDecryptContent(const KeyId& key_id) const;
|
||||
|
||||
// Update the license after handling license remove. Plugin is disabled to
|
||||
// playback stream, store and renew license.
|
||||
virtual void UpdateLicenseForLicenseRemove();
|
||||
|
||||
// Query the license to see if storage is allowed.
|
||||
virtual bool CanStoreLicense() const;
|
||||
|
||||
// Returns the group id specified in the license. Group id is expected to be
|
||||
// non-empty if the license is MULTI_CONTENT_LICENSE or GROUP_LICENSE; and
|
||||
// empty if the license is SINGLE_CONTENT_LICENSE_DEFAULT.
|
||||
virtual std::string GetGroupId() const;
|
||||
|
||||
// If the license is MULTI_CONTENT_LICENSE, the returned vector contains all
|
||||
// content ids that the license is for. Returns empty if the license if not
|
||||
// MULTI_CONTENT_LICENSE.
|
||||
virtual std::vector<std::string> GetContentIdList() const;
|
||||
|
||||
// Returns true if the license is MULTI_CONTENT_LICENSE, and false otherwise.
|
||||
virtual bool IsMultiContentLicense() const;
|
||||
|
||||
// Returns true if the license is GROUP_LICENSE, and false otherwise.
|
||||
virtual bool IsGroupLicense() const;
|
||||
|
||||
// Policy timer implentation.
|
||||
void OnTimerEvent() override;
|
||||
|
||||
// Event listener implementation.
|
||||
void OnSessionRenewalNeeded() override;
|
||||
|
||||
void OnSessionKeysChange(const KeyStatusMap& keys_status,
|
||||
bool has_new_usable_key) override;
|
||||
|
||||
void OnExpirationUpdate(int64_t new_expiry_time_seconds) override;
|
||||
|
||||
void OnLicenseExpiration() override;
|
||||
|
||||
void OnNewRenewalServerUrl(const std::string& renewal_server_url) override;
|
||||
|
||||
void OnAgeRestrictionUpdated(const WvCasSessionId& sessionId,
|
||||
uint8_t ecm_age_restriction) override;
|
||||
void OnFingerprintingUpdated(const CasData& fingerprinting) override;
|
||||
void OnServiceBlockingUpdated(const CasData& service_blocking) override;
|
||||
|
||||
void OnSessionFingerprintingUpdated(const WvCasSessionId& sessionId,
|
||||
const CasData& fingerprinting) override;
|
||||
|
||||
void OnSessionServiceBlockingUpdated(
|
||||
const WvCasSessionId& sessionId,
|
||||
const CasData& service_blocking) override;
|
||||
|
||||
void OnEntitlementPeriodUpdateNeeded(
|
||||
const std::string& signed_license_request) override;
|
||||
|
||||
// Query to see if the license is expired.
|
||||
virtual bool IsExpired() const;
|
||||
|
||||
// Notify the license that playback decryption has begun.
|
||||
virtual void BeginDecryption();
|
||||
|
||||
// Returns NoError if a valid entitlement period index exists in
|
||||
// |license_file|. The index will be assigned to |entitlement_period_index|.
|
||||
static CasStatus GetEntitlementPeriodIndexFromStoredLicense(
|
||||
const std::string& license_file, uint32_t& entitlement_period_index);
|
||||
|
||||
CasLicense(const CasLicense&) = delete;
|
||||
CasLicense& operator=(const CasLicense&) = delete;
|
||||
|
||||
private:
|
||||
CasStatus GenerateDeviceProvisioningRequestWithKeybox(
|
||||
std::string* provisioning_request) const;
|
||||
CasStatus GenerateDeviceProvisioningRequestWithOEMCert() const;
|
||||
CasStatus InstallLicense(const std::string& session_key,
|
||||
const std::string& serialized_license,
|
||||
const std::string& core_message,
|
||||
const std::string& signature);
|
||||
CasStatus InstallLicenseRenewal(const std::string& serialized_license,
|
||||
const std::string& core_message,
|
||||
const std::string& signature);
|
||||
|
||||
virtual std::unique_ptr<PolicyEngine> GetPolicyEngine();
|
||||
|
||||
std::unique_ptr<PolicyEngine> policy_engine_;
|
||||
std::shared_ptr<CryptoSession> crypto_session_;
|
||||
CasEventListener* event_listener_ = nullptr;
|
||||
video_widevine::License license_;
|
||||
std::string emm_request_;
|
||||
std::string emm_response_;
|
||||
std::string renewal_request_;
|
||||
std::string renewal_response_;
|
||||
std::string init_data_;
|
||||
bool is_renewal_in_license_file_ = false;
|
||||
};
|
||||
|
||||
} // namespace wvcas
|
||||
|
||||
#endif // CAS_LICENSE_H
|
||||
25
plugin/include/cas_media_id.h
Normal file
25
plugin/include/cas_media_id.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#ifndef CAS_MEDIA_ID_H
|
||||
#define CAS_MEDIA_ID_H
|
||||
|
||||
namespace wvcas {
|
||||
|
||||
class CasMediaId {
|
||||
protected:
|
||||
CasMediaId() {}
|
||||
|
||||
public:
|
||||
CasMediaId(const CasMediaId&) = delete;
|
||||
CasMediaId& operator=(const CasMediaId&) = delete;
|
||||
virtual ~CasMediaId() {}
|
||||
static std::unique_ptr<CasMediaId> create();
|
||||
virtual CasStatus initialize(const std::string& init_data) = 0;
|
||||
virtual const std::string content_id() = 0;
|
||||
virtual const std::string provider_id() = 0;
|
||||
virtual bool is_entitlement_rotation_enabled() { return false; }
|
||||
virtual uint32_t entitlement_period_index() = 0;
|
||||
virtual std::string get_init_data() = 0;
|
||||
};
|
||||
|
||||
} // namespace wvcas
|
||||
|
||||
#endif // CAS_MEDIA_ID_H
|
||||
53
plugin/include/cas_status.h
Normal file
53
plugin/include/cas_status.h
Normal file
@@ -0,0 +1,53 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#ifndef CAS_STATUS_H
|
||||
#define CAS_STATUS_H
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace wvcas {
|
||||
|
||||
// TODO(jfore): Add more detailed error status codes.
|
||||
enum class CasStatusCode : int32_t {
|
||||
kUnknownError = 0,
|
||||
kNoError = 1,
|
||||
kCryptoInterfaceError = 2,
|
||||
kCryptoSessionError = 3,
|
||||
kCasLicenseError = 4,
|
||||
kIndividualizationError = 5,
|
||||
kInvalidParameter = 6,
|
||||
kDecryptionError = 7,
|
||||
kKeyNotFound = 8,
|
||||
kSessionNotFound = 9,
|
||||
kUnknownLicenseType = 10,
|
||||
kLicenseFileParseError = 11,
|
||||
kInvalidLicenseFile = 12,
|
||||
kInvalidPesData = 13,
|
||||
kDeferedEcmProcessing = 14,
|
||||
kAccessDeniedByParentalControl = 15,
|
||||
kUnknownEvent = 16,
|
||||
kOEMCryptoVersionMismatch = 17,
|
||||
};
|
||||
|
||||
class CasStatus {
|
||||
public:
|
||||
CasStatus(CasStatusCode status = CasStatusCode::kNoError,
|
||||
const std::string& err_string = std::string())
|
||||
: status_(status), err_string_(err_string) {}
|
||||
static CasStatus OkStatus() { return CasStatus(); }
|
||||
virtual ~CasStatus() {}
|
||||
|
||||
virtual CasStatusCode status_code() const { return status_; }
|
||||
virtual const std::string& error_string() const { return err_string_; }
|
||||
virtual bool ok() const { return status_ == CasStatusCode::kNoError; }
|
||||
|
||||
private:
|
||||
CasStatusCode status_;
|
||||
std::string err_string_;
|
||||
};
|
||||
|
||||
} // namespace wvcas
|
||||
|
||||
#endif // CAS_STATUS_H
|
||||
116
plugin/include/cas_types.h
Normal file
116
plugin/include/cas_types.h
Normal file
@@ -0,0 +1,116 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#ifndef CAS_TYPES_H
|
||||
#define CAS_TYPES_H
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
static const int kCryptoSessionErrorStart = 1000;
|
||||
|
||||
namespace wvcas {
|
||||
|
||||
typedef std::vector<uint8_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,
|
||||
kAesECB = 6,
|
||||
};
|
||||
|
||||
enum KeyStatus {
|
||||
kKeyStatusKeyUnknown,
|
||||
kKeyStatusUsable,
|
||||
kKeyStatusExpired,
|
||||
kKeyStatusOutputNotAllowed,
|
||||
kKeyStatusPending,
|
||||
kKeyStatusInternalError,
|
||||
};
|
||||
|
||||
enum SecurityLevel {
|
||||
kSecurityLevelUninitialized,
|
||||
kSecurityLevelL1,
|
||||
kSecurityLevelL2,
|
||||
kSecurityLevelL3,
|
||||
kSecurityLevelUnknown
|
||||
};
|
||||
|
||||
typedef std::vector<uint8_t> CasEcm;
|
||||
typedef std::vector<uint8_t> CasEmm;
|
||||
typedef std::vector<uint8_t> CasData;
|
||||
typedef std::string KeyId;
|
||||
typedef std::map<KeyId, KeyStatus> KeyStatusMap;
|
||||
|
||||
// Listener for events from the policy engine.
|
||||
class CasEventListener {
|
||||
public:
|
||||
CasEventListener() {}
|
||||
virtual ~CasEventListener() {}
|
||||
|
||||
// Notify listeners that a license renewal is needed.
|
||||
virtual void OnSessionRenewalNeeded() = 0;
|
||||
|
||||
// Notify listeners that the keys have changed state.
|
||||
virtual void OnSessionKeysChange(const KeyStatusMap& keys_status,
|
||||
bool has_new_usable_key) = 0;
|
||||
|
||||
// |new_expiry_time_seconds| of 0 means "never expires".
|
||||
virtual void OnExpirationUpdate(int64_t new_expiry_time_seconds) = 0;
|
||||
|
||||
// Notify listeners of the current renewal url.
|
||||
virtual void OnNewRenewalServerUrl(const std::string& renewal_server_url) = 0;
|
||||
|
||||
// Notify listeners of current license is expired.
|
||||
virtual void OnLicenseExpiration() = 0;
|
||||
|
||||
// Notify listeners of new age restriction value in processed ECM.
|
||||
virtual void OnAgeRestrictionUpdated(const WvCasSessionId& sessionId,
|
||||
uint8_t ecm_age_restriction) = 0;
|
||||
|
||||
// Notifies listeners of new session fingerprinting info.
|
||||
virtual void OnSessionFingerprintingUpdated(
|
||||
const WvCasSessionId& sessionId, const CasData& fingerprinting) = 0;
|
||||
|
||||
// Notifies listeners of new session service blocking info.
|
||||
virtual void OnSessionServiceBlockingUpdated(
|
||||
const WvCasSessionId& sessionId, const CasData& service_blocking) = 0;
|
||||
|
||||
// Notifies listeners of new fingerprinting info.
|
||||
virtual void OnFingerprintingUpdated(const CasData& fingerprinting) = 0;
|
||||
|
||||
// Notifies listeners of new service blocking info.
|
||||
virtual void OnServiceBlockingUpdated(const CasData& service_blocking) = 0;
|
||||
|
||||
virtual void OnEntitlementPeriodUpdateNeeded(
|
||||
const std::string& signed_license_request) = 0;
|
||||
|
||||
CasEventListener(const CasEventListener&) = delete;
|
||||
CasEventListener& operator=(const CasEventListener&) = delete;
|
||||
};
|
||||
|
||||
} // namespace wvcas
|
||||
|
||||
#endif // CAS_TYPES_H
|
||||
21
plugin/include/cas_util.h
Normal file
21
plugin/include/cas_util.h
Normal file
@@ -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 <memory>
|
||||
|
||||
#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 <typename T, typename... Args>
|
||||
std::unique_ptr<T> make_unique(Args&&... args) {
|
||||
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // CAS_UTIL_H_
|
||||
310
plugin/include/crypto_session.h
Normal file
310
plugin/include/crypto_session.h
Normal file
@@ -0,0 +1,310 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#ifndef CRYPTO_SESSION_H
|
||||
#define CRYPTO_SESSION_H
|
||||
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "OEMCryptoCENC.h"
|
||||
#include "cas_status.h"
|
||||
#include "cas_types.h"
|
||||
#include "oemcrypto_interface.h"
|
||||
#include "rw_lock.h"
|
||||
|
||||
namespace wvcas {
|
||||
|
||||
struct KeySlot {
|
||||
std::vector<uint8_t> key_id;
|
||||
std::vector<uint8_t> entitlement_key_id;
|
||||
std::vector<uint8_t> wrapped_key;
|
||||
std::vector<uint8_t> wrapped_key_iv;
|
||||
std::vector<uint8_t> 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 <class Func>
|
||||
static auto WithStaticFieldWriteLock(const char* tag, Func body)
|
||||
-> decltype(body());
|
||||
template <class Func>
|
||||
static auto WithStaticFieldReadLock(const char* tag, Func body)
|
||||
-> decltype(body());
|
||||
template <class Func>
|
||||
static auto WithOecWriteLock(const char* tag, Func body) -> decltype(body());
|
||||
template <class Func>
|
||||
static auto WithOecReadLock(const char* tag, Func body) -> decltype(body());
|
||||
template <class Func>
|
||||
auto WithOecSessionLock(const char* tag, Func body) -> decltype(body());
|
||||
|
||||
private:
|
||||
// The locking methods above should be used in preference to taking these
|
||||
// mutexes directly. If code takes these manually and needs to take more
|
||||
// than one, it must *always* take them in the order they are defined here.
|
||||
static wvutil::shared_mutex static_field_mutex_;
|
||||
static wvutil::shared_mutex oem_crypto_mutex_;
|
||||
std::mutex oem_crypto_session_mutex_;
|
||||
};
|
||||
|
||||
class CryptoInterface {
|
||||
CryptoInterface();
|
||||
|
||||
public:
|
||||
virtual ~CryptoInterface();
|
||||
|
||||
virtual OEMCryptoResult OEMCrypto_OpenSession(OEMCrypto_SESSION* session);
|
||||
virtual OEMCryptoResult OEMCrypto_CloseSession(OEMCrypto_SESSION session);
|
||||
virtual OEMCrypto_ProvisioningMethod OEMCrypto_GetProvisioningMethod();
|
||||
virtual OEMCryptoResult OEMCrypto_GetKeyData(uint8_t* keyData,
|
||||
size_t* keyDataLength);
|
||||
virtual uint32_t OEMCrypto_SupportedCertificates();
|
||||
virtual OEMCryptoResult OEMCrypto_GenerateNonce(OEMCrypto_SESSION session,
|
||||
uint32_t* nonce);
|
||||
virtual OEMCryptoResult OEMCrypto_GenerateDerivedKeys(
|
||||
OEMCrypto_SESSION session, const uint8_t* mac_key_context,
|
||||
uint32_t mac_key_context_length, const uint8_t* enc_key_context,
|
||||
uint32_t enc_key_context_length);
|
||||
virtual OEMCryptoResult OEMCrypto_PrepAndSignLicenseRequest(
|
||||
OEMCrypto_SESSION session, uint8_t* message, size_t message_length,
|
||||
size_t* core_message_size, uint8_t* signature, size_t* signature_length);
|
||||
virtual OEMCryptoResult OEMCrypto_PrepAndSignRenewalRequest(
|
||||
OEMCrypto_SESSION session, uint8_t* message, size_t message_length,
|
||||
size_t* core_message_size, uint8_t* signature, size_t* signature_length);
|
||||
virtual OEMCryptoResult OEMCrypto_PrepAndSignProvisioningRequest(
|
||||
OEMCrypto_SESSION session, uint8_t* message, size_t message_length,
|
||||
size_t* core_message_size, uint8_t* signature, size_t* signature_length);
|
||||
virtual OEMCryptoResult OEMCrypto_LoadProvisioning(
|
||||
OEMCrypto_SESSION session, const uint8_t* message, size_t message_length,
|
||||
size_t core_message_length, const uint8_t* signature,
|
||||
size_t signature_length, uint8_t* wrapped_private_key,
|
||||
size_t* wrapped_private_key_length);
|
||||
virtual OEMCryptoResult OEMCrypto_GetOEMPublicCertificate(
|
||||
OEMCrypto_SESSION session, uint8_t* public_cert,
|
||||
size_t* public_cert_length);
|
||||
virtual OEMCryptoResult OEMCrypto_LoadDRMPrivateKey(
|
||||
OEMCrypto_SESSION session, OEMCrypto_PrivateKeyType key_type,
|
||||
const uint8_t* wrapped_rsa_key, size_t wrapped_rsa_key_length);
|
||||
virtual OEMCryptoResult OEMCrypto_GenerateRSASignature(
|
||||
OEMCrypto_SESSION session, const uint8_t* message, size_t message_length,
|
||||
uint8_t* signature, size_t* signature_length,
|
||||
RSA_Padding_Scheme padding_scheme);
|
||||
virtual OEMCryptoResult OEMCrypto_DeriveKeysFromSessionKey(
|
||||
OEMCrypto_SESSION session, const uint8_t* enc_session_key,
|
||||
size_t enc_session_key_length, const uint8_t* mac_key_context,
|
||||
size_t mac_key_context_length, const uint8_t* enc_key_context,
|
||||
size_t enc_key_context_length);
|
||||
virtual OEMCryptoResult OEMCrypto_LoadLicense(OEMCrypto_SESSION session,
|
||||
const uint8_t* message,
|
||||
size_t message_length,
|
||||
size_t core_message_length,
|
||||
const uint8_t* signature,
|
||||
size_t signature_length);
|
||||
virtual OEMCryptoResult OEMCrypto_LoadRenewal(OEMCrypto_SESSION session,
|
||||
const uint8_t* message,
|
||||
size_t message_length,
|
||||
size_t core_message_length,
|
||||
const uint8_t* signature,
|
||||
size_t signature_length);
|
||||
virtual OEMCryptoResult OEMCrypto_LoadCasECMKeys(
|
||||
OEMCrypto_SESSION session, const uint8_t* message, size_t message_length,
|
||||
const OEMCrypto_EntitledContentKeyObject* even_key,
|
||||
const OEMCrypto_EntitledContentKeyObject* odd_key);
|
||||
virtual OEMCryptoResult OEMCrypto_GetHDCPCapability(
|
||||
OEMCrypto_HDCP_Capability* current, OEMCrypto_HDCP_Capability* max);
|
||||
virtual OEMCryptoResult OEMCrypto_GetDeviceID(uint8_t* deviceID,
|
||||
size_t* idLength);
|
||||
virtual const char* OEMCrypto_SecurityLevel();
|
||||
virtual OEMCryptoResult OEMCrypto_CreateEntitledKeySession(
|
||||
OEMCrypto_SESSION session, OEMCrypto_SESSION* entitled_key_session_id);
|
||||
virtual OEMCryptoResult OEMCrypto_RemoveEntitledKeySession(
|
||||
OEMCrypto_SESSION entitled_key_session_id);
|
||||
virtual OEMCryptoResult OEMCrypto_ReassociateEntitledKeySession(
|
||||
OEMCrypto_SESSION key_sid, OEMCrypto_SESSION oec_sid);
|
||||
virtual uint32_t OEMCrypto_APIVersion();
|
||||
virtual OEMCryptoResult OEMCrypto_GetOEMKeyToken(
|
||||
OEMCrypto_SESSION key_session, uint8_t* key_token,
|
||||
size_t* key_token_length);
|
||||
virtual OEMCryptoResult OEMCrypto_GetSignatureHashAlgorithm(
|
||||
OEMCrypto_SESSION session, OEMCrypto_SignatureHashAlgorithm* algorithm);
|
||||
|
||||
// This is the factory method used to enable the oemcrypto interface.
|
||||
static OEMCryptoResult create(std::unique_ptr<CryptoInterface>* 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 <typename CI>
|
||||
static OEMCryptoResult create(std::unique_ptr<CryptoInterface>* 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<CryptoInterface>* init);
|
||||
|
||||
static bool initialized_;
|
||||
static int session_count_;
|
||||
static std::unique_ptr<CryptoLock> lock_;
|
||||
OEMCryptoInterface* oemcrypto_interface_;
|
||||
};
|
||||
|
||||
class SupportedCertificates {
|
||||
public:
|
||||
explicit SupportedCertificates(uint32_t supported) : supported_(supported) {}
|
||||
bool rsa_2048bit() { return OEMCrypto_Supports_RSA_2048bit & supported_; }
|
||||
bool rsa_3072bit() { return OEMCrypto_Supports_RSA_3072bit & supported_; }
|
||||
bool rsa_CASTbit() { return OEMCrypto_Supports_RSA_CAST & supported_; }
|
||||
|
||||
private:
|
||||
uint32_t supported_;
|
||||
};
|
||||
|
||||
// CryptoSession implements the core methods need to interface with OEMCrypto.
|
||||
class CryptoSession {
|
||||
public:
|
||||
explicit CryptoSession();
|
||||
virtual ~CryptoSession();
|
||||
virtual CasStatus initialize();
|
||||
virtual CasStatus reset();
|
||||
virtual CasStatus close();
|
||||
virtual CasProvisioningMethod provisioning_method();
|
||||
virtual CasStatus GetKeyData(uint8_t* keyData, size_t* keyDataLength);
|
||||
virtual SupportedCertificates supported_certificates();
|
||||
virtual CasStatus GenerateNonce(uint32_t* nonce);
|
||||
virtual CasStatus GenerateDerivedKeys(const uint8_t* mac_key_context,
|
||||
uint32_t mac_key_context_length,
|
||||
const uint8_t* enc_key_context,
|
||||
uint32_t enc_key_context_length);
|
||||
virtual CasStatus PrepareAndSignLicenseRequest(
|
||||
const std::string& message, std::string* core_message,
|
||||
std::string* signature, bool& should_specify_algorithm,
|
||||
OEMCrypto_SignatureHashAlgorithm& algorithm);
|
||||
virtual CasStatus PrepareAndSignRenewalRequest(const std::string& message,
|
||||
std::string* core_message,
|
||||
std::string* signature);
|
||||
virtual CasStatus PrepareAndSignProvisioningRequest(
|
||||
const std::string& message, std::string* core_message,
|
||||
std::string* signature, bool& should_specify_algorithm,
|
||||
OEMCrypto_SignatureHashAlgorithm& algorithm);
|
||||
virtual CasStatus LoadProvisioning(const std::string& signed_message,
|
||||
const std::string& core_message,
|
||||
const std::string& signature,
|
||||
std::string* wrapped_private_key);
|
||||
virtual CasStatus GetOEMPublicCertificate(uint8_t* public_cert,
|
||||
size_t* public_cert_length);
|
||||
virtual CasStatus LoadDeviceRSAKey(const uint8_t* wrapped_rsa_key,
|
||||
size_t wrapped_rsa_key_length);
|
||||
virtual CasStatus GenerateRSASignature(const uint8_t* message,
|
||||
size_t message_length,
|
||||
uint8_t* signature,
|
||||
size_t* signature_length,
|
||||
RSA_Padding_Scheme padding_scheme);
|
||||
virtual CasStatus DeriveKeysFromSessionKey(const uint8_t* enc_session_key,
|
||||
size_t enc_session_key_length,
|
||||
const uint8_t* mac_key_context,
|
||||
size_t mac_key_context_length,
|
||||
const uint8_t* enc_key_context,
|
||||
size_t enc_key_context_length);
|
||||
virtual CasStatus LoadLicense(const std::string& signed_message,
|
||||
const std::string& core_message,
|
||||
const std::string& signature);
|
||||
virtual CasStatus LoadRenewal(const std::string& signed_message,
|
||||
const std::string& core_message,
|
||||
const std::string& signature);
|
||||
// LoadCasECMKeys loads the ecm keys into the crypto library making them
|
||||
// available for use.
|
||||
// |odd_key| - if not null, contains control word data.
|
||||
// |even_key| - if not null, contains control word data.
|
||||
virtual CasStatus LoadCasECMKeys(OEMCrypto_SESSION session,
|
||||
const KeySlot* even_key,
|
||||
const KeySlot* odd_key);
|
||||
virtual bool GetHdcpCapabilities(HdcpCapability* current,
|
||||
HdcpCapability* max);
|
||||
virtual CasStatus GetDeviceID(std::string* buffer);
|
||||
virtual const char* SecurityLevel();
|
||||
virtual CasStatus CreateEntitledKeySession(
|
||||
OEMCrypto_SESSION* entitled_key_session_id);
|
||||
virtual CasStatus RemoveEntitledKeySession(
|
||||
OEMCrypto_SESSION entitled_key_session_id);
|
||||
virtual CasStatus ReassociateEntitledKeySession(
|
||||
OEMCrypto_SESSION entitled_key_session_id);
|
||||
virtual CasStatus APIVersion(uint32_t* api_version);
|
||||
virtual CasStatus GetOEMKeyToken(OEMCrypto_SESSION entitled_key_session_id,
|
||||
std::vector<uint8_t>& token);
|
||||
|
||||
CryptoSession(const CryptoSession&) = delete;
|
||||
CryptoSession& operator=(const CryptoSession&) = delete;
|
||||
|
||||
private:
|
||||
virtual OEMCryptoResult getCryptoInterface(
|
||||
std::unique_ptr<CryptoInterface>* interface);
|
||||
|
||||
// TODO(jfore, widevine-eng): Merge CryptoInterface into CryptoSession and
|
||||
// drop this shared pointer.
|
||||
std::unique_ptr<CryptoInterface> crypto_interface_;
|
||||
OEMCrypto_SESSION session_;
|
||||
};
|
||||
|
||||
} // namespace wvcas
|
||||
|
||||
#endif // CRYPTO_SESSION_H
|
||||
59
plugin/include/ecm_parser.h
Normal file
59
plugin/include/ecm_parser.h
Normal file
@@ -0,0 +1,59 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#ifndef ECM_PARSER_H
|
||||
#define ECM_PARSER_H
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "cas_types.h"
|
||||
#include "media_cas.pb.h"
|
||||
|
||||
namespace wvcas {
|
||||
|
||||
enum class KeySlotId { kEvenKeySlot, kOddKeySlot };
|
||||
|
||||
// EcmParser allows random access to the fields of an ECM.
|
||||
class EcmParser {
|
||||
public:
|
||||
EcmParser() = default;
|
||||
virtual ~EcmParser() {}
|
||||
|
||||
// The EcmParser factory method.
|
||||
// Validates the ecm. If validations is successful returns true and constructs
|
||||
// an EcmParser in |parser| using |ecm|.
|
||||
static std::unique_ptr<EcmParser> Create(const CasEcm& ecm);
|
||||
|
||||
// Accessor methods.
|
||||
virtual uint8_t version() const = 0;
|
||||
virtual CryptoMode crypto_mode() const = 0;
|
||||
virtual bool rotation_enabled() const = 0;
|
||||
virtual size_t content_iv_size() const = 0;
|
||||
virtual uint8_t age_restriction() const = 0;
|
||||
virtual std::vector<uint8_t> entitlement_key_id(KeySlotId id) const = 0;
|
||||
virtual std::vector<uint8_t> content_key_id(KeySlotId id) const = 0;
|
||||
virtual std::vector<uint8_t> wrapped_key_data(KeySlotId id) const = 0;
|
||||
virtual std::vector<uint8_t> wrapped_key_iv(KeySlotId id) const = 0;
|
||||
virtual std::vector<uint8_t> content_iv(KeySlotId id) const = 0;
|
||||
|
||||
// Process group content keys instead of the normal content keys.
|
||||
virtual bool set_group_id(const std::string& group_id) = 0;
|
||||
|
||||
virtual bool has_fingerprinting() const = 0;
|
||||
virtual video_widevine::Fingerprinting fingerprinting() const = 0;
|
||||
virtual bool has_service_blocking() const = 0;
|
||||
virtual video_widevine::ServiceBlocking service_blocking() const = 0;
|
||||
// The serialized payload that the signature is calculated on.
|
||||
virtual std::string ecm_serialized_payload() const = 0;
|
||||
virtual std::string signature() const = 0;
|
||||
|
||||
virtual bool is_entitlement_rotation_enabled() const = 0;
|
||||
virtual uint32_t entitlement_period_index() const = 0;
|
||||
virtual uint32_t entitlement_rotation_window_left() const = 0;
|
||||
};
|
||||
|
||||
} // namespace wvcas
|
||||
|
||||
#endif // ECM_PARSER_H
|
||||
85
plugin/include/ecm_parser_v2.h
Normal file
85
plugin/include/ecm_parser_v2.h
Normal file
@@ -0,0 +1,85 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#ifndef ECM_PARSER_V2_H
|
||||
#define ECM_PARSER_V2_H
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "cas_types.h"
|
||||
#include "ecm_parser.h"
|
||||
|
||||
namespace wvcas {
|
||||
|
||||
struct EcmKeyData;
|
||||
|
||||
// EcmParserV2 allows random access to the fields of an ECM version 2 and under.
|
||||
// It should be initialized via EcmParser factory create only.
|
||||
class EcmParserV2 : public EcmParser {
|
||||
public:
|
||||
~EcmParserV2() override = default;
|
||||
EcmParserV2(const EcmParserV2&) = delete;
|
||||
EcmParserV2& operator=(const EcmParserV2&) = delete;
|
||||
|
||||
// The EcmParserV2 factory method.
|
||||
// |ecm| must be Widevine ECM v2 or under without section header.
|
||||
// Validates the ecm. The only validation performed is to ensure that the ecm
|
||||
// passed in is large enough to hold a single key entry. If validations is
|
||||
// successful returns true and constructs an EcmParserV2 in |parser| using
|
||||
// |ecm|.
|
||||
static bool create(const CasEcm& cas_ecm,
|
||||
std::unique_ptr<EcmParserV2>* parser);
|
||||
|
||||
// Accessor methods.
|
||||
uint8_t version() const override;
|
||||
CryptoMode crypto_mode() const override;
|
||||
bool rotation_enabled() const override;
|
||||
size_t content_iv_size() const override;
|
||||
uint8_t age_restriction() const override;
|
||||
std::vector<uint8_t> entitlement_key_id(KeySlotId id) const override;
|
||||
std::vector<uint8_t> content_key_id(KeySlotId id) const override;
|
||||
std::vector<uint8_t> wrapped_key_data(KeySlotId id) const override;
|
||||
std::vector<uint8_t> wrapped_key_iv(KeySlotId id) const override;
|
||||
std::vector<uint8_t> content_iv(KeySlotId id) const override;
|
||||
|
||||
// Group keys not supported in v2.
|
||||
bool set_group_id(const std::string& group_id) override {
|
||||
return group_id.empty();
|
||||
};
|
||||
|
||||
// ECM v2 or under does not have these fields.
|
||||
bool has_fingerprinting() const override { return false; }
|
||||
video_widevine::Fingerprinting fingerprinting() const override {
|
||||
video_widevine::Fingerprinting fingerprinting;
|
||||
return fingerprinting;
|
||||
}
|
||||
bool has_service_blocking() const override { return false; };
|
||||
video_widevine::ServiceBlocking service_blocking() const override {
|
||||
video_widevine::ServiceBlocking service_blocking;
|
||||
return service_blocking;
|
||||
}
|
||||
std::string ecm_serialized_payload() const override { return ""; }
|
||||
std::string signature() const override { return ""; }
|
||||
|
||||
bool is_entitlement_rotation_enabled() const override { return false; }
|
||||
uint32_t entitlement_period_index() const override { return 0; }
|
||||
uint32_t entitlement_rotation_window_left() const override { return 0; }
|
||||
|
||||
private:
|
||||
// Constructs an EcmParserV2 using |ecm|.
|
||||
explicit EcmParserV2(const CasEcm& ecm);
|
||||
|
||||
size_t key_data_size() const;
|
||||
// Returns false if the ecm used to construct the object is not a valid size.
|
||||
// TODO(jfore): Add validation using the version field.
|
||||
bool is_valid_size() const;
|
||||
const EcmKeyData* key_slot_data(KeySlotId id) const;
|
||||
|
||||
CasEcm ecm_;
|
||||
};
|
||||
|
||||
} // namespace wvcas
|
||||
|
||||
#endif // ECM_PARSER_V2_H
|
||||
68
plugin/include/ecm_parser_v3.h
Normal file
68
plugin/include/ecm_parser_v3.h
Normal file
@@ -0,0 +1,68 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#ifndef ECM_PARSER_V3_H
|
||||
#define ECM_PARSER_V3_H
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "cas_types.h"
|
||||
#include "ecm_parser.h"
|
||||
#include "media_cas.pb.h"
|
||||
|
||||
namespace wvcas {
|
||||
|
||||
// EcmParser allows random access to the fields of an ECM.
|
||||
class EcmParserV3 : public EcmParser {
|
||||
public:
|
||||
~EcmParserV3() override = default;
|
||||
EcmParserV3(const EcmParserV3&) = delete;
|
||||
EcmParserV3& operator=(const EcmParserV3&) = delete;
|
||||
|
||||
// The EcmParserV3 factory method.
|
||||
// |ecm| must be Widevine ECM v3 (or higher if compatible) without section
|
||||
// header. Validates the ecm. If validations is successful returns an
|
||||
// EcmParserV3, otherwise an nullptr.
|
||||
static std::unique_ptr<EcmParserV3> Create(const CasEcm& ecm);
|
||||
|
||||
// Accessor methods.
|
||||
uint8_t version() const override;
|
||||
CryptoMode crypto_mode() const override;
|
||||
bool rotation_enabled() const override;
|
||||
size_t content_iv_size() const override;
|
||||
uint8_t age_restriction() const override;
|
||||
std::vector<uint8_t> entitlement_key_id(KeySlotId id) const override;
|
||||
std::vector<uint8_t> content_key_id(KeySlotId id) const override;
|
||||
std::vector<uint8_t> wrapped_key_data(KeySlotId id) const override;
|
||||
std::vector<uint8_t> wrapped_key_iv(KeySlotId id) const override;
|
||||
std::vector<uint8_t> content_iv(KeySlotId id) const override;
|
||||
|
||||
bool set_group_id(const std::string& group_id) override;
|
||||
|
||||
bool has_fingerprinting() const override;
|
||||
video_widevine::Fingerprinting fingerprinting() const override;
|
||||
bool has_service_blocking() const override;
|
||||
video_widevine::ServiceBlocking service_blocking() const override;
|
||||
// The serialized payload that the signature is calculated on.
|
||||
std::string ecm_serialized_payload() const override;
|
||||
std::string signature() const override;
|
||||
|
||||
bool is_entitlement_rotation_enabled() const override;
|
||||
uint32_t entitlement_period_index() const override;
|
||||
uint32_t entitlement_rotation_window_left() const override;
|
||||
|
||||
private:
|
||||
// Constructs an EcmParserV3 using |ecm|.
|
||||
EcmParserV3(video_widevine::SignedEcmPayload signed_ecm_payload,
|
||||
video_widevine::EcmPayload ecm_payload);
|
||||
video_widevine::SignedEcmPayload signed_ecm_payload_;
|
||||
video_widevine::EcmPayload ecm_payload_;
|
||||
video_widevine::EcmKeyData even_key_data_;
|
||||
video_widevine::EcmKeyData odd_key_data_;
|
||||
};
|
||||
|
||||
} // namespace wvcas
|
||||
|
||||
#endif // ECM_PARSER_V3_H
|
||||
49
plugin/include/emm_parser.h
Normal file
49
plugin/include/emm_parser.h
Normal file
@@ -0,0 +1,49 @@
|
||||
// Copyright 2020 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#ifndef EMM_PARSER_H
|
||||
#define EMM_PARSER_H
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "cas_types.h"
|
||||
#include "media_cas.pb.h"
|
||||
|
||||
namespace wvcas {
|
||||
|
||||
using video_widevine::EmmPayload;
|
||||
|
||||
class EmmParser {
|
||||
public:
|
||||
EmmParser(const EmmParser&) = delete;
|
||||
EmmParser& operator=(const EmmParser&) = delete;
|
||||
virtual ~EmmParser() = default;
|
||||
|
||||
// The EmmParser factory method.
|
||||
// The methods validates the passed in |emm|. If validation is successful, it
|
||||
// constructs and returns an EmmParser. Otherwise, nullptr is returned.
|
||||
static std::unique_ptr<const EmmParser> Create(const CasEmm& emm);
|
||||
|
||||
// Accessor methods.
|
||||
virtual uint64_t timestamp() const { return timestamp_; }
|
||||
virtual std::string signature() const { return signature_; }
|
||||
virtual EmmPayload emm_payload() const { return emm_payload_; };
|
||||
|
||||
protected:
|
||||
// Called by the factory create and unit test.
|
||||
EmmParser() = default;
|
||||
|
||||
private:
|
||||
bool Parse(int start_index, const CasEmm& emm);
|
||||
|
||||
uint8_t version_;
|
||||
uint64_t timestamp_;
|
||||
EmmPayload emm_payload_;
|
||||
std::string signature_;
|
||||
};
|
||||
|
||||
} // namespace wvcas
|
||||
|
||||
#endif // EMM_PARSER_H
|
||||
210
plugin/include/license_key_status.h
Normal file
210
plugin/include/license_key_status.h
Normal file
@@ -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 <map>
|
||||
|
||||
#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<WidevinePsshData_EntitledKey>& keys);
|
||||
|
||||
LicenseKeys(const LicenseKeys&) = delete;
|
||||
LicenseKeys& operator=(const LicenseKeys&) = delete;
|
||||
|
||||
private:
|
||||
typedef ::video_widevine::License::KeyContainer KeyContainer;
|
||||
typedef std::map<KeyId, LicenseKeyStatus*>::const_iterator
|
||||
LicenseKeyStatusIterator;
|
||||
|
||||
void Clear();
|
||||
|
||||
bool is_initialized_;
|
||||
// |key_statuses_| can hold either content key statuses, or entitlement key
|
||||
// statuses.
|
||||
std::map<KeyId, LicenseKeyStatus*> 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<KeyId, KeyId> 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<VideoResolutionConstraint>
|
||||
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_
|
||||
138
plugin/include/oemcrypto_interface.h
Normal file
138
plugin/include/oemcrypto_interface.h
Normal file
@@ -0,0 +1,138 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#ifndef OEMCRYPTO_INTERFACE_H
|
||||
#define OEMCRYPTO_INTERFACE_H
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "OEMCryptoCENC.h"
|
||||
|
||||
namespace wvcas {
|
||||
|
||||
// InputStreamParams mirrors the parameters in OEMCrypto API. The
|
||||
// purpose is to allow OEMCrypto_Descramble to be mocked. OEMCrypto_Descramble
|
||||
// takes 11 parameters as of API V15. GoogleMock allows a maximum of 10.
|
||||
struct InputStreamParams {
|
||||
const uint8_t* data_addr;
|
||||
size_t data_length;
|
||||
bool is_encrypted;
|
||||
|
||||
InputStreamParams(){};
|
||||
InputStreamParams(const uint8_t* data_addr, size_t data_length,
|
||||
bool is_encrypted)
|
||||
: data_addr(data_addr),
|
||||
data_length(data_length),
|
||||
is_encrypted(is_encrypted) {}
|
||||
};
|
||||
|
||||
// Calls to oemcrypto are called via this object. The purpose of this object is
|
||||
// to allow OEMCrypto to be mocked. The implementation of this object only wraps
|
||||
// OEMCrypto methods adding limited additional functionality. Added
|
||||
// functionality is limited to adapt the input parameters to the oemcrypto api.
|
||||
// Method signatures in this class can only have a maximum of 10 parameters to
|
||||
// maintain compatibility with googlemock.
|
||||
class OEMCryptoInterface {
|
||||
public:
|
||||
OEMCryptoInterface();
|
||||
virtual ~OEMCryptoInterface();
|
||||
|
||||
virtual OEMCryptoResult OEMCrypto_Initialize(void);
|
||||
virtual OEMCryptoResult OEMCrypto_Terminate(void);
|
||||
virtual OEMCryptoResult OEMCrypto_OpenSession(
|
||||
OEMCrypto_SESSION* session) const;
|
||||
virtual OEMCryptoResult OEMCrypto_CloseSession(
|
||||
OEMCrypto_SESSION session) const;
|
||||
virtual OEMCrypto_ProvisioningMethod OEMCrypto_GetProvisioningMethod() const;
|
||||
virtual OEMCryptoResult OEMCrypto_GetKeyData(uint8_t* keyData,
|
||||
size_t* keyDataLength) const;
|
||||
virtual uint32_t OEMCrypto_SupportedCertificates() const;
|
||||
virtual OEMCryptoResult OEMCrypto_GenerateNonce(OEMCrypto_SESSION session,
|
||||
uint32_t* nonce) const;
|
||||
virtual OEMCryptoResult OEMCrypto_GenerateDerivedKeys(
|
||||
OEMCrypto_SESSION session, const uint8_t* mac_key_context,
|
||||
uint32_t mac_key_context_length, const uint8_t* enc_key_context,
|
||||
uint32_t enc_key_context_length) const;
|
||||
virtual OEMCryptoResult OEMCrypto_PrepAndSignLicenseRequest(
|
||||
OEMCrypto_SESSION session, uint8_t* message, size_t message_length,
|
||||
size_t* core_message_size, uint8_t* signature,
|
||||
size_t* signature_length) const;
|
||||
virtual OEMCryptoResult OEMCrypto_PrepAndSignRenewalRequest(
|
||||
OEMCrypto_SESSION session, uint8_t* message, size_t message_length,
|
||||
size_t* core_message_size, uint8_t* signature,
|
||||
size_t* signature_length) const;
|
||||
virtual OEMCryptoResult OEMCrypto_PrepAndSignProvisioningRequest(
|
||||
OEMCrypto_SESSION session, uint8_t* message, size_t message_length,
|
||||
size_t* core_message_size, uint8_t* signature,
|
||||
size_t* signature_length) const;
|
||||
virtual OEMCryptoResult OEMCrypto_LoadProvisioning(
|
||||
OEMCrypto_SESSION session, const uint8_t* message, size_t message_length,
|
||||
size_t core_message_length, const uint8_t* signature,
|
||||
size_t signature_length, uint8_t* wrapped_private_key,
|
||||
size_t* wrapped_private_key_length) const;
|
||||
virtual OEMCryptoResult OEMCrypto_GetOEMPublicCertificate(
|
||||
OEMCrypto_SESSION session, uint8_t* public_cert,
|
||||
size_t* public_cert_length) const;
|
||||
virtual OEMCryptoResult OEMCrypto_LoadDRMPrivateKey(
|
||||
OEMCrypto_SESSION session, OEMCrypto_PrivateKeyType key_type,
|
||||
const uint8_t* wrapped_rsa_key, size_t wrapped_rsa_key_length) const;
|
||||
virtual OEMCryptoResult OEMCrypto_GenerateRSASignature(
|
||||
OEMCrypto_SESSION session, const uint8_t* message, size_t message_length,
|
||||
uint8_t* signature, size_t* signature_length,
|
||||
RSA_Padding_Scheme padding_scheme) const;
|
||||
virtual OEMCryptoResult OEMCrypto_DeriveKeysFromSessionKey(
|
||||
OEMCrypto_SESSION session, const uint8_t* enc_session_key,
|
||||
size_t enc_session_key_length, const uint8_t* mac_key_context,
|
||||
size_t mac_key_context_length, const uint8_t* enc_key_context,
|
||||
size_t enc_key_context_length) const;
|
||||
virtual OEMCryptoResult OEMCrypto_LoadLicense(OEMCrypto_SESSION session,
|
||||
const uint8_t* message,
|
||||
size_t message_length,
|
||||
size_t core_message_length,
|
||||
const uint8_t* signature,
|
||||
size_t signature_length) const;
|
||||
virtual OEMCryptoResult OEMCrypto_LoadRenewal(OEMCrypto_SESSION session,
|
||||
const uint8_t* message,
|
||||
size_t message_length,
|
||||
size_t core_message_length,
|
||||
const uint8_t* signature,
|
||||
size_t signature_length) const;
|
||||
virtual OEMCryptoResult OEMCrypto_LoadCasECMKeys(
|
||||
OEMCrypto_SESSION session, const uint8_t* message, size_t message_length,
|
||||
const OEMCrypto_EntitledContentKeyObject* even_key,
|
||||
const OEMCrypto_EntitledContentKeyObject* odd_key) const;
|
||||
virtual OEMCryptoResult OEMCrypto_GetHDCPCapability(
|
||||
OEMCrypto_HDCP_Capability* current, OEMCrypto_HDCP_Capability* max) const;
|
||||
virtual OEMCryptoResult OEMCrypto_GetDeviceID(uint8_t* deviceID,
|
||||
size_t* idLength) const;
|
||||
virtual OEMCryptoResult OEMCrypto_LoadTestKeybox(const uint8_t* buffer,
|
||||
size_t length) const;
|
||||
virtual const char* OEMCrypto_SecurityLevel() const;
|
||||
virtual OEMCryptoResult OEMCrypto_CreateEntitledKeySession(
|
||||
OEMCrypto_SESSION oec_session, OEMCrypto_SESSION* key_session) const;
|
||||
virtual OEMCryptoResult OEMCrypto_RemoveEntitledKeySession(
|
||||
OEMCrypto_SESSION key_session) const;
|
||||
virtual OEMCryptoResult OEMCrypto_ReassociateEntitledKeySession(
|
||||
OEMCrypto_SESSION key_session, OEMCrypto_SESSION oec_session) const;
|
||||
virtual uint32_t OEMCrypto_APIVersion() const;
|
||||
virtual OEMCryptoResult OEMCrypto_GetOEMKeyToken(
|
||||
OEMCrypto_SESSION key_session, uint8_t* key_token,
|
||||
size_t* key_token_length) const;
|
||||
virtual OEMCryptoResult OEMCrypto_GetSignatureHashAlgorithm(
|
||||
OEMCrypto_SESSION session,
|
||||
OEMCrypto_SignatureHashAlgorithm* algorithm) const;
|
||||
|
||||
OEMCryptoInterface(const OEMCryptoInterface&) = delete;
|
||||
OEMCryptoInterface& operator=(const OEMCryptoInterface&) = delete;
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> impl_;
|
||||
};
|
||||
|
||||
} // namespace wvcas
|
||||
|
||||
#endif // OEMCRYPTO_INTERFACE_H
|
||||
214
plugin/include/policy_engine.h
Normal file
214
plugin/include/policy_engine.h
Normal file
@@ -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 <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#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<CryptoSession> 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<WidevinePsshData_EntitledKey>& 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<wvcas::LicenseKeys> CreateLicenseKeys();
|
||||
|
||||
virtual std::unique_ptr<wvutil::Clock> 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<LicenseKeys> license_keys_;
|
||||
std::unique_ptr<wvutil::Clock> 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<wvcas::CryptoSession> crypto_session_;
|
||||
uint32_t current_resolution_ = 0;
|
||||
std::string renewal_server_url_;
|
||||
};
|
||||
|
||||
} // namespace wvcas
|
||||
|
||||
#endif // WIDEVINE_CAS_POLICY_ENGINE_H_
|
||||
210
plugin/include/widevine_cas_api.h
Normal file
210
plugin/include/widevine_cas_api.h
Normal file
@@ -0,0 +1,210 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#ifndef WIDEVINE_CAS_API_H
|
||||
#define WIDEVINE_CAS_API_H
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
|
||||
#include "cas_license.h"
|
||||
#include "cas_media_id.h"
|
||||
#include "cas_status.h"
|
||||
#include "cas_types.h"
|
||||
#include "crypto_session.h"
|
||||
#include "ecm_parser.h"
|
||||
#include "emm_parser.h"
|
||||
#include "file_store.h"
|
||||
#include "media_cas.pb.h"
|
||||
#include "timer.h"
|
||||
#include "widevine_cas_session.h"
|
||||
|
||||
namespace wvcas {
|
||||
|
||||
// TODO(jfore): Fix the function name inconsistency in this class. These
|
||||
// functions were migrated from the android plugin api implementation. They
|
||||
// should not follow Android's style.
|
||||
class WidevineCas : public wvutil::TimerHandler {
|
||||
public:
|
||||
WidevineCas() {}
|
||||
virtual ~WidevineCas() {}
|
||||
|
||||
virtual CasStatus initialize(CasEventListener* event_listener);
|
||||
|
||||
// Open a session for descrambling a program, or one or more elementary
|
||||
// streams.
|
||||
virtual CasStatus openSession(WvCasSessionId* sessionId);
|
||||
|
||||
// Close a previously opened session.
|
||||
virtual CasStatus closeSession(const WvCasSessionId& sessionId);
|
||||
|
||||
// Process an EMM which may contain fingerprinting and service blocking info.
|
||||
virtual CasStatus processEmm(const CasEmm& emm);
|
||||
|
||||
// Process an ECM from the ECM stream for this session’s elementary
|
||||
// stream.
|
||||
virtual CasStatus processEcm(const WvCasSessionId& sessionId,
|
||||
const CasEcm& ecm);
|
||||
|
||||
// Generates a device provisioning request message in |provisioning_request|.
|
||||
virtual CasStatus generateDeviceProvisioningRequest(
|
||||
std::string* provisioning_request);
|
||||
|
||||
// Processes a |response| to provisioning request.
|
||||
virtual CasStatus handleProvisioningResponse(const std::string& response);
|
||||
|
||||
// Generates an entitlement license request in |entitlement_request| for the
|
||||
// media described in |init_data|.
|
||||
virtual CasStatus generateEntitlementRequest(const std::string& init_data,
|
||||
std::string* entitlement_request,
|
||||
std::string& license_id);
|
||||
|
||||
// Processes the entitlement |response| to a entitlement license request.
|
||||
// |license_id| is the id of the license installed. Can be used to select
|
||||
// which license to install.
|
||||
// |multi_content_license_info| contains the message that can be sent to the
|
||||
// app if the installed license is a multi content license.
|
||||
// |group_license_info| contains the message that can be sent to the app if
|
||||
// the installed license is a group license.
|
||||
virtual CasStatus handleEntitlementResponse(
|
||||
const std::string& response, std::string& license_id,
|
||||
std::string& multi_content_license_info, std::string& group_license_info);
|
||||
|
||||
// Generates an entitlement license request in |entitlement_request| for the
|
||||
// media described in |init_data|.
|
||||
virtual CasStatus generateEntitlementRenewalRequest(
|
||||
std::string* entitlement_renewal_request);
|
||||
|
||||
// Processes the entitlement renewal |response| to a entitlement license
|
||||
// request.
|
||||
virtual CasStatus handleEntitlementRenewalResponse(
|
||||
const std::string& response, std::string& license_id);
|
||||
|
||||
// Generates an entitlement license request in a new crypto session, and send
|
||||
// the license request as an event to the app.
|
||||
virtual CasStatus generateEntitlementPeriodUpdateRequest(
|
||||
const std::string& init_data);
|
||||
|
||||
// Processes the license |response| to switch the current license to this
|
||||
// new one.
|
||||
virtual CasStatus handleEntitlementPeriodUpdateResponse(
|
||||
const std::string& response, std::string& license_id);
|
||||
|
||||
// Returns true if the device has been provisioned with a device certificate.
|
||||
virtual bool is_provisioned() const;
|
||||
|
||||
// Processes the CAS |private_data| from a CAT table. If successful a
|
||||
// serialized pssh data is retured in |init_data|.
|
||||
virtual CasStatus ProcessCAPrivateData(const CasData& private_data,
|
||||
std::string* init_data);
|
||||
|
||||
// Processes the CAS |private_data| from a PMT table. If successful a
|
||||
// serialized pssh data is retured in |init_data|. The CA private data can be
|
||||
// unique to the ecm session indicated by |session_id|.
|
||||
virtual CasStatus ProcessSessionCAPrivateData(
|
||||
const WvCasSessionId& session_id, const CasData& private_data,
|
||||
std::string* init_data);
|
||||
// Returns the device unique identifier.
|
||||
virtual CasStatus GetUniqueID(std::string* buffer);
|
||||
|
||||
// Set the minimum age required to process ECM.
|
||||
virtual CasStatus HandleSetParentalControlAge(const CasData& data);
|
||||
|
||||
// Remove the license file given the filename user provides.
|
||||
virtual CasStatus RemoveLicense(const std::string& file_name);
|
||||
|
||||
// Record the license id that user provides. This license id will be used to
|
||||
// select license if multiple licenses exist.
|
||||
virtual CasStatus RecordLicenseId(const std::string& license_id);
|
||||
|
||||
void OnTimerEvent() override;
|
||||
|
||||
// Stops the timer thread. Called by CAS plugin destructor to avoid race.
|
||||
void StopTimer();
|
||||
|
||||
private:
|
||||
virtual CasStatus HandleStoredDrmCert(const std::string& certificate);
|
||||
virtual CasStatus HandleProcessEcm(const WvCasSessionId& sessionId,
|
||||
const CasEcm& ecm);
|
||||
virtual CasStatus HandleDeferredECMs();
|
||||
// Extracts the entitlement rotation period index from ECM if specified, and
|
||||
// store it. The function should be called before any license request and the
|
||||
// extracted index will be included in the license request.
|
||||
virtual void TryExtractEntitlementPeriodIndex(const CasEcm& ecm);
|
||||
// Returns true if an offline license with |filename| is successfully loaded.
|
||||
virtual bool TryReuseStoredLicense(const std::string& filename);
|
||||
// Check if a new license is needed due to entitlement period changes. If so,
|
||||
// it will call generateEntitlementPeriodUpdateRequest().
|
||||
void CheckEntitlementPeriodUpdate(uint32_t period_index,
|
||||
uint32_t window_left);
|
||||
|
||||
virtual std::shared_ptr<CryptoSession> getCryptoSession();
|
||||
virtual std::unique_ptr<CasLicense> getCasLicense();
|
||||
virtual std::unique_ptr<wvutil::FileSystem> getFileSystem();
|
||||
virtual std::shared_ptr<WidevineCasSession> newCasSession();
|
||||
virtual std::unique_ptr<EcmParser> getEcmParser(const CasEcm& ecm) const;
|
||||
|
||||
// Creates an EmmParser. Marked as virtual for easier unit test.
|
||||
virtual std::unique_ptr<const EmmParser> getEmmParser(
|
||||
const CasEmm& emm) const;
|
||||
std::vector<uint8_t> GenerateFingerprintingEventMessage(
|
||||
const video_widevine::Fingerprinting& fingerprinting) const;
|
||||
std::vector<uint8_t> GenerateServiceBlockingEventMessage(
|
||||
const video_widevine::ServiceBlocking& service_blocking) const;
|
||||
|
||||
// The CryptoSession will be shared by the all cas sessions. It is also needed
|
||||
// by the cas api to generate EMM requests.
|
||||
std::shared_ptr<CryptoSession> crypto_session_;
|
||||
std::unique_ptr<CasLicense> cas_license_;
|
||||
std::unique_ptr<wvutil::FileSystem> 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<CasMediaId> 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<WvCasSessionId, const CasEcm> deferred_ecms_;
|
||||
// The value |has_license_| will be false when the plugin is created. Once a
|
||||
// license is loaded, |has_license_| will be set to true.
|
||||
bool has_license_ = false;
|
||||
// The age_restriction field in ECM must be greater or equal to
|
||||
// |parental_control_min_age|. Otherwise, ECM will stop being processed.
|
||||
uint parental_control_age_ = 0;
|
||||
// The requested_license_id helps to indicate which license file current
|
||||
// content will use if multiple licenses exist.
|
||||
std::string requested_license_id_;
|
||||
// The current in use license_id.
|
||||
std::string license_id_;
|
||||
// The group id of a Group license. Empty if the license is not a Group
|
||||
// license (multi content license is not a group license). Used in processECM
|
||||
// to select group keys that can be decrypted by the license.
|
||||
std::string license_group_id_;
|
||||
// Fingerprinting events sent in processing last ECM/EMM. Used to avoid
|
||||
// sending a same event again.
|
||||
std::set<CasData> last_fingerprinting_events_;
|
||||
// Service blocking events sent in processing last ECM/EMM. Used to avoid
|
||||
// sending a same event again.
|
||||
std::set<CasData> last_service_blocking_events_;
|
||||
// Indicates if |entitlement_period_index_| below is valid or not.
|
||||
bool is_entitlement_rotation_enabled_ = false;
|
||||
// The entitlement period index in the last received ECM.
|
||||
uint32_t entitlement_period_index_;
|
||||
|
||||
// |next_*| used to handle entitlement key rotation. They will be moved to
|
||||
// normal ones once the license switch completed.
|
||||
std::shared_ptr<CryptoSession> next_crypto_session_;
|
||||
std::unique_ptr<CasLicense> next_cas_license_;
|
||||
std::unique_ptr<CasMediaId> next_media_id_;
|
||||
}; // namespace wvcas
|
||||
|
||||
} // namespace wvcas
|
||||
|
||||
#endif // WIDEVINE_CAS_API_H
|
||||
113
plugin/include/widevine_cas_session.h
Normal file
113
plugin/include/widevine_cas_session.h
Normal file
@@ -0,0 +1,113 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#ifndef WIDEVINE_CAS_SESSION_H
|
||||
#define WIDEVINE_CAS_SESSION_H
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
#include "cas_types.h"
|
||||
#include "crypto_session.h"
|
||||
#include "ecm_parser.h"
|
||||
#include "media_cas.pb.h"
|
||||
|
||||
namespace wvcas {
|
||||
|
||||
class WidevineCasSession;
|
||||
typedef std::shared_ptr<WidevineCasSession> 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<CryptoSession> crypto_session,
|
||||
CasEventListener* event_listener,
|
||||
WvCasSessionId* session_id);
|
||||
|
||||
CasStatus resetCryptoSession(std::shared_ptr<CryptoSession> crypto_session);
|
||||
|
||||
// Process an ecm and extract the key slot data. Extracted data will be used
|
||||
// to update |current_ecm_| and |entitlement_key_id_| and |keys_|.
|
||||
// |parental_control_age| (if non-zero) must be greater or equal to the
|
||||
// age_restriction field specified in |ecm|. Otherwise, ECM will not be
|
||||
// processed and error will be returned.
|
||||
// |license_group_id| if non empty, processEcm will decrypt content keys that
|
||||
// are specified by |license_group_id|.
|
||||
virtual CasStatus processEcm(const CasEcm& ecm, uint8_t parental_control_age,
|
||||
const std::string& license_group_id);
|
||||
|
||||
// Returns the security level retrieved from OEMCrypto.
|
||||
const char* securityLevel();
|
||||
|
||||
// Returns current ecm age restriction value.
|
||||
uint8_t GetEcmAgeRestriction() const { return ecm_age_restriction_; }
|
||||
// Returns the entitlement period index specified in the last received ECM.
|
||||
uint32_t GetEntitlementPeriodIndex() const {
|
||||
return entitlement_period_index_;
|
||||
}
|
||||
// Returns the entitlement rotation window left value specified in the last
|
||||
// received ECM.
|
||||
uint32_t GetEntitlementRotationWindowLeft() const {
|
||||
return entitlement_rotation_window_left_;
|
||||
}
|
||||
|
||||
WidevineCasSession(const WidevineCasSession&) = delete;
|
||||
WidevineCasSession& operator=(const WidevineCasSession&) = delete;
|
||||
|
||||
private:
|
||||
// Creates an EcmParser.
|
||||
virtual std::unique_ptr<EcmParser> getEcmParser(const CasEcm& ecm) const;
|
||||
|
||||
CasKeySlotData keys_; // Odd and even key slots.
|
||||
std::string entitlement_key_id_;
|
||||
std::mutex crypto_lock_;
|
||||
CasEcm current_ecm_;
|
||||
uint8_t ecm_age_restriction_ = 0;
|
||||
std::shared_ptr<CryptoSession> crypto_session_;
|
||||
// Id of the entitled key session in OEMCrypto associated with this session.
|
||||
uint32_t key_session_id_;
|
||||
// This is the session id returned to the app. It is actually the OEM key
|
||||
// token.
|
||||
std::vector<uint8_t> external_key_session_id_;
|
||||
CasEventListener* event_listener_ = nullptr;
|
||||
// Fingerprinting events sent in processing last ECM/EMM. Used to avoid
|
||||
// sending a same event again.
|
||||
std::vector<uint8_t> last_fingerprinting_message_;
|
||||
// Service blocking events sent in processing last ECM/EMM. Used to avoid
|
||||
// sending a same event again.
|
||||
std::vector<uint8_t> last_service_blocking_message_;
|
||||
// The entitlement period index in the last received ECM.
|
||||
uint32_t entitlement_period_index_;
|
||||
// The entitlement rotation window left in the last received ECM.
|
||||
uint32_t entitlement_rotation_window_left_;
|
||||
};
|
||||
|
||||
} // namespace wvcas
|
||||
|
||||
#endif // WIDEVINE_CAS_SESSION_H
|
||||
54
plugin/include/widevine_cas_session_map.h
Normal file
54
plugin/include/widevine_cas_session_map.h
Normal file
@@ -0,0 +1,54 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#ifndef WIDEVINE_CAS_SESSION_MAP_H
|
||||
#define WIDEVINE_CAS_SESSION_MAP_H
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "cas_types.h"
|
||||
#include "widevine_cas_session.h"
|
||||
|
||||
namespace wvcas {
|
||||
|
||||
typedef std::map<WvCasSessionId, CasSessionPtr> CasSessionMap;
|
||||
|
||||
// WidevineCasSessionMap is a singleton. It used as a shared resource used by
|
||||
// both cas and descrambler plugins. Cas sessions are created by the cas plugin,
|
||||
// and can be *discovered* by descrambler plugins.
|
||||
class WidevineCasSessionMap {
|
||||
public:
|
||||
virtual ~WidevineCasSessionMap() {}
|
||||
|
||||
// Adds a new Widevine cas session to the map.
|
||||
// Returns true if the session is successfully added to the map, false
|
||||
// otherwise.
|
||||
bool AddSession(const WvCasSessionId& cas_session_id, CasSessionPtr session);
|
||||
// Obtain a shared pointer to a cas session. If the session does not exist in
|
||||
// the map, the returned pointer == nullptr.
|
||||
CasSessionPtr GetSession(const WvCasSessionId& cas_session_id) const;
|
||||
// Remove an entry in the map.
|
||||
void RemoveSession(const WvCasSessionId& cas_session_id);
|
||||
// Retrieves all the session ids.
|
||||
std::vector<CasSessionPtr> GetAllSessions() const;
|
||||
|
||||
// Returns a reference to the map.
|
||||
static WidevineCasSessionMap& instance();
|
||||
|
||||
WidevineCasSessionMap(const WidevineCasSessionMap&) = delete;
|
||||
WidevineCasSessionMap& operator=(const WidevineCasSessionMap&) = delete;
|
||||
|
||||
private:
|
||||
WidevineCasSessionMap() {}
|
||||
CasSessionMap map_;
|
||||
mutable std::mutex lock_;
|
||||
};
|
||||
|
||||
} // namespace wvcas
|
||||
|
||||
#endif // WIDEVINE_CAS_SESSION_MAP_H
|
||||
59
plugin/include/widevine_media_cas.h
Normal file
59
plugin/include/widevine_media_cas.h
Normal file
@@ -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<CasPluginDescriptor>* 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
|
||||
154
plugin/include/widevine_media_cas_plugin.h
Normal file
154
plugin/include/widevine_media_cas_plugin.h
Normal file
@@ -0,0 +1,154 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#ifndef WIDEVINE_MEDIA_CAS_PLUGIN
|
||||
#define WIDEVINE_MEDIA_CAS_PLUGIN
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "media/cas/CasAPI.h"
|
||||
#include "widevine_cas_api.h"
|
||||
|
||||
using android::CasPlugin;
|
||||
using android::CasPluginCallback;
|
||||
using android::CasPluginCallbackExt;
|
||||
using android::CasPluginStatusCallback;
|
||||
using android::CasSessionId;
|
||||
using android::status_t;
|
||||
using android::String8;
|
||||
|
||||
namespace wvcas {
|
||||
|
||||
// WidevineCasPlugin inplements the android::CasPlugin interface.
|
||||
class WidevineCasPlugin : public CasPlugin, public CasEventListener {
|
||||
public:
|
||||
// Constructs a new WidevineCasPlugin. The |callback| is a method to transmit
|
||||
// events to the user application. To receive these events, a MediaCas
|
||||
// application must implement The MediaCas.EventListener java interface and
|
||||
// call MediaCas.setEventListener. More information can be found in the
|
||||
// MediaCas platform api documentation.
|
||||
WidevineCasPlugin(void* appData, CasPluginCallback callback);
|
||||
WidevineCasPlugin(void* appData, CasPluginCallbackExt callback);
|
||||
~WidevineCasPlugin() override;
|
||||
|
||||
status_t initialize();
|
||||
|
||||
// Provide a callback to report plugin status.
|
||||
status_t setStatusCallback(CasPluginStatusCallback callback) override;
|
||||
|
||||
// Provide the CA private data from a CA_descriptor in the conditional
|
||||
// access table.
|
||||
status_t setPrivateData(const CasData& privateData) override;
|
||||
|
||||
// Open a session for descrambling a program, or one or more elementary
|
||||
// streams.
|
||||
status_t openSession(CasSessionId* sessionId) override;
|
||||
|
||||
// Open a session with intend and mode for descrambling a program, or one
|
||||
// or more elementary streams.
|
||||
status_t openSession(uint32_t intent, uint32_t mode,
|
||||
CasSessionId* sessionId) override;
|
||||
|
||||
// Close a previously opened session.
|
||||
status_t closeSession(const CasSessionId& sessionId) override;
|
||||
|
||||
// Provide the CA private data from a CA_descriptor in the program map
|
||||
// table.
|
||||
status_t setSessionPrivateData(const CasSessionId& sessionId,
|
||||
const CasData& privateData) override;
|
||||
|
||||
// Process an ECM from the ECM stream for this session’s elementary stream.
|
||||
status_t processEcm(const CasSessionId& sessionId,
|
||||
const CasEcm& ecm) override;
|
||||
|
||||
// Process an in-band EMM from the EMM stream.
|
||||
status_t processEmm(const CasEmm& emm) override;
|
||||
|
||||
// Deliver an event to the CasPlugin. The format of the event is specific
|
||||
// to the CA scheme and is opaque to the framework.
|
||||
status_t sendEvent(int32_t event, int32_t arg,
|
||||
const CasData& eventData) override;
|
||||
|
||||
// Deliver an session event to the CasPlugin. The format of the event is
|
||||
// specific to the CA scheme and is opaque to the framework.
|
||||
status_t sendSessionEvent(const CasSessionId& sessionId, int32_t event,
|
||||
int32_t arg, const CasData& eventData) override;
|
||||
|
||||
// Native implementation of the MediaCas Java API provision method.
|
||||
status_t provision(const String8& provisionString) override;
|
||||
|
||||
// Native implementation of the MediaCas Java API refreshEntitlements method.
|
||||
status_t refreshEntitlements(int32_t refreshType,
|
||||
const CasData& refreshData) override;
|
||||
|
||||
WidevineCasPlugin(const WidevineCasPlugin&) = delete;
|
||||
WidevineCasPlugin& operator=(const WidevineCasPlugin&) = delete;
|
||||
|
||||
protected:
|
||||
// For unit test only.
|
||||
virtual void SetWidevineCasApi(
|
||||
std::unique_ptr<WidevineCas> widevine_cas_api) {
|
||||
widevine_cas_api_ = std::move(widevine_cas_api);
|
||||
}
|
||||
WidevineCasPlugin(){};
|
||||
|
||||
private:
|
||||
// |sessionId| is nullptr if the event is not a session event.
|
||||
CasStatus processEvent(int32_t event, int32_t arg, const CasData& eventData,
|
||||
const CasSessionId* sessionId);
|
||||
CasStatus HandleIndividualizationResponse(const CasData& response);
|
||||
CasStatus HandleEntitlementResponse(const CasData& response);
|
||||
status_t requestLicense(const std::string& init_data);
|
||||
CasStatus HandleEntitlementRenewalResponse(const CasData& response);
|
||||
CasStatus HandleUniqueIDQuery();
|
||||
CasStatus HandleSetParentalControlAge(const CasData& data);
|
||||
CasStatus HandleLicenseRemoval(const CasData& license_id);
|
||||
CasStatus HandleAssignLicenseID(const CasData& license_id);
|
||||
CasStatus HandlePluginVersionQuery();
|
||||
CasStatus HandleEntitlementPeriodUpdateResponse(const CasData& response);
|
||||
|
||||
// Returns true if the device has been provisioned with a device certificate.
|
||||
bool is_provisioned() const;
|
||||
// Event listener implementation
|
||||
void OnSessionRenewalNeeded() override;
|
||||
void OnSessionKeysChange(const KeyStatusMap& keys_status,
|
||||
bool has_new_usable_key) override;
|
||||
// |new_expiry_time_seconds| of 0 means "never expires".
|
||||
void OnExpirationUpdate(int64_t new_expiry_time_seconds) override;
|
||||
void OnNewRenewalServerUrl(const std::string& renewal_server_url) override;
|
||||
void OnLicenseExpiration() override;
|
||||
void OnAgeRestrictionUpdated(const WvCasSessionId& sessionId,
|
||||
uint8_t ecm_age_restriction) override;
|
||||
void OnSessionFingerprintingUpdated(const WvCasSessionId& sessionId,
|
||||
const CasData& fingerprinting) override;
|
||||
void OnSessionServiceBlockingUpdated(
|
||||
const WvCasSessionId& sessionId,
|
||||
const CasData& service_blocking) override;
|
||||
void OnFingerprintingUpdated(const CasData& fingerprinting) override;
|
||||
void OnServiceBlockingUpdated(const CasData& service_blocking) override;
|
||||
void OnEntitlementPeriodUpdateNeeded(
|
||||
const std::string& signed_license_request) override;
|
||||
|
||||
// Choose to use |callback_| or |callback_ext_| to send back information.
|
||||
// |sessionId| is ignored if |callback_ext_| is null,
|
||||
virtual void CallBack(void* appData, int32_t event, int32_t arg,
|
||||
uint8_t* data, size_t size,
|
||||
const CasSessionId* sessionId) const;
|
||||
|
||||
void* app_data_;
|
||||
CasPluginCallback callback_;
|
||||
CasPluginCallbackExt callback_ext_;
|
||||
// If provision is called with a non-empty string,
|
||||
// it is taken as a PSSH that overrides data in CA descripor.
|
||||
// Otherwise, first CA descriptor available to the plugin
|
||||
// is used to build a PSSH, and others are discarded.
|
||||
bool is_emm_request_sent_ = false;
|
||||
// This is always the serialized PSSH data.
|
||||
std::string init_data_;
|
||||
std::unique_ptr<WidevineCas> widevine_cas_api_;
|
||||
};
|
||||
|
||||
} // namespace wvcas
|
||||
|
||||
#endif // WIDEVINE_MEDIA_CAS_PLUGIN
|
||||
1138
plugin/src/cas_license.cpp
Normal file
1138
plugin/src/cas_license.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1083
plugin/src/crypto_session.cpp
Normal file
1083
plugin/src/crypto_session.cpp
Normal file
File diff suppressed because it is too large
Load Diff
84
plugin/src/ecm_parser.cpp
Normal file
84
plugin/src/ecm_parser.cpp
Normal file
@@ -0,0 +1,84 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#include "ecm_parser.h"
|
||||
|
||||
#include "ecm_parser_v2.h"
|
||||
#include "ecm_parser_v3.h"
|
||||
#include "log.h"
|
||||
|
||||
namespace wvcas {
|
||||
|
||||
namespace {
|
||||
// 2 bytes cas id, 1 byte version.
|
||||
constexpr int kEcmHeaderSize = 3;
|
||||
constexpr int kCasIdIndex = 0;
|
||||
constexpr int kVersionIndex = 2;
|
||||
|
||||
// Legacy Widevine CAS ID
|
||||
constexpr uint16_t kWidevineCasId = 0x4AD4;
|
||||
// New Widevine CAS IDs 0x56C0 to 0x56C9 (all inclusive).
|
||||
constexpr uint16_t kWidevineNewCasIdLowerBound = 0x56C0;
|
||||
constexpr uint16_t kWidevineNewCasIdUpperBound = 0x56C9;
|
||||
|
||||
// Two values of the table_id field (0x80 and 0x81) are reserved for
|
||||
// transmission of ECM data. A change of these two table_id values signals
|
||||
// that a change of ECM contents has occurred.
|
||||
constexpr uint8_t kSectionHeader1 = 0x80;
|
||||
constexpr uint8_t kSectionHeader2 = 0x81;
|
||||
constexpr size_t kSectionHeaderSize = 3;
|
||||
constexpr size_t kSectionHeaderWithPointerSize = 4;
|
||||
constexpr uint8_t kPointerFieldZero = 0x00;
|
||||
|
||||
// Returns the possible starting index of ECM. It assumes the pointer field will
|
||||
// always set to 0, if present.
|
||||
int find_ecm_start_index(const CasEcm& cas_ecm) {
|
||||
if (cas_ecm.empty()) {
|
||||
return 0;
|
||||
}
|
||||
// Case 1: Pointer field (always set to 0); section header; ECM.
|
||||
if (cas_ecm[0] == kPointerFieldZero) {
|
||||
return kSectionHeaderWithPointerSize;
|
||||
}
|
||||
// Case 2: Section header (3 bytes), ECM.
|
||||
if (cas_ecm[0] == kSectionHeader1 || cas_ecm[0] == kSectionHeader2) {
|
||||
return kSectionHeaderSize;
|
||||
}
|
||||
// Case 3: ECM.
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<EcmParser> EcmParser::Create(const CasEcm& cas_ecm) {
|
||||
// Detect and strip optional section header.
|
||||
const int offset = find_ecm_start_index(cas_ecm);
|
||||
if (offset < 0 ||
|
||||
(offset + kEcmHeaderSize > static_cast<int>(cas_ecm.size()))) {
|
||||
LOGE("Unable to find start of ECM");
|
||||
return nullptr;
|
||||
}
|
||||
const CasEcm ecm(cas_ecm.begin() + offset, cas_ecm.end());
|
||||
// Confirm ecm data starts with valid Widevine CAS ID.
|
||||
uint16_t cas_id_val = (ecm[kCasIdIndex] << 8) | ecm[kCasIdIndex + 1];
|
||||
if (cas_id_val != kWidevineCasId &&
|
||||
(cas_id_val < kWidevineNewCasIdLowerBound ||
|
||||
cas_id_val > kWidevineNewCasIdUpperBound)) {
|
||||
LOGE("Supported Widevine CAS IDs not found at the start of ECM. Found: %u",
|
||||
cas_id_val);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (ecm[kVersionIndex] <= 2) {
|
||||
std::unique_ptr<EcmParserV2> parser;
|
||||
if (!EcmParserV2::create(ecm, &parser)) {
|
||||
return nullptr;
|
||||
}
|
||||
return parser;
|
||||
} else {
|
||||
return EcmParserV3::Create(ecm);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace wvcas
|
||||
181
plugin/src/ecm_parser_v2.cpp
Normal file
181
plugin/src/ecm_parser_v2.cpp
Normal file
@@ -0,0 +1,181 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#include "ecm_parser_v2.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace wvcas {
|
||||
|
||||
namespace {
|
||||
// ECM constants
|
||||
constexpr uint8_t kCryptoModeFlags = (0x3 << 1); // Mode bits 1..2
|
||||
constexpr uint8_t kCryptoModeFlagsV2 = (0xF << 1); // Mode bits 1..4
|
||||
constexpr uint8_t kRotationFlag = (0x1 << 0); // Mode bit 0
|
||||
constexpr uint8_t kContentIVSizeFlag = (0x1 << 6);
|
||||
constexpr uint8_t kAgeRestrictionMask = (0x1F << 1);
|
||||
|
||||
constexpr size_t kEntitlementKeyIDSizeBytes = 16;
|
||||
constexpr size_t kContentKeyIDSizeBytes = 16;
|
||||
constexpr size_t kContentKeyDataSizeBytes = 16;
|
||||
constexpr size_t kWrappedKeyIVSizeBytes = 16;
|
||||
// Size is either 8 or 16 bytes, depending on ContentIVSize flag.
|
||||
constexpr size_t kContentKeyMaxIVSizeBytes = 16;
|
||||
|
||||
constexpr size_t kMaxTsPayloadSizeBytes = 184;
|
||||
} // namespace
|
||||
|
||||
#pragma pack(push, 1) // No padding in ecm struct definition.
|
||||
struct EcmKeyData {
|
||||
const uint8_t entitlement_key_id[kEntitlementKeyIDSizeBytes];
|
||||
const uint8_t content_key_id[kContentKeyIDSizeBytes];
|
||||
const uint8_t control_word[kContentKeyDataSizeBytes];
|
||||
const uint8_t control_word_iv[kWrappedKeyIVSizeBytes];
|
||||
// Actual size can be either 8 or 16 bytes.
|
||||
const uint8_t content_iv[kContentKeyMaxIVSizeBytes];
|
||||
};
|
||||
|
||||
struct EcmDescriptor {
|
||||
const uint16_t ca_id;
|
||||
const uint8_t version;
|
||||
const uint8_t flags_cipher_rotation;
|
||||
const uint8_t flags_iv_age;
|
||||
};
|
||||
|
||||
static_assert((sizeof(EcmDescriptor) + 2 * sizeof(EcmKeyData)) <=
|
||||
kMaxTsPayloadSizeBytes,
|
||||
"Maximum possible ecm size is larger than a ts payload");
|
||||
#pragma pack(pop) // Revert padding value to previous.
|
||||
|
||||
EcmParserV2::EcmParserV2(const CasEcm& ecm) : ecm_(ecm) {}
|
||||
|
||||
size_t EcmParserV2::key_data_size() const {
|
||||
return sizeof(EcmKeyData) + content_iv_size() - kContentKeyMaxIVSizeBytes;
|
||||
}
|
||||
|
||||
bool EcmParserV2::is_valid_size() const {
|
||||
size_t expected_size =
|
||||
sizeof(EcmDescriptor) + key_data_size() * (rotation_enabled() ? 2 : 1);
|
||||
// Parser always receives entire ts payload of 184 bytes.
|
||||
return ecm_.size() >= expected_size;
|
||||
}
|
||||
|
||||
const EcmKeyData* EcmParserV2::key_slot_data(KeySlotId id) const {
|
||||
// ECM descriptor is followed by either one or two ECM key data.
|
||||
size_t key_data_offset = sizeof(EcmDescriptor);
|
||||
if (rotation_enabled()) {
|
||||
if (id == KeySlotId::kOddKeySlot) {
|
||||
key_data_offset += key_data_size();
|
||||
} else if (id != KeySlotId::kEvenKeySlot) {
|
||||
return nullptr;
|
||||
}
|
||||
} else {
|
||||
// No rotation enabled.
|
||||
if (id != KeySlotId::kEvenKeySlot) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
return reinterpret_cast<const EcmKeyData*>(&ecm_[key_data_offset]);
|
||||
}
|
||||
|
||||
bool EcmParserV2::create(const CasEcm& cas_ecm,
|
||||
std::unique_ptr<EcmParserV2>* parser) {
|
||||
if (parser == nullptr) {
|
||||
return false;
|
||||
}
|
||||
// Using 'new' to access a non-public constructor.
|
||||
auto new_parser = std::unique_ptr<EcmParserV2>(new EcmParserV2(cas_ecm));
|
||||
if (!new_parser->is_valid_size()) {
|
||||
return false;
|
||||
}
|
||||
*parser = std::move(new_parser);
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8_t EcmParserV2::version() const {
|
||||
const EcmDescriptor* ecm = reinterpret_cast<const EcmDescriptor*>(&ecm_[0]);
|
||||
return ecm->version;
|
||||
}
|
||||
|
||||
CryptoMode EcmParserV2::crypto_mode() const {
|
||||
const EcmDescriptor* ecm = reinterpret_cast<const EcmDescriptor*>(&ecm_[0]);
|
||||
if (version() == 1) {
|
||||
return static_cast<CryptoMode>(
|
||||
(ecm->flags_cipher_rotation & kCryptoModeFlags) >> 1);
|
||||
}
|
||||
return static_cast<CryptoMode>(
|
||||
(ecm->flags_cipher_rotation & kCryptoModeFlagsV2) >> 1);
|
||||
}
|
||||
|
||||
bool EcmParserV2::rotation_enabled() const {
|
||||
const EcmDescriptor* ecm = reinterpret_cast<const EcmDescriptor*>(&ecm_[0]);
|
||||
return (ecm->flags_cipher_rotation & kRotationFlag);
|
||||
}
|
||||
|
||||
size_t EcmParserV2::content_iv_size() const {
|
||||
const EcmDescriptor* ecm = reinterpret_cast<const EcmDescriptor*>(&ecm_[0]);
|
||||
// Content key IV size is 8 bytes if flag is zero, and 16 if flag is set.
|
||||
return (ecm->flags_iv_age & kContentIVSizeFlag) ? 16 : 8;
|
||||
}
|
||||
|
||||
uint8_t EcmParserV2::age_restriction() const {
|
||||
if (version() == 1) {
|
||||
return 0;
|
||||
}
|
||||
const EcmDescriptor* ecm = reinterpret_cast<const EcmDescriptor*>(&ecm_[0]);
|
||||
return (ecm->flags_iv_age & kAgeRestrictionMask) >> 1;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> EcmParserV2::entitlement_key_id(KeySlotId id) const {
|
||||
std::vector<uint8_t> ekey_id;
|
||||
const EcmKeyData* key_data = key_slot_data(id);
|
||||
if (key_data) {
|
||||
ekey_id.assign(
|
||||
&key_data->entitlement_key_id[0],
|
||||
&key_data->entitlement_key_id[0] + kEntitlementKeyIDSizeBytes);
|
||||
}
|
||||
return ekey_id;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> EcmParserV2::content_key_id(KeySlotId id) const {
|
||||
std::vector<uint8_t> ckey_id;
|
||||
const EcmKeyData* key_data = key_slot_data(id);
|
||||
if (key_data) {
|
||||
ckey_id.assign(&key_data->content_key_id[0],
|
||||
&key_data->content_key_id[0] + kContentKeyIDSizeBytes);
|
||||
}
|
||||
return ckey_id;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> EcmParserV2::wrapped_key_data(KeySlotId id) const {
|
||||
std::vector<uint8_t> ckey_data;
|
||||
const EcmKeyData* key_data = key_slot_data(id);
|
||||
if (key_data) {
|
||||
ckey_data.assign(&key_data->control_word[0],
|
||||
&key_data->control_word[0] + kContentKeyDataSizeBytes);
|
||||
}
|
||||
return ckey_data;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> EcmParserV2::wrapped_key_iv(KeySlotId id) const {
|
||||
std::vector<uint8_t> iv;
|
||||
const EcmKeyData* key_data = key_slot_data(id);
|
||||
if (key_data) {
|
||||
iv.assign(&key_data->control_word_iv[0],
|
||||
&key_data->control_word_iv[0] + kWrappedKeyIVSizeBytes);
|
||||
}
|
||||
return iv;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> EcmParserV2::content_iv(KeySlotId id) const {
|
||||
std::vector<uint8_t> 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
|
||||
223
plugin/src/ecm_parser_v3.cpp
Normal file
223
plugin/src/ecm_parser_v3.cpp
Normal file
@@ -0,0 +1,223 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#include "ecm_parser_v3.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "log.h"
|
||||
|
||||
namespace wvcas {
|
||||
|
||||
namespace {
|
||||
using video_widevine::EcmKeyData;
|
||||
using video_widevine::EcmMetaData;
|
||||
using video_widevine::EcmPayload;
|
||||
using video_widevine::SignedEcmPayload;
|
||||
|
||||
constexpr int kEcmHeaderSize = 3;
|
||||
constexpr uint8_t kEcmVersion = 3;
|
||||
// 16 bytes fixed content key ids
|
||||
constexpr uint8_t kEvenContentKeyId[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00};
|
||||
constexpr uint8_t kOddContentKeyId[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x01};
|
||||
|
||||
CryptoMode ConvertProtoCipherMode(EcmMetaData::CipherMode cipher_mode) {
|
||||
switch (cipher_mode) {
|
||||
case EcmMetaData::AES_CBC:
|
||||
return CryptoMode::kAesCBC;
|
||||
case EcmMetaData::AES_CTR:
|
||||
return CryptoMode::kAesCTR;
|
||||
case EcmMetaData::DVB_CSA2:
|
||||
return CryptoMode::kDvbCsa2;
|
||||
case EcmMetaData::DVB_CSA3:
|
||||
return CryptoMode::kDvbCsa3;
|
||||
case EcmMetaData::AES_OFB:
|
||||
return CryptoMode::kAesOFB;
|
||||
case EcmMetaData::AES_SCTE52:
|
||||
return CryptoMode::kAesSCTE;
|
||||
case EcmMetaData::AES_ECB:
|
||||
return CryptoMode::kAesECB;
|
||||
case EcmMetaData::UNSPECIFIED:
|
||||
default:
|
||||
return CryptoMode::kInvalid;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
EcmParserV3::EcmParserV3(SignedEcmPayload signed_ecm_payload,
|
||||
EcmPayload ecm_payload)
|
||||
: signed_ecm_payload_(std::move(signed_ecm_payload)),
|
||||
ecm_payload_(std::move(ecm_payload)) {
|
||||
even_key_data_ = ecm_payload_.even_key_data();
|
||||
odd_key_data_ = ecm_payload_.odd_key_data();
|
||||
}
|
||||
|
||||
std::unique_ptr<EcmParserV3> EcmParserV3::Create(const CasEcm& cas_ecm) {
|
||||
if (cas_ecm.size() <= kEcmHeaderSize) {
|
||||
LOGE("ECM is too short. Size: %lu", cas_ecm.size());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SignedEcmPayload signed_ecm_payload;
|
||||
// The 3 byte ecm header is ignored.
|
||||
if (!signed_ecm_payload.ParseFromArray(cas_ecm.data() + kEcmHeaderSize,
|
||||
cas_ecm.size() - kEcmHeaderSize)) {
|
||||
LOGE("Unable to parse signed ecm payload");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
EcmPayload ecm_payload;
|
||||
if (!ecm_payload.ParseFromString(signed_ecm_payload.serialized_payload())) {
|
||||
LOGE("Unable to parse ecm payload");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Using 'new' to access a non-public constructor.
|
||||
return std::unique_ptr<EcmParserV3>(
|
||||
new EcmParserV3(signed_ecm_payload, ecm_payload));
|
||||
}
|
||||
|
||||
uint8_t EcmParserV3::version() const { return kEcmVersion; }
|
||||
|
||||
CryptoMode EcmParserV3::crypto_mode() const {
|
||||
return ConvertProtoCipherMode(ecm_payload_.meta_data().cipher_mode());
|
||||
}
|
||||
|
||||
bool EcmParserV3::rotation_enabled() const {
|
||||
return ecm_payload_.has_odd_key_data();
|
||||
}
|
||||
|
||||
size_t EcmParserV3::content_iv_size() const {
|
||||
return ecm_payload_.even_key_data().content_iv().size();
|
||||
}
|
||||
|
||||
uint8_t EcmParserV3::age_restriction() const {
|
||||
return static_cast<uint8_t>(ecm_payload_.meta_data().age_restriction());
|
||||
}
|
||||
|
||||
std::vector<uint8_t> EcmParserV3::entitlement_key_id(KeySlotId id) const {
|
||||
// Use the even entitlement_key_id if the odd one is empty (omitted).
|
||||
const EcmKeyData& key_data =
|
||||
id == KeySlotId::kOddKeySlot &&
|
||||
!odd_key_data_.entitlement_key_id().empty()
|
||||
? odd_key_data_
|
||||
: even_key_data_;
|
||||
|
||||
return {key_data.entitlement_key_id().begin(),
|
||||
key_data.entitlement_key_id().end()};
|
||||
}
|
||||
|
||||
std::vector<uint8_t> EcmParserV3::content_key_id(KeySlotId id) const {
|
||||
if (id == KeySlotId::kEvenKeySlot && ecm_payload_.has_even_key_data()) {
|
||||
return {kEvenContentKeyId, kEvenContentKeyId + sizeof(kEvenContentKeyId)};
|
||||
} else if (id == KeySlotId::kOddKeySlot && ecm_payload_.has_odd_key_data()) {
|
||||
return {kOddContentKeyId, kOddContentKeyId + sizeof(kOddContentKeyId)};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<uint8_t> EcmParserV3::wrapped_key_data(KeySlotId id) const {
|
||||
const EcmKeyData& key_data =
|
||||
id == KeySlotId::kOddKeySlot ? odd_key_data_ : even_key_data_;
|
||||
|
||||
return {key_data.wrapped_key_data().begin(),
|
||||
key_data.wrapped_key_data().end()};
|
||||
}
|
||||
|
||||
std::vector<uint8_t> EcmParserV3::wrapped_key_iv(KeySlotId id) const {
|
||||
// Use the even wrapped_key_iv if the odd one is empty (omitted).
|
||||
const EcmKeyData* key_data =
|
||||
id == KeySlotId::kOddKeySlot && !odd_key_data_.wrapped_key_iv().empty()
|
||||
? &odd_key_data_
|
||||
: &even_key_data_;
|
||||
// Wrapped key IV may be omitted for group keys.
|
||||
if (key_data->wrapped_key_iv().empty()) {
|
||||
key_data = id == KeySlotId::kOddKeySlot &&
|
||||
!ecm_payload_.odd_key_data().wrapped_key_iv().empty()
|
||||
? &ecm_payload_.odd_key_data()
|
||||
: &ecm_payload_.even_key_data();
|
||||
}
|
||||
|
||||
return {key_data->wrapped_key_iv().begin(), key_data->wrapped_key_iv().end()};
|
||||
}
|
||||
|
||||
std::vector<uint8_t> EcmParserV3::content_iv(KeySlotId id) const {
|
||||
// Use the even content_iv if the odd one is empty (omitted).
|
||||
const EcmKeyData* key_data =
|
||||
id == KeySlotId::kOddKeySlot && !odd_key_data_.content_iv().empty()
|
||||
? &odd_key_data_
|
||||
: &even_key_data_;
|
||||
// Content IV may be omitted for group keys.
|
||||
if (key_data->content_iv().empty()) {
|
||||
key_data = id == KeySlotId::kOddKeySlot &&
|
||||
!ecm_payload_.odd_key_data().content_iv().empty()
|
||||
? &ecm_payload_.odd_key_data()
|
||||
: &ecm_payload_.even_key_data();
|
||||
}
|
||||
|
||||
return {key_data->content_iv().begin(), key_data->content_iv().end()};
|
||||
}
|
||||
|
||||
bool EcmParserV3::has_fingerprinting() const {
|
||||
return ecm_payload_.has_fingerprinting();
|
||||
}
|
||||
|
||||
video_widevine::Fingerprinting EcmParserV3::fingerprinting() const {
|
||||
return ecm_payload_.fingerprinting();
|
||||
}
|
||||
|
||||
bool EcmParserV3::has_service_blocking() const {
|
||||
return ecm_payload_.has_service_blocking();
|
||||
}
|
||||
|
||||
video_widevine::ServiceBlocking EcmParserV3::service_blocking() const {
|
||||
return ecm_payload_.service_blocking();
|
||||
}
|
||||
|
||||
std::string EcmParserV3::ecm_serialized_payload() const {
|
||||
return signed_ecm_payload_.serialized_payload();
|
||||
}
|
||||
std::string EcmParserV3::signature() const {
|
||||
return signed_ecm_payload_.signature();
|
||||
}
|
||||
|
||||
bool EcmParserV3::set_group_id(const std::string& group_id) {
|
||||
if (group_id.empty()) {
|
||||
even_key_data_ = ecm_payload_.even_key_data();
|
||||
odd_key_data_ = ecm_payload_.odd_key_data();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool found = false;
|
||||
for (int i = 0; i < ecm_payload_.group_key_data_size(); ++i) {
|
||||
const video_widevine::EcmGroupKeyData& group_key_data =
|
||||
ecm_payload_.group_key_data(i);
|
||||
if (group_key_data.group_id() == group_id) {
|
||||
found = true;
|
||||
even_key_data_ = group_key_data.even_key_data();
|
||||
odd_key_data_ = group_key_data.odd_key_data();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
bool EcmParserV3::is_entitlement_rotation_enabled() const {
|
||||
return ecm_payload_.meta_data().has_entitlement_period_index();
|
||||
}
|
||||
|
||||
uint32_t EcmParserV3::entitlement_period_index() const {
|
||||
return ecm_payload_.meta_data().entitlement_period_index();
|
||||
}
|
||||
|
||||
uint32_t EcmParserV3::entitlement_rotation_window_left() const {
|
||||
return ecm_payload_.meta_data().entitlement_rotation_window_left();
|
||||
}
|
||||
|
||||
} // namespace wvcas
|
||||
75
plugin/src/emm_parser.cpp
Normal file
75
plugin/src/emm_parser.cpp
Normal file
@@ -0,0 +1,75 @@
|
||||
// Copyright 2020 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#include "emm_parser.h"
|
||||
|
||||
#include "log.h"
|
||||
|
||||
namespace wvcas {
|
||||
namespace {
|
||||
// ETSI ETR 289 specifies table ids 0x82 to 0x8F are for CA System private
|
||||
// usage, which are typically used by EMM, with one table id for each EMM type.
|
||||
constexpr uint16_t kSectionHeader = 0x82;
|
||||
constexpr size_t kSectionHeaderSize = 3;
|
||||
constexpr size_t kSectionHeaderWithPointerSize = 4;
|
||||
constexpr uint8_t kPointerFieldZero = 0x00;
|
||||
|
||||
// Returns the possible starting index of EMM. -1 will be returned in case of
|
||||
// error. It assumes the pointer field will always set to 0, if present.
|
||||
int find_emm_start_index(const CasEmm& cas_emm) {
|
||||
if (cas_emm.empty()) {
|
||||
return -1;
|
||||
}
|
||||
// Case 1: Pointer field (always set to 0); section header; EMM.
|
||||
if (cas_emm[0] == kPointerFieldZero) {
|
||||
return kSectionHeaderWithPointerSize < cas_emm.size()
|
||||
? kSectionHeaderWithPointerSize
|
||||
: -1;
|
||||
}
|
||||
// Case 2: Section header (3 bytes), EMM.
|
||||
if (cas_emm[0] == kSectionHeader) {
|
||||
return kSectionHeaderSize < cas_emm.size() ? kSectionHeaderSize : -1;
|
||||
}
|
||||
// Case 3: EMM.
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<const EmmParser> EmmParser::Create(const CasEmm& emm) {
|
||||
auto parser = std::unique_ptr<EmmParser>(new EmmParser());
|
||||
if (!parser->Parse(find_emm_start_index(emm), emm)) {
|
||||
return nullptr;
|
||||
}
|
||||
return parser;
|
||||
}
|
||||
|
||||
bool EmmParser::Parse(int start_index, const CasEmm& emm) {
|
||||
if (start_index < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
video_widevine::SignedEmmPayload signed_emm;
|
||||
if (!signed_emm.ParseFromArray(emm.data() + start_index,
|
||||
emm.size() - start_index)) {
|
||||
LOGE("Failed to parse signed EMM.");
|
||||
return false;
|
||||
}
|
||||
|
||||
signature_ = signed_emm.signature();
|
||||
if (signature_.empty()) {
|
||||
LOGE("No signature in the EMM.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!emm_payload_.ParseFromString(signed_emm.serialized_payload())) {
|
||||
LOGE("Failed to parse EMM payload.");
|
||||
return false;
|
||||
}
|
||||
timestamp_ = emm_payload_.timestamp_secs();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace wvcas
|
||||
355
plugin/src/license_key_status.cpp
Normal file
355
plugin/src/license_key_status.cpp
Normal file
@@ -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 <string>
|
||||
|
||||
#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<VideoResolutionConstraint>
|
||||
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<WidevinePsshData_EntitledKey>& keys) {
|
||||
for (std::vector<WidevinePsshData_EntitledKey>::const_iterator key =
|
||||
keys.begin();
|
||||
key != keys.end(); key++) {
|
||||
// Check to see if we have an entitlement key for this content key.
|
||||
std::map<KeyId, LicenseKeyStatus*>::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
|
||||
413
plugin/src/oemcrypto_interface.cpp
Normal file
413
plugin/src/oemcrypto_interface.cpp
Normal file
@@ -0,0 +1,413 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#include "oemcrypto_interface.h"
|
||||
|
||||
#include <dlfcn.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#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<name##_t>(LOOKUP(handle_, OEMCrypto_##name)); \
|
||||
if (nullptr == name) { \
|
||||
LOGE("%s", dlerror()); \
|
||||
return false; \
|
||||
}
|
||||
#define TRY_LOAD_SYM(name) \
|
||||
name = reinterpret_cast<name##_t>(LOOKUP(handle_, OEMCrypto_##name)); \
|
||||
if (nullptr == name) { \
|
||||
LOGW("%s", dlerror()); \
|
||||
}
|
||||
|
||||
namespace wvcas {
|
||||
class OEMCryptoInterface::Impl {
|
||||
public:
|
||||
~Impl() {}
|
||||
static std::unique_ptr<Impl> create() {
|
||||
// Using 'new' to access a non-public constructor.
|
||||
std::unique_ptr<Impl> interface = std::unique_ptr<Impl>(new Impl());
|
||||
if (!interface->initialize()) {
|
||||
return std::unique_ptr<Impl>();
|
||||
}
|
||||
return interface;
|
||||
}
|
||||
|
||||
typedef OEMCryptoResult (*Initialize_t)();
|
||||
typedef OEMCryptoResult (*Terminate_t)();
|
||||
typedef OEMCryptoResult (*OpenSession_t)(OEMCrypto_SESSION*);
|
||||
typedef OEMCryptoResult (*CloseSession_t)(OEMCrypto_SESSION);
|
||||
typedef OEMCrypto_ProvisioningMethod (*GetProvisioningMethod_t)();
|
||||
typedef OEMCryptoResult (*GetKeyData_t)(uint8_t*, size_t*);
|
||||
typedef uint32_t (*SupportedCertificates_t)();
|
||||
typedef OEMCryptoResult (*GenerateNonce_t)(OEMCrypto_SESSION session,
|
||||
uint32_t* nonce);
|
||||
typedef OEMCryptoResult (*PrepAndSignLicenseRequest_t)(OEMCrypto_SESSION,
|
||||
const uint8_t*, size_t,
|
||||
size_t*, uint8_t*,
|
||||
size_t*);
|
||||
typedef OEMCryptoResult (*PrepAndSignRenewalRequest_t)(OEMCrypto_SESSION,
|
||||
const uint8_t*, size_t,
|
||||
size_t*, uint8_t*,
|
||||
size_t*);
|
||||
typedef OEMCryptoResult (*PrepAndSignProvisioningRequest_t)(
|
||||
OEMCrypto_SESSION, const uint8_t*, size_t, size_t*, uint8_t*, size_t*);
|
||||
typedef OEMCryptoResult (*LoadProvisioning_t)(OEMCrypto_SESSION,
|
||||
const uint8_t*, size_t, size_t,
|
||||
const uint8_t*, size_t,
|
||||
uint8_t*, size_t*);
|
||||
typedef OEMCryptoResult (*GenerateDerivedKeys_t)(
|
||||
OEMCrypto_SESSION session, const uint8_t* mac_key_context,
|
||||
uint32_t mac_key_context_length, const uint8_t* enc_key_context,
|
||||
uint32_t enc_key_context_length);
|
||||
typedef OEMCryptoResult (*GetOEMPublicCertificate_t)(OEMCrypto_SESSION,
|
||||
uint8_t*, size_t*);
|
||||
typedef OEMCryptoResult (*LoadDRMPrivateKey_t)(OEMCrypto_SESSION,
|
||||
OEMCrypto_PrivateKeyType,
|
||||
const uint8_t*, size_t);
|
||||
typedef OEMCryptoResult (*GenerateRSASignature_t)(OEMCrypto_SESSION,
|
||||
const uint8_t*, size_t,
|
||||
uint8_t*, size_t*,
|
||||
RSA_Padding_Scheme);
|
||||
typedef OEMCryptoResult (*DeriveKeysFromSessionKey_t)(OEMCrypto_SESSION,
|
||||
const uint8_t*, size_t,
|
||||
const uint8_t*, size_t,
|
||||
const uint8_t*, size_t);
|
||||
typedef OEMCryptoResult (*LoadLicense_t)(OEMCrypto_SESSION session,
|
||||
const uint8_t* message,
|
||||
size_t message_length,
|
||||
size_t core_message_length,
|
||||
const uint8_t* signature,
|
||||
size_t signature_length);
|
||||
typedef OEMCryptoResult (*LoadRenewal_t)(OEMCrypto_SESSION session,
|
||||
const uint8_t* message,
|
||||
size_t message_length,
|
||||
size_t core_message_length,
|
||||
const uint8_t* signature,
|
||||
size_t signature_length);
|
||||
typedef OEMCryptoResult (*LoadCasECMKeys_t)(
|
||||
OEMCrypto_SESSION, const uint8_t*, size_t,
|
||||
const OEMCrypto_EntitledContentKeyObject*,
|
||||
const OEMCrypto_EntitledContentKeyObject*);
|
||||
typedef OEMCryptoResult (*GetHDCPCapability_t)(OEMCrypto_HDCP_Capability*,
|
||||
OEMCrypto_HDCP_Capability*);
|
||||
typedef OEMCryptoResult (*GetDeviceID_t)(uint8_t* deviceID, size_t* idLength);
|
||||
typedef OEMCryptoResult (*LoadTestKeybox_t)(const uint8_t* buffer,
|
||||
size_t length);
|
||||
typedef const char* (*SecurityLevel_t)();
|
||||
typedef OEMCryptoResult (*CreateEntitledKeySession_t)(
|
||||
OEMCrypto_SESSION oec_session, OEMCrypto_SESSION* key_session);
|
||||
typedef OEMCryptoResult (*RemoveEntitledKeySession_t)(
|
||||
OEMCrypto_SESSION key_session);
|
||||
typedef OEMCryptoResult (*ReassociateEntitledKeySession_t)(
|
||||
OEMCrypto_SESSION key_session, OEMCrypto_SESSION oec_session);
|
||||
typedef uint32_t (*APIVersion_t)();
|
||||
typedef OEMCryptoResult (*GetOEMKeyToken_t)(OEMCrypto_SESSION key_session,
|
||||
uint8_t* key_token,
|
||||
size_t* key_token_length);
|
||||
typedef OEMCryptoResult (*GetSignatureHashAlgorithm_t)(
|
||||
OEMCrypto_SESSION session, OEMCrypto_SignatureHashAlgorithm* algorithm);
|
||||
|
||||
Initialize_t Initialize = nullptr;
|
||||
Terminate_t Terminate = nullptr;
|
||||
OpenSession_t OpenSession = nullptr;
|
||||
CloseSession_t CloseSession = nullptr;
|
||||
GetProvisioningMethod_t GetProvisioningMethod = nullptr;
|
||||
GetKeyData_t GetKeyData = nullptr;
|
||||
SupportedCertificates_t SupportedCertificates = nullptr;
|
||||
GenerateNonce_t GenerateNonce = nullptr;
|
||||
GenerateDerivedKeys_t GenerateDerivedKeys = nullptr;
|
||||
PrepAndSignLicenseRequest_t PrepAndSignLicenseRequest = nullptr;
|
||||
PrepAndSignRenewalRequest_t PrepAndSignRenewalRequest = nullptr;
|
||||
PrepAndSignProvisioningRequest_t PrepAndSignProvisioningRequest = nullptr;
|
||||
LoadProvisioning_t LoadProvisioning = nullptr;
|
||||
GetOEMPublicCertificate_t GetOEMPublicCertificate = nullptr;
|
||||
LoadDRMPrivateKey_t LoadDRMPrivateKey = nullptr;
|
||||
GenerateRSASignature_t GenerateRSASignature = nullptr;
|
||||
DeriveKeysFromSessionKey_t DeriveKeysFromSessionKey = nullptr;
|
||||
LoadLicense_t LoadLicense = nullptr;
|
||||
LoadRenewal_t LoadRenewal = nullptr;
|
||||
LoadCasECMKeys_t LoadCasECMKeys = nullptr;
|
||||
GetHDCPCapability_t GetHDCPCapability = nullptr;
|
||||
GetDeviceID_t GetDeviceID = nullptr;
|
||||
LoadTestKeybox_t LoadTestKeybox = nullptr;
|
||||
SecurityLevel_t SecurityLevel = nullptr;
|
||||
CreateEntitledKeySession_t CreateEntitledKeySession = nullptr;
|
||||
RemoveEntitledKeySession_t RemoveEntitledKeySession = nullptr;
|
||||
ReassociateEntitledKeySession_t ReassociateEntitledKeySession = nullptr;
|
||||
APIVersion_t APIVersion = nullptr;
|
||||
GetOEMKeyToken_t GetOEMKeyToken = nullptr;
|
||||
GetSignatureHashAlgorithm_t GetSignatureHashAlgorithm = nullptr;
|
||||
|
||||
private:
|
||||
bool initialize() {
|
||||
dlerror();
|
||||
std::string oemcrypto_path;
|
||||
if (!Properties::GetOEMCryptoPath(&oemcrypto_path)) {
|
||||
LOGE("Can't get oemc library path");
|
||||
return false;
|
||||
}
|
||||
handle_ = dlopen(oemcrypto_path.c_str(), RTLD_NOW);
|
||||
if (nullptr == handle_) {
|
||||
LOGE("Can't open oemc library: %s", dlerror());
|
||||
return false;
|
||||
}
|
||||
|
||||
LOAD_SYM(Initialize);
|
||||
LOAD_SYM(Terminate);
|
||||
LOAD_SYM(OpenSession);
|
||||
LOAD_SYM(CloseSession);
|
||||
LOAD_SYM(GetProvisioningMethod);
|
||||
LOAD_SYM(GetKeyData);
|
||||
LOAD_SYM(SupportedCertificates);
|
||||
LOAD_SYM(GenerateNonce);
|
||||
LOAD_SYM(GenerateDerivedKeys);
|
||||
LOAD_SYM(PrepAndSignLicenseRequest);
|
||||
LOAD_SYM(PrepAndSignRenewalRequest);
|
||||
LOAD_SYM(PrepAndSignProvisioningRequest);
|
||||
LOAD_SYM(LoadProvisioning);
|
||||
LOAD_SYM(GetOEMPublicCertificate);
|
||||
LOAD_SYM(LoadDRMPrivateKey);
|
||||
LOAD_SYM(GenerateRSASignature);
|
||||
LOAD_SYM(DeriveKeysFromSessionKey);
|
||||
LOAD_SYM(LoadLicense);
|
||||
LOAD_SYM(LoadRenewal);
|
||||
LOAD_SYM(LoadCasECMKeys);
|
||||
LOAD_SYM(GetHDCPCapability);
|
||||
LOAD_SYM(GetDeviceID);
|
||||
LOAD_SYM(SecurityLevel);
|
||||
LOAD_SYM(CreateEntitledKeySession);
|
||||
LOAD_SYM(RemoveEntitledKeySession);
|
||||
LOAD_SYM(APIVersion);
|
||||
LOAD_SYM(ReassociateEntitledKeySession);
|
||||
TRY_LOAD_SYM(GetOEMKeyToken);
|
||||
TRY_LOAD_SYM(GetSignatureHashAlgorithm);
|
||||
|
||||
// Optional methods that may be available.
|
||||
TRY_LOAD_SYM(LoadTestKeybox);
|
||||
return true;
|
||||
}
|
||||
Impl() : handle_(nullptr) {}
|
||||
void* handle_ = nullptr;
|
||||
};
|
||||
|
||||
OEMCryptoInterface::OEMCryptoInterface() {}
|
||||
OEMCryptoInterface::~OEMCryptoInterface() = default;
|
||||
|
||||
OEMCryptoResult OEMCryptoInterface::OEMCrypto_Initialize(void) {
|
||||
if (!impl_) {
|
||||
impl_ = Impl::create();
|
||||
if (!impl_) {
|
||||
return OEMCrypto_ERROR_INIT_FAILED;
|
||||
}
|
||||
}
|
||||
return impl_->Initialize();
|
||||
}
|
||||
|
||||
OEMCryptoResult OEMCryptoInterface::OEMCrypto_Terminate(void) {
|
||||
return impl_->Terminate();
|
||||
}
|
||||
|
||||
OEMCryptoResult OEMCryptoInterface::OEMCrypto_OpenSession(
|
||||
OEMCrypto_SESSION* session) const {
|
||||
return impl_->OpenSession(session);
|
||||
}
|
||||
|
||||
OEMCryptoResult OEMCryptoInterface::OEMCrypto_CloseSession(
|
||||
OEMCrypto_SESSION session) const {
|
||||
return impl_->CloseSession(session);
|
||||
}
|
||||
|
||||
OEMCrypto_ProvisioningMethod
|
||||
OEMCryptoInterface::OEMCrypto_GetProvisioningMethod() const {
|
||||
return impl_->GetProvisioningMethod();
|
||||
}
|
||||
|
||||
OEMCryptoResult OEMCryptoInterface::OEMCrypto_GetKeyData(
|
||||
uint8_t* keyData, size_t* keyDataLength) const {
|
||||
return impl_->GetKeyData(keyData, keyDataLength);
|
||||
}
|
||||
|
||||
uint32_t OEMCryptoInterface::OEMCrypto_SupportedCertificates() const {
|
||||
return impl_->SupportedCertificates();
|
||||
}
|
||||
|
||||
OEMCryptoResult OEMCryptoInterface::OEMCrypto_GenerateNonce(
|
||||
OEMCrypto_SESSION session, uint32_t* nonce) const {
|
||||
return impl_->GenerateNonce(session, nonce);
|
||||
}
|
||||
|
||||
OEMCryptoResult OEMCryptoInterface::OEMCrypto_GenerateDerivedKeys(
|
||||
OEMCrypto_SESSION session, const uint8_t* mac_key_context,
|
||||
uint32_t mac_key_context_length, const uint8_t* enc_key_context,
|
||||
uint32_t enc_key_context_length) const {
|
||||
return impl_->GenerateDerivedKeys(session, mac_key_context,
|
||||
mac_key_context_length, enc_key_context,
|
||||
enc_key_context_length);
|
||||
}
|
||||
|
||||
OEMCryptoResult OEMCryptoInterface::OEMCrypto_PrepAndSignLicenseRequest(
|
||||
OEMCrypto_SESSION session, uint8_t* message, size_t message_length,
|
||||
size_t* core_message_size, uint8_t* signature,
|
||||
size_t* signature_length) const {
|
||||
return impl_->PrepAndSignLicenseRequest(session, message, message_length,
|
||||
core_message_size, signature,
|
||||
signature_length);
|
||||
}
|
||||
|
||||
OEMCryptoResult OEMCryptoInterface::OEMCrypto_PrepAndSignRenewalRequest(
|
||||
OEMCrypto_SESSION session, uint8_t* message, size_t message_length,
|
||||
size_t* core_message_size, uint8_t* signature,
|
||||
size_t* signature_length) const {
|
||||
return impl_->PrepAndSignRenewalRequest(session, message, message_length,
|
||||
core_message_size, signature,
|
||||
signature_length);
|
||||
}
|
||||
|
||||
OEMCryptoResult OEMCryptoInterface::OEMCrypto_PrepAndSignProvisioningRequest(
|
||||
OEMCrypto_SESSION session, uint8_t* message, size_t message_length,
|
||||
size_t* core_message_size, uint8_t* signature,
|
||||
size_t* signature_length) const {
|
||||
return impl_->PrepAndSignProvisioningRequest(session, message, message_length,
|
||||
core_message_size, signature,
|
||||
signature_length);
|
||||
}
|
||||
|
||||
OEMCryptoResult OEMCryptoInterface::OEMCrypto_LoadProvisioning(
|
||||
OEMCrypto_SESSION session, const uint8_t* message, size_t message_length,
|
||||
size_t core_message_length, const uint8_t* signature,
|
||||
size_t signature_length, uint8_t* wrapped_private_key,
|
||||
size_t* wrapped_private_key_length) const {
|
||||
return impl_->LoadProvisioning(
|
||||
session, message, message_length, core_message_length, signature,
|
||||
signature_length, wrapped_private_key, wrapped_private_key_length);
|
||||
}
|
||||
|
||||
OEMCryptoResult OEMCryptoInterface::OEMCrypto_GetOEMPublicCertificate(
|
||||
OEMCrypto_SESSION session, uint8_t* public_cert,
|
||||
size_t* public_cert_length) const {
|
||||
return impl_->GetOEMPublicCertificate(session, public_cert,
|
||||
public_cert_length);
|
||||
}
|
||||
|
||||
OEMCryptoResult OEMCryptoInterface::OEMCrypto_LoadDRMPrivateKey(
|
||||
OEMCrypto_SESSION session, OEMCrypto_PrivateKeyType key_type,
|
||||
const uint8_t* wrapped_rsa_key, size_t wrapped_rsa_key_length) const {
|
||||
return impl_->LoadDRMPrivateKey(session, key_type, wrapped_rsa_key,
|
||||
wrapped_rsa_key_length);
|
||||
}
|
||||
|
||||
OEMCryptoResult OEMCryptoInterface::OEMCrypto_GenerateRSASignature(
|
||||
OEMCrypto_SESSION session, const uint8_t* message, size_t message_length,
|
||||
uint8_t* signature, size_t* signature_length,
|
||||
RSA_Padding_Scheme padding_scheme) const {
|
||||
return impl_->GenerateRSASignature(session, message, message_length,
|
||||
signature, signature_length,
|
||||
padding_scheme);
|
||||
}
|
||||
|
||||
OEMCryptoResult OEMCryptoInterface::OEMCrypto_DeriveKeysFromSessionKey(
|
||||
OEMCrypto_SESSION session, const uint8_t* enc_session_key,
|
||||
size_t enc_session_key_length, const uint8_t* mac_key_context,
|
||||
size_t mac_key_context_length, const uint8_t* enc_key_context,
|
||||
size_t enc_key_context_length) const {
|
||||
return impl_->DeriveKeysFromSessionKey(
|
||||
session, enc_session_key, enc_session_key_length, mac_key_context,
|
||||
mac_key_context_length, enc_key_context, enc_key_context_length);
|
||||
}
|
||||
|
||||
OEMCryptoResult OEMCryptoInterface::OEMCrypto_LoadLicense(
|
||||
OEMCrypto_SESSION session, const uint8_t* message, size_t message_length,
|
||||
size_t core_message_length, const uint8_t* signature,
|
||||
size_t signature_length) const {
|
||||
return impl_->LoadLicense(session, message, message_length,
|
||||
core_message_length, signature, signature_length);
|
||||
}
|
||||
|
||||
OEMCryptoResult OEMCryptoInterface::OEMCrypto_LoadRenewal(
|
||||
OEMCrypto_SESSION session, const uint8_t* message, size_t message_length,
|
||||
size_t core_message_length, const uint8_t* signature,
|
||||
size_t signature_length) const {
|
||||
return impl_->LoadRenewal(session, message, message_length,
|
||||
core_message_length, signature, signature_length);
|
||||
}
|
||||
|
||||
OEMCryptoResult OEMCryptoInterface::OEMCrypto_LoadCasECMKeys(
|
||||
OEMCrypto_SESSION session, const uint8_t* message, size_t message_length,
|
||||
const OEMCrypto_EntitledContentKeyObject* even_key,
|
||||
const OEMCrypto_EntitledContentKeyObject* odd_key) const {
|
||||
return impl_->LoadCasECMKeys(session, message, message_length, even_key,
|
||||
odd_key);
|
||||
}
|
||||
|
||||
OEMCryptoResult OEMCryptoInterface::OEMCrypto_GetHDCPCapability(
|
||||
OEMCrypto_HDCP_Capability* current, OEMCrypto_HDCP_Capability* max) const {
|
||||
return impl_->GetHDCPCapability(current, max);
|
||||
}
|
||||
|
||||
OEMCryptoResult OEMCryptoInterface::OEMCrypto_GetDeviceID(
|
||||
uint8_t* deviceID, size_t* idLength) const {
|
||||
return impl_->GetDeviceID(deviceID, idLength);
|
||||
}
|
||||
|
||||
OEMCryptoResult OEMCryptoInterface::OEMCrypto_LoadTestKeybox(
|
||||
const uint8_t* buffer, size_t length) const {
|
||||
// Optional method. Handle missing method.
|
||||
if (impl_->LoadTestKeybox == nullptr) {
|
||||
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
return impl_->LoadTestKeybox(buffer, length);
|
||||
}
|
||||
|
||||
const char* OEMCryptoInterface::OEMCrypto_SecurityLevel() const {
|
||||
return impl_->SecurityLevel();
|
||||
}
|
||||
|
||||
OEMCryptoResult OEMCryptoInterface::OEMCrypto_CreateEntitledKeySession(
|
||||
OEMCrypto_SESSION oec_session, OEMCrypto_SESSION* key_session) const {
|
||||
return impl_->CreateEntitledKeySession(oec_session, key_session);
|
||||
}
|
||||
|
||||
OEMCryptoResult OEMCryptoInterface::OEMCrypto_RemoveEntitledKeySession(
|
||||
OEMCrypto_SESSION key_session) const {
|
||||
return impl_->RemoveEntitledKeySession(key_session);
|
||||
}
|
||||
|
||||
OEMCryptoResult OEMCryptoInterface::OEMCrypto_ReassociateEntitledKeySession(
|
||||
OEMCrypto_SESSION key_session, OEMCrypto_SESSION oec_session) const {
|
||||
return impl_->ReassociateEntitledKeySession(key_session, oec_session);
|
||||
}
|
||||
|
||||
uint32_t OEMCryptoInterface::OEMCrypto_APIVersion() const {
|
||||
return impl_->APIVersion();
|
||||
}
|
||||
|
||||
OEMCryptoResult OEMCryptoInterface::OEMCrypto_GetOEMKeyToken(
|
||||
OEMCrypto_SESSION key_session, uint8_t* key_token,
|
||||
size_t* key_token_length) const {
|
||||
// Optional method. Handle missing method.
|
||||
if (impl_->GetOEMKeyToken == nullptr) {
|
||||
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
return impl_->GetOEMKeyToken(key_session, key_token, key_token_length);
|
||||
}
|
||||
|
||||
OEMCryptoResult OEMCryptoInterface::OEMCrypto_GetSignatureHashAlgorithm(
|
||||
OEMCrypto_SESSION session,
|
||||
OEMCrypto_SignatureHashAlgorithm* algorithm) const {
|
||||
// Optional method. Handle missing method.
|
||||
if (impl_->GetOEMKeyToken == nullptr) {
|
||||
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
return impl_->GetSignatureHashAlgorithm(session, algorithm);
|
||||
}
|
||||
|
||||
} // namespace wvcas
|
||||
368
plugin/src/policy_engine.cpp
Normal file
368
plugin/src/policy_engine.cpp
Normal file
@@ -0,0 +1,368 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#include "policy_engine.h"
|
||||
|
||||
#include <limits.h>
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#include "log.h"
|
||||
#include "string_conversions.h"
|
||||
|
||||
// TODO(vtarasov):
|
||||
//#include "properties.h"
|
||||
//#include "cas_event_listener.h"
|
||||
|
||||
using video_widevine::License;
|
||||
|
||||
namespace {
|
||||
|
||||
static constexpr int kPolicyTimerDurationSeconds = 1;
|
||||
static constexpr int kClockSkewDelta = 5; // seconds
|
||||
|
||||
// Use 0 to represent never expired license as specified in EME spec
|
||||
// (NaN in JS translates to 0 in unix timestamp).
|
||||
static constexpr int64_t NEVER_EXPIRES = 0;
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace wvcas {
|
||||
|
||||
bool PolicyEngine::CanDecryptContent(const KeyId& key_id) const {
|
||||
if (license_keys_->IsContentKey(key_id)) {
|
||||
return license_keys_->CanDecryptContent(key_id);
|
||||
} else {
|
||||
LOGE("PolicyEngine::CanDecryptContent Key '%s' not in license.",
|
||||
wvutil::b2a_hex(key_id).c_str());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void PolicyEngine::SetLicense(const License& license) {
|
||||
license_id_.Clear();
|
||||
license_id_.CopyFrom(license.id());
|
||||
policy_.Clear();
|
||||
license_keys_->SetFromLicense(license);
|
||||
UpdateLicense(license);
|
||||
}
|
||||
|
||||
void PolicyEngine::UpdateLicense(const License& license) {
|
||||
if (!license.has_policy()) return;
|
||||
|
||||
if (kLicenseStateExpired == license_state_) {
|
||||
LOGD("PolicyEngine::UpdateLicense: updating an expired license");
|
||||
}
|
||||
|
||||
policy_.MergeFrom(license.policy());
|
||||
|
||||
// some basic license validation
|
||||
// license start time needs to be specified in the initial response
|
||||
if (!license.has_license_start_time()) return;
|
||||
|
||||
// if renewal, discard license if version has not been updated
|
||||
if (license_state_ != kLicenseStateInitial && policy_.can_play()) {
|
||||
if (license.id().version() > license_id_.version())
|
||||
license_id_.CopyFrom(license.id());
|
||||
else
|
||||
return;
|
||||
}
|
||||
|
||||
// Update time information
|
||||
license_start_time_ = license.license_start_time();
|
||||
next_renewal_time_ = license_start_time_ + policy_.renewal_delay_seconds();
|
||||
|
||||
int64_t current_time = GetCurrentTime();
|
||||
if ((!policy_.can_play() ||
|
||||
HasLicenseOrPlaybackDurationExpired(current_time)) &&
|
||||
license_state_ != kLicenseStateExpired) {
|
||||
license_state_ = kLicenseStateExpired;
|
||||
NotifyLicenseExpired(license_state_);
|
||||
NotifyKeysChange(kKeyStatusExpired);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update state
|
||||
if (current_time >= license_start_time_) {
|
||||
license_state_ = kLicenseStateCanPlay;
|
||||
NotifyKeysChange(kKeyStatusUsable);
|
||||
} else {
|
||||
license_state_ = kLicenseStatePending;
|
||||
NotifyKeysChange(kKeyStatusPending);
|
||||
}
|
||||
NotifyExpirationUpdate(current_time);
|
||||
NotifyRenewalServerUpdate();
|
||||
}
|
||||
|
||||
int64_t PolicyEngine::GetCurrentTime() {
|
||||
int64_t current_time = clock_->GetCurrentTime();
|
||||
if (current_time + kClockSkewDelta < last_recorded_current_time_)
|
||||
current_time = last_recorded_current_time_;
|
||||
else
|
||||
last_recorded_current_time_ = current_time;
|
||||
return current_time;
|
||||
}
|
||||
|
||||
void PolicyEngine::NotifyRenewalServerUpdate() {
|
||||
if (policy_.renewal_server_url() != renewal_server_url_) {
|
||||
renewal_server_url_ = policy_.renewal_server_url();
|
||||
if (event_listener_) {
|
||||
event_listener_->OnNewRenewalServerUrl(renewal_server_url_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PolicyEngine::NotifyLicenseExpired(LicenseState license_state) {
|
||||
if (event_listener_ && license_state == kLicenseStateExpired) {
|
||||
event_listener_->OnLicenseExpiration();
|
||||
}
|
||||
}
|
||||
|
||||
// Apply a key status to the current keys.
|
||||
// If this represents a new key status, perform a notification callback.
|
||||
// NOTE: if the new status is kKeyStatusUsable, the HDCP check may result in an
|
||||
// override to kKeyStatusOutputNotAllowed.
|
||||
void PolicyEngine::NotifyKeysChange(KeyStatus new_status) {
|
||||
bool keys_changed;
|
||||
bool has_new_usable_key = false;
|
||||
if (new_status == kKeyStatusUsable) {
|
||||
CheckDeviceHdcpStatus();
|
||||
}
|
||||
keys_changed =
|
||||
license_keys_->ApplyStatusChange(new_status, &has_new_usable_key);
|
||||
if (event_listener_ && keys_changed) {
|
||||
KeyStatusMap content_keys;
|
||||
license_keys_->ExtractKeyStatuses(&content_keys);
|
||||
event_listener_->OnSessionKeysChange(content_keys, has_new_usable_key);
|
||||
}
|
||||
}
|
||||
|
||||
bool PolicyEngine::HasLicenseOrPlaybackDurationExpired(int64_t current_time) {
|
||||
const int64_t expiry_time =
|
||||
GetExpiryTime(current_time,
|
||||
/* ignore_soft_enforce_playback_duration */ false);
|
||||
return expiry_time != NEVER_EXPIRES && expiry_time <= current_time;
|
||||
}
|
||||
|
||||
void PolicyEngine::NotifyExpirationUpdate(int64_t current_time) {
|
||||
const int64_t expiry_time =
|
||||
GetExpiryTime(current_time,
|
||||
/* ignore_soft_enforce_playback_duration */ false);
|
||||
if (!last_expiry_time_set_ || expiry_time != last_expiry_time_) {
|
||||
last_expiry_time_ = expiry_time;
|
||||
if (event_listener_) event_listener_->OnExpirationUpdate(expiry_time);
|
||||
}
|
||||
last_expiry_time_set_ = true;
|
||||
}
|
||||
|
||||
int64_t PolicyEngine::GetExpiryTime(
|
||||
int64_t current_time, bool ignore_soft_enforce_playback_duration) {
|
||||
if (!HasPlaybackStarted(current_time)) return GetRentalExpiryTime();
|
||||
|
||||
const int64_t hard_limit = GetHardLicenseExpiryTime();
|
||||
if (policy_.playback_duration_seconds() == 0) return hard_limit;
|
||||
if (!ignore_soft_enforce_playback_duration && !was_expired_on_load_ &&
|
||||
policy_.soft_enforce_playback_duration()) {
|
||||
return hard_limit;
|
||||
}
|
||||
const int64_t expiry_time =
|
||||
playback_start_time_ + policy_.playback_duration_seconds();
|
||||
|
||||
if (hard_limit == NEVER_EXPIRES) return expiry_time;
|
||||
return std::min(hard_limit, expiry_time);
|
||||
}
|
||||
|
||||
void PolicyEngine::CheckDeviceHdcpStatusOnTimer(int64_t current_time) {
|
||||
if (current_time >= next_device_check_) {
|
||||
CheckDeviceHdcpStatus();
|
||||
next_device_check_ = current_time + HDCP_DEVICE_CHECK_INTERVAL;
|
||||
}
|
||||
}
|
||||
|
||||
int64_t PolicyEngine::GetRentalExpiryTime() {
|
||||
const int64_t hard_limit = GetHardLicenseExpiryTime();
|
||||
if (policy_.rental_duration_seconds() == 0) return hard_limit;
|
||||
const int64_t expiry_time =
|
||||
license_start_time_ + policy_.rental_duration_seconds();
|
||||
if (hard_limit == NEVER_EXPIRES) return expiry_time;
|
||||
return std::min(hard_limit, expiry_time);
|
||||
}
|
||||
|
||||
// For the policy time fields checked in the following methods, a value of 0
|
||||
// indicates that there is no limit to the duration. If the fields are zero
|
||||
// (including the hard limit) then these methods will return NEVER_EXPIRES.
|
||||
|
||||
int64_t PolicyEngine::GetHardLicenseExpiryTime() {
|
||||
return policy_.license_duration_seconds() > 0
|
||||
? license_start_time_ + policy_.license_duration_seconds()
|
||||
: NEVER_EXPIRES;
|
||||
}
|
||||
|
||||
void PolicyEngine::CheckDeviceHdcpStatus() {
|
||||
if (!license_keys_->Empty()) {
|
||||
HdcpCapability current_hdcp_level;
|
||||
HdcpCapability ignored;
|
||||
if (!crypto_session_->GetHdcpCapabilities(¤t_hdcp_level, &ignored)) {
|
||||
current_hdcp_level = HDCP_NONE;
|
||||
}
|
||||
license_keys_->ApplyConstraints(current_resolution_, current_hdcp_level);
|
||||
}
|
||||
}
|
||||
|
||||
void PolicyEngine::BeginDecryption() {
|
||||
if (playback_start_time_ == 0) {
|
||||
switch (license_state_) {
|
||||
case kLicenseStateCanPlay:
|
||||
case kLicenseStateNeedRenewal:
|
||||
case kLicenseStateWaitingLicenseUpdate:
|
||||
playback_start_time_ = GetCurrentTime();
|
||||
last_playback_time_ = playback_start_time_;
|
||||
if (policy_.play_start_grace_period_seconds() == 0)
|
||||
grace_period_end_time_ = playback_start_time_;
|
||||
|
||||
if (policy_.renew_with_usage()) {
|
||||
license_state_ = kLicenseStateNeedRenewal;
|
||||
}
|
||||
NotifyExpirationUpdate(playback_start_time_);
|
||||
break;
|
||||
case kLicenseStateInitial:
|
||||
case kLicenseStatePending:
|
||||
case kLicenseStateExpired:
|
||||
NotifyLicenseExpired(license_state_);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PolicyEngine::OnTimerEvent() {
|
||||
last_recorded_current_time_ += kPolicyTimerDurationSeconds;
|
||||
int64_t current_time = GetCurrentTime();
|
||||
|
||||
// If we have passed the grace period, the expiration will update.
|
||||
if (grace_period_end_time_ == 0 && HasPlaybackStarted(current_time)) {
|
||||
grace_period_end_time_ = playback_start_time_;
|
||||
NotifyExpirationUpdate(current_time);
|
||||
}
|
||||
|
||||
// License expiration trumps all.
|
||||
if (HasLicenseOrPlaybackDurationExpired(current_time) &&
|
||||
license_state_ != kLicenseStateExpired) {
|
||||
license_state_ = kLicenseStateExpired;
|
||||
NotifyLicenseExpired(license_state_);
|
||||
NotifyKeysChange(kKeyStatusExpired);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check device conditions that affect playability (HDCP, resolution)
|
||||
CheckDeviceHdcpStatusOnTimer(current_time);
|
||||
|
||||
bool renewal_needed = false;
|
||||
|
||||
// Test to determine if renewal should be attempted.
|
||||
switch (license_state_) {
|
||||
case kLicenseStateCanPlay: {
|
||||
if (HasRenewalDelayExpired(current_time)) {
|
||||
renewal_needed = true;
|
||||
}
|
||||
// HDCP may change, so force a check.
|
||||
NotifyKeysChange(kKeyStatusUsable);
|
||||
break;
|
||||
}
|
||||
|
||||
case kLicenseStateNeedRenewal: {
|
||||
renewal_needed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case kLicenseStateWaitingLicenseUpdate: {
|
||||
if (HasRenewalRetryIntervalExpired(current_time)) {
|
||||
renewal_needed = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case kLicenseStatePending: {
|
||||
if (current_time >= license_start_time_) {
|
||||
license_state_ = kLicenseStateCanPlay;
|
||||
NotifyKeysChange(kKeyStatusUsable);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case kLicenseStateInitial:
|
||||
case kLicenseStateExpired: {
|
||||
NotifyLicenseExpired(license_state_);
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
license_state_ = kLicenseStateExpired;
|
||||
NotifyLicenseExpired(license_state_);
|
||||
NotifyKeysChange(kKeyStatusInternalError);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (renewal_needed) {
|
||||
UpdateRenewalRequest(current_time);
|
||||
if (event_listener_) {
|
||||
event_listener_->OnSessionRenewalNeeded();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool PolicyEngine::HasRenewalDelayExpired(int64_t current_time) {
|
||||
return policy_.can_renew() && (policy_.renewal_delay_seconds() > 0) &&
|
||||
license_start_time_ + policy_.renewal_delay_seconds() <= current_time;
|
||||
}
|
||||
|
||||
bool PolicyEngine::HasRenewalRetryIntervalExpired(int64_t current_time) {
|
||||
return policy_.can_renew() &&
|
||||
(policy_.renewal_retry_interval_seconds() > 0) &&
|
||||
next_renewal_time_ <= current_time;
|
||||
}
|
||||
|
||||
void PolicyEngine::UpdateRenewalRequest(int64_t current_time) {
|
||||
license_state_ = kLicenseStateWaitingLicenseUpdate;
|
||||
next_renewal_time_ = current_time + policy_.renewal_retry_interval_seconds();
|
||||
}
|
||||
|
||||
void PolicyEngine::SetEntitledLicenseKeys(
|
||||
const std::vector<WidevinePsshData_EntitledKey>& entitled_keys) {
|
||||
license_keys_->SetEntitledKeys(entitled_keys);
|
||||
}
|
||||
|
||||
std::unique_ptr<wvcas::LicenseKeys> PolicyEngine::CreateLicenseKeys() {
|
||||
return make_unique<wvcas::LicenseKeys>();
|
||||
}
|
||||
|
||||
std::unique_ptr<wvutil::Clock> PolicyEngine::CreateClock() {
|
||||
return make_unique<wvutil::Clock>();
|
||||
}
|
||||
|
||||
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
|
||||
931
plugin/src/widevine_cas_api.cpp
Normal file
931
plugin/src/widevine_cas_api.cpp
Normal file
@@ -0,0 +1,931 @@
|
||||
#include "widevine_cas_api.h"
|
||||
|
||||
#include <openssl/rand.h>
|
||||
#include <openssl/sha.h>
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include "cas_events.h"
|
||||
#include "cas_util.h"
|
||||
#include "license_protocol.pb.h"
|
||||
#include "log.h"
|
||||
#include "media_cas.pb.h"
|
||||
#include "string_conversions.h"
|
||||
#include "widevine_cas_session_map.h"
|
||||
|
||||
constexpr char kBasePathPrefix[] = "/data/vendor/mediacas/IDM/widevine/";
|
||||
constexpr char kCertFileBase[] = "cert.bin";
|
||||
constexpr char kLicenseFileNameSuffix[] = ".lic";
|
||||
|
||||
namespace {
|
||||
bool ReadFileFromStorage(wvutil::FileSystem& file_system,
|
||||
const std::string& filename, std::string* file_data) {
|
||||
if (nullptr == file_data) {
|
||||
return false;
|
||||
}
|
||||
if (!file_system.Exists(filename)) {
|
||||
return false;
|
||||
}
|
||||
size_t filesize = file_system.FileSize(filename);
|
||||
if (0 == filesize) {
|
||||
return false;
|
||||
}
|
||||
file_data->resize(filesize);
|
||||
std::unique_ptr<wvutil::File> 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<wvutil::File> file(file_system.Open(
|
||||
filename, wvutil::FileSystem::kTruncate | wvutil::FileSystem::kCreate));
|
||||
if (nullptr == file) {
|
||||
return false;
|
||||
}
|
||||
size_t bytes_written = file->Write(file_data.data(), file_data.size());
|
||||
if (bytes_written != file_data.size()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string GenerateLicenseFilename(const std::string& content_id,
|
||||
const std::string& provider_id) {
|
||||
std::string data(content_id + provider_id);
|
||||
std::string hash;
|
||||
hash.resize(SHA256_DIGEST_LENGTH);
|
||||
const unsigned char* input =
|
||||
reinterpret_cast<const unsigned char*>(data.data());
|
||||
unsigned char* output = reinterpret_cast<unsigned char*>(&hash[0]);
|
||||
SHA256(input, data.size(), output);
|
||||
return std::string(std::string(kBasePathPrefix) + wvutil::b2a_hex(hash) +
|
||||
std::string(kLicenseFileNameSuffix));
|
||||
}
|
||||
|
||||
std::string GenerateMultiContentLicenseInfo(
|
||||
const std::string& license_id,
|
||||
const std::vector<std::string>& content_list) {
|
||||
std::string message;
|
||||
if (license_id.empty() || content_list.empty()) {
|
||||
return message;
|
||||
}
|
||||
message.push_back(MultiContentLicenseFieldType::MULTI_CONTENT_LICENSE_ID);
|
||||
message.push_back((license_id.size() >> 8) & 0xff);
|
||||
message.push_back(license_id.size() & 0xff);
|
||||
message.append(license_id);
|
||||
for (const auto& content_id : content_list) {
|
||||
message.push_back(
|
||||
MultiContentLicenseFieldType::MULTI_CONTENT_LICENSE_CONTENT_ID);
|
||||
message.push_back((content_id.size() >> 8) & 0xff);
|
||||
message.push_back(content_id.size() & 0xff);
|
||||
message.append(content_id);
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
std::string GenerateGroupLicenseInfo(const std::string& license_id,
|
||||
const std::string group_id) {
|
||||
std::string message;
|
||||
if (license_id.empty() || group_id.empty()) {
|
||||
return message;
|
||||
}
|
||||
message.push_back(GroupLicenseFieldType::GROUP_LICENSE_ID);
|
||||
message.push_back((license_id.size() >> 8) & 0xff);
|
||||
message.push_back(license_id.size() & 0xff);
|
||||
message.append(license_id);
|
||||
|
||||
message.push_back(GroupLicenseFieldType::GROUP_LICENSE_GROUP_ID);
|
||||
message.push_back((group_id.size() >> 8) & 0xff);
|
||||
message.push_back(group_id.size() & 0xff);
|
||||
message.append(group_id);
|
||||
return message;
|
||||
}
|
||||
|
||||
// Generates a random number between 1 and |range_to|, all inclusive.
|
||||
uint32_t GetRandom(uint32_t range_to) {
|
||||
if (range_to <= 1) {
|
||||
return 1;
|
||||
}
|
||||
constexpr uint32_t max_val = std::numeric_limits<uint32_t>::max();
|
||||
|
||||
// Keep searching for a random value in a range divisible by |range_to|.
|
||||
// Worst case we have 1/2 chance to end the loop on each roll.
|
||||
uint32_t generated;
|
||||
do {
|
||||
RAND_bytes(reinterpret_cast<uint8_t*>(&generated), /*len=*/4);
|
||||
} while (generated >= (max_val - (max_val % range_to)));
|
||||
|
||||
return 1 + (generated % range_to);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace wvcas {
|
||||
|
||||
class MediaContext : public CasMediaId {
|
||||
public:
|
||||
MediaContext() : CasMediaId() {}
|
||||
~MediaContext() override {}
|
||||
MediaContext(const MediaContext&) = delete;
|
||||
MediaContext& operator=(const MediaContext&) = delete;
|
||||
|
||||
const std::string content_id() override { return pssh_.content_id(); }
|
||||
const std::string provider_id() override { return pssh_.provider(); }
|
||||
bool is_entitlement_rotation_enabled() override {
|
||||
return pssh_.has_entitlement_period_index();
|
||||
}
|
||||
uint32_t entitlement_period_index() override {
|
||||
return pssh_.entitlement_period_index();
|
||||
}
|
||||
std::string get_init_data() override { return pssh_.SerializeAsString(); }
|
||||
|
||||
CasStatus initialize(const std::string& init_data) override {
|
||||
if (!pssh_.ParseFromString(init_data)) {
|
||||
return CasStatus(CasStatusCode::kInvalidParameter, "invalid init_data");
|
||||
}
|
||||
return CasStatusCode::kNoError;
|
||||
}
|
||||
|
||||
private:
|
||||
video_widevine::WidevinePsshData pssh_;
|
||||
};
|
||||
|
||||
std::unique_ptr<CasMediaId> CasMediaId::create() {
|
||||
std::unique_ptr<MediaContext> ctx = make_unique<MediaContext>();
|
||||
return std::move(ctx);
|
||||
}
|
||||
|
||||
std::shared_ptr<CryptoSession> WidevineCas::getCryptoSession() {
|
||||
return std::make_shared<CryptoSession>();
|
||||
}
|
||||
|
||||
std::unique_ptr<CasLicense> WidevineCas::getCasLicense() {
|
||||
return make_unique<CasLicense>();
|
||||
}
|
||||
|
||||
std::unique_ptr<wvutil::FileSystem> WidevineCas::getFileSystem() {
|
||||
return make_unique<wvutil::FileSystem>();
|
||||
}
|
||||
|
||||
std::shared_ptr<WidevineCasSession> WidevineCas::newCasSession() {
|
||||
return std::make_shared<WidevineCasSession>();
|
||||
}
|
||||
|
||||
std::unique_ptr<EcmParser> WidevineCas::getEcmParser(const CasEcm& ecm) const {
|
||||
return EcmParser::Create(ecm);
|
||||
}
|
||||
|
||||
void WidevineCas::OnTimerEvent() {
|
||||
std::unique_lock<std::mutex> locker(lock_);
|
||||
if (cas_license_.get() != nullptr) {
|
||||
cas_license_->OnTimerEvent();
|
||||
|
||||
// Delete expired license after firing expired event in policy_engine
|
||||
if (cas_license_->IsExpired() && (media_id_.get() != nullptr)) {
|
||||
std::string filename = license_id_ + kLicenseFileNameSuffix;
|
||||
if (!file_system_->Exists(filename)) {
|
||||
LOGI("No expired license file stored in disk");
|
||||
} else {
|
||||
if (RemoveFile(*file_system_, filename)) {
|
||||
LOGI("Remove expired license file from disk successfully.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CasStatus WidevineCas::initialize(CasEventListener* event_listener) {
|
||||
std::unique_lock<std::mutex> 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<std::mutex> locker(lock_);
|
||||
if (nullptr == sessionId) {
|
||||
return CasStatus(CasStatusCode::kInvalidParameter,
|
||||
"missing openSession sessionId");
|
||||
}
|
||||
|
||||
CasSessionPtr session = newCasSession();
|
||||
CasStatus status =
|
||||
session->initialize(crypto_session_, event_listener_, sessionId);
|
||||
if (CasStatusCode::kNoError != status.status_code()) {
|
||||
return status;
|
||||
}
|
||||
WidevineCasSessionMap::instance().AddSession(*sessionId, session);
|
||||
return CasStatusCode::kNoError;
|
||||
}
|
||||
|
||||
CasStatus WidevineCas::closeSession(const WvCasSessionId& sessionId) {
|
||||
std::unique_lock<std::mutex> locker(lock_);
|
||||
CasSessionPtr session =
|
||||
WidevineCasSessionMap::instance().GetSession(sessionId);
|
||||
// TODO(jfore): Add a log event if the session doesn't exist and perhaps raise
|
||||
// an error.`
|
||||
if (session == nullptr) {
|
||||
return CasStatus(CasStatusCode::kSessionNotFound, "unknown session id");
|
||||
}
|
||||
WidevineCasSessionMap::instance().RemoveSession(sessionId);
|
||||
return CasStatusCode::kNoError;
|
||||
}
|
||||
|
||||
CasStatus WidevineCas::processEmm(const CasEmm& emm) {
|
||||
LOGI("WidevineCas::processEmm.");
|
||||
std::unique_ptr<const EmmParser> emm_parser = getEmmParser(emm);
|
||||
if (emm_parser == nullptr) {
|
||||
return CasStatus(CasStatusCode::kInvalidParameter, "Unable to parse emm");
|
||||
}
|
||||
if (event_listener_ == nullptr) {
|
||||
LOGW("processEmm: Event listener is not initialized.");
|
||||
return CasStatusCode::kNoError;
|
||||
}
|
||||
|
||||
// TODO(b/): Verify signature.
|
||||
// TODO(b/): Update EMM timer.
|
||||
|
||||
const video_widevine::EmmPayload& emm_payload = emm_parser->emm_payload();
|
||||
// Process fingerprinting info.
|
||||
std::set<CasData> current_fingerprinting_events_;
|
||||
for (int i = 0; i < emm_payload.fingerprinting_size(); ++i) {
|
||||
CasData message =
|
||||
GenerateFingerprintingEventMessage(emm_payload.fingerprinting(i));
|
||||
if (message.empty()) {
|
||||
continue;
|
||||
}
|
||||
if (last_fingerprinting_events_.find(message) ==
|
||||
last_fingerprinting_events_.end()) {
|
||||
event_listener_->OnFingerprintingUpdated(message);
|
||||
}
|
||||
current_fingerprinting_events_.insert(message);
|
||||
}
|
||||
last_fingerprinting_events_.clear();
|
||||
last_fingerprinting_events_.insert(current_fingerprinting_events_.begin(),
|
||||
current_fingerprinting_events_.end());
|
||||
|
||||
// Process service blocking info.
|
||||
std::set<CasData> current_service_blocking_events_;
|
||||
for (int i = 0; i < emm_payload.service_blocking_size(); ++i) {
|
||||
CasData message =
|
||||
GenerateServiceBlockingEventMessage(emm_payload.service_blocking(i));
|
||||
if (message.empty()) {
|
||||
continue;
|
||||
}
|
||||
if (last_service_blocking_events_.find(message) ==
|
||||
last_service_blocking_events_.end()) {
|
||||
event_listener_->OnServiceBlockingUpdated(message);
|
||||
}
|
||||
current_service_blocking_events_.insert(message);
|
||||
}
|
||||
last_service_blocking_events_.clear();
|
||||
last_service_blocking_events_.insert(current_service_blocking_events_.begin(),
|
||||
current_service_blocking_events_.end());
|
||||
|
||||
return CasStatusCode::kNoError;
|
||||
}
|
||||
|
||||
// TODO(jfore): Add unit test to widevine_cas_api_test.cpp that is added in
|
||||
// another cl.
|
||||
CasStatus WidevineCas::processEcm(const WvCasSessionId& sessionId,
|
||||
const CasEcm& ecm) {
|
||||
LOGD("WidevineCasPlugin::processEcm");
|
||||
std::unique_lock<std::mutex> locker(lock_);
|
||||
// If we don't have a license yet, save the ecm and session id.
|
||||
if (!has_license_) {
|
||||
// In the case of entitlement key rotation enabled, the caller is expected
|
||||
// to call processEcm first (before processPrivateData), so we know which
|
||||
// entitlement period index to request when requesting license.
|
||||
TryExtractEntitlementPeriodIndex(ecm);
|
||||
deferred_ecms_.emplace(sessionId, ecm);
|
||||
return CasStatusCode::kDeferedEcmProcessing;
|
||||
}
|
||||
return HandleProcessEcm(sessionId, ecm);
|
||||
}
|
||||
|
||||
CasStatus WidevineCas::HandleProcessEcm(const WvCasSessionId& sessionId,
|
||||
const CasEcm& ecm) {
|
||||
if (cas_license_->IsExpired()) {
|
||||
return CasStatus(CasStatusCode::kCasLicenseError,
|
||||
"license is expired, unable to process ecm");
|
||||
}
|
||||
CasSessionPtr session =
|
||||
WidevineCasSessionMap::instance().GetSession(sessionId);
|
||||
if (session == nullptr) {
|
||||
return CasStatus(CasStatusCode::kSessionNotFound,
|
||||
"unknown session for processEcm");
|
||||
}
|
||||
uint8_t ecm_age_previous = session->GetEcmAgeRestriction();
|
||||
|
||||
CasStatus status =
|
||||
session->processEcm(ecm, parental_control_age_, license_group_id_);
|
||||
uint8_t ecm_age_current = session->GetEcmAgeRestriction();
|
||||
if (event_listener_ != nullptr && ecm_age_current != ecm_age_previous) {
|
||||
event_listener_->OnAgeRestrictionUpdated(sessionId, ecm_age_current);
|
||||
}
|
||||
|
||||
if (media_id_ != nullptr && media_id_->is_entitlement_rotation_enabled()) {
|
||||
CheckEntitlementPeriodUpdate(session->GetEntitlementPeriodIndex(),
|
||||
session->GetEntitlementRotationWindowLeft());
|
||||
}
|
||||
|
||||
if (status.ok()) {
|
||||
cas_license_->BeginDecryption();
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
CasStatus WidevineCas::HandleDeferredECMs() {
|
||||
for (const auto& deferred_ecm : deferred_ecms_) {
|
||||
CasStatus status =
|
||||
HandleProcessEcm(deferred_ecm.first, deferred_ecm.second);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
deferred_ecms_.clear();
|
||||
return CasStatusCode::kNoError;
|
||||
}
|
||||
|
||||
CasStatus WidevineCas::generateDeviceProvisioningRequest(
|
||||
std::string* provisioning_request) {
|
||||
std::unique_lock<std::mutex> 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<std::mutex> locker(lock_);
|
||||
CasStatus status = cas_license_->HandleDeviceProvisioningResponse(
|
||||
response, &device_certificate_, &wrapped_rsa_key_, &device_file);
|
||||
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
if (!device_file.empty()) {
|
||||
std::string cert_filename(std::string(kBasePathPrefix) +
|
||||
std::string(kCertFileBase));
|
||||
StoreFile(*file_system_, cert_filename, device_file);
|
||||
}
|
||||
return CasStatusCode::kNoError;
|
||||
}
|
||||
|
||||
CasStatus WidevineCas::generateEntitlementRequest(
|
||||
const std::string& init_data, std::string* entitlement_request,
|
||||
std::string& license_id) {
|
||||
media_id_ = CasMediaId::create();
|
||||
CasStatus status = media_id_->initialize(init_data);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
std::string filename;
|
||||
// Backward compatible. If the license_filename is unrequested by app, plugin
|
||||
// will directly use the single_content_license named "content_id +
|
||||
// provider_id" by default.
|
||||
if (requested_license_id_.empty()) {
|
||||
filename = GenerateLicenseFilename(media_id_->content_id(),
|
||||
media_id_->provider_id());
|
||||
} else {
|
||||
filename = requested_license_id_ + kLicenseFileNameSuffix;
|
||||
// Clean up the assigned_license_filename for next round use.
|
||||
requested_license_id_.clear();
|
||||
}
|
||||
|
||||
// An offline license file is successfully loaded.
|
||||
if (TryReuseStoredLicense(filename)) {
|
||||
// If license file is expired, don't proceed the request. Also
|
||||
// delete the stored license file.
|
||||
std::unique_lock<std::mutex> locker(lock_);
|
||||
if (cas_license_->IsExpired()) {
|
||||
if (!RemoveFile(*file_system_, filename)) {
|
||||
return CasStatus(CasStatusCode::kInvalidLicenseFile,
|
||||
"unable to remove expired license file from disk");
|
||||
}
|
||||
LOGI("Remove expired license file from disk successfully.");
|
||||
return CasStatus(CasStatusCode::kCasLicenseError,
|
||||
"license is expired, unable to process emm");
|
||||
}
|
||||
license_id =
|
||||
filename.substr(0, filename.size() - strlen(kLicenseFileNameSuffix));
|
||||
if (cas_license_->IsGroupLicense()) {
|
||||
license_group_id_ = cas_license_->GetGroupId();
|
||||
}
|
||||
|
||||
// Save current in use license_id. The purpose is to make the license_id
|
||||
// available for license removal or license expiration.
|
||||
license_id_ = license_id;
|
||||
policy_timer_.Start(this, 1);
|
||||
has_license_ = true;
|
||||
return HandleDeferredECMs();
|
||||
}
|
||||
|
||||
if (entitlement_request == nullptr) {
|
||||
return CasStatus(CasStatusCode::kInvalidParameter,
|
||||
"missing output buffer for entitlement request");
|
||||
}
|
||||
std::unique_lock<std::mutex> locker(lock_);
|
||||
return cas_license_->GenerateEntitlementRequest(
|
||||
init_data, device_certificate_, wrapped_rsa_key_, license_type_,
|
||||
entitlement_request);
|
||||
}
|
||||
|
||||
CasStatus WidevineCas::handleEntitlementResponse(
|
||||
const std::string& response, std::string& license_id,
|
||||
std::string& multi_content_license_info, std::string& group_license_info) {
|
||||
if (response.empty()) {
|
||||
return CasStatus(CasStatusCode::kCasLicenseError,
|
||||
"empty entitlement response");
|
||||
}
|
||||
if (media_id_ == nullptr) {
|
||||
return CasStatus(CasStatusCode::kCasLicenseError, "No media id");
|
||||
}
|
||||
|
||||
std::string device_file;
|
||||
std::unique_lock<std::mutex> locker(lock_);
|
||||
CasStatus status =
|
||||
cas_license_->HandleEntitlementResponse(response, &device_file);
|
||||
if (status.ok()) {
|
||||
// A license has been successfully loaded. Load any ecms that may have been
|
||||
// deferred waiting for the license.
|
||||
if (cas_license_->IsGroupLicense()) {
|
||||
license_group_id_ = cas_license_->GetGroupId();
|
||||
}
|
||||
has_license_ = true;
|
||||
|
||||
status = HandleDeferredECMs();
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
policy_timer_.Start(this, 1);
|
||||
|
||||
if (!device_file.empty()) {
|
||||
const std::string license_group_id = cas_license_->GetGroupId();
|
||||
std::string filename = GenerateLicenseFilename(
|
||||
license_group_id.empty() ? media_id_->content_id() : license_group_id,
|
||||
media_id_->provider_id());
|
||||
StoreFile(*file_system_, filename, device_file);
|
||||
// license_id will be the filename without ".lic" extension.
|
||||
license_id =
|
||||
filename.substr(0, filename.size() - strlen(kLicenseFileNameSuffix));
|
||||
// Save the license id.
|
||||
license_id_ = license_id;
|
||||
|
||||
// License info is only needed if the license is stored.
|
||||
if (cas_license_->IsMultiContentLicense()) {
|
||||
multi_content_license_info = GenerateMultiContentLicenseInfo(
|
||||
license_id, cas_license_->GetContentIdList());
|
||||
}
|
||||
if (cas_license_->IsGroupLicense()) {
|
||||
group_license_info =
|
||||
GenerateGroupLicenseInfo(license_id, license_group_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
CasStatus WidevineCas::generateEntitlementRenewalRequest(
|
||||
std::string* entitlement_renewal_request) {
|
||||
if (entitlement_renewal_request == nullptr) {
|
||||
return CasStatus(CasStatusCode::kInvalidParameter,
|
||||
"missing output buffer for entitlement renewal request");
|
||||
}
|
||||
return cas_license_->GenerateEntitlementRenewalRequest(
|
||||
device_certificate_, entitlement_renewal_request);
|
||||
}
|
||||
|
||||
CasStatus WidevineCas::handleEntitlementRenewalResponse(
|
||||
const std::string& response, std::string& license_id) {
|
||||
if (response.empty()) {
|
||||
return CasStatus(CasStatusCode::kCasLicenseError,
|
||||
"empty entitlement renewal response");
|
||||
}
|
||||
std::string device_file;
|
||||
std::unique_lock<std::mutex> locker(lock_);
|
||||
CasStatus status =
|
||||
cas_license_->HandleEntitlementRenewalResponse(response, &device_file);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
if (!device_file.empty() && media_id_ != nullptr) {
|
||||
const std::string license_group_id = cas_license_->GetGroupId();
|
||||
std::string filename = GenerateLicenseFilename(
|
||||
license_group_id.empty() ? media_id_->content_id() : license_group_id,
|
||||
media_id_->provider_id());
|
||||
StoreFile(*file_system_, filename, device_file);
|
||||
// TODO(chelu): The license id should not change, right?
|
||||
// license_id will be the filename without ".lic" extension.
|
||||
license_id =
|
||||
filename.substr(0, filename.size() - strlen(kLicenseFileNameSuffix));
|
||||
license_id_ = license_id;
|
||||
}
|
||||
return CasStatusCode::kNoError;
|
||||
}
|
||||
|
||||
CasStatus WidevineCas::generateEntitlementPeriodUpdateRequest(
|
||||
const std::string& init_data) {
|
||||
std::unique_lock<std::mutex> locker(lock_);
|
||||
next_media_id_ = CasMediaId::create();
|
||||
CasStatus status = next_media_id_->initialize(init_data);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
// Setup a new OEMCrypto session.
|
||||
next_crypto_session_ = getCryptoSession();
|
||||
status = next_crypto_session_->initialize();
|
||||
if (!status.ok()) {
|
||||
LOGE("WidevineCas new oemcrypto session failed: %d", status.status_code());
|
||||
return status;
|
||||
}
|
||||
// Setup a new CasLicense.
|
||||
next_cas_license_ = getCasLicense();
|
||||
status = next_cas_license_->initialize(next_crypto_session_, event_listener_);
|
||||
if (!status.ok()) {
|
||||
LOGE("WidevineCas new license initialize failed: %d", status.status_code());
|
||||
return status;
|
||||
}
|
||||
|
||||
std::string entitlement_request;
|
||||
status = next_cas_license_->GenerateEntitlementRequest(
|
||||
init_data, device_certificate_, wrapped_rsa_key_, license_type_,
|
||||
&entitlement_request);
|
||||
if (!status.ok()) {
|
||||
LOGE("WidevineCas generate entitlement request failed: %d",
|
||||
status.status_code());
|
||||
return status;
|
||||
}
|
||||
|
||||
if (event_listener_ == nullptr) {
|
||||
LOGE("No event listener");
|
||||
return CasStatus(CasStatusCode::kUnknownError, "No event listener");
|
||||
}
|
||||
event_listener_->OnEntitlementPeriodUpdateNeeded(entitlement_request);
|
||||
return CasStatusCode::kNoError;
|
||||
}
|
||||
|
||||
CasStatus WidevineCas::handleEntitlementPeriodUpdateResponse(
|
||||
const std::string& response, std::string& license_id) {
|
||||
std::unique_lock<std::mutex> locker(lock_);
|
||||
if (next_media_id_ == nullptr || next_crypto_session_ == nullptr ||
|
||||
next_cas_license_ == nullptr) {
|
||||
return CasStatus(CasStatusCode::kInvalidParameter,
|
||||
"Must generate entitlement switch request first.");
|
||||
}
|
||||
// Install the new license.
|
||||
std::string device_file;
|
||||
CasStatus status =
|
||||
next_cas_license_->HandleEntitlementResponse(response, &device_file);
|
||||
if (!status.ok()) {
|
||||
LOGE("WidevineCas install new license failed: %d", status.status_code());
|
||||
return status;
|
||||
}
|
||||
// License has been successfully installed. Switch to use it across all
|
||||
// sessions.
|
||||
for (const auto& session :
|
||||
WidevineCasSessionMap::instance().GetAllSessions()) {
|
||||
status = session->resetCryptoSession(next_crypto_session_);
|
||||
if (!status.ok()) {
|
||||
// Some of the sessions may have already been reassociated (unlikely to
|
||||
// happen). Here we continue process ignoring the errors. Some sessions
|
||||
// will become unusable.
|
||||
LOGE("resetCryptoSession failed, error %d: %s", status.status_code(),
|
||||
status.error_string().c_str());
|
||||
}
|
||||
}
|
||||
// Close the current OEMCrypto session.
|
||||
crypto_session_->close();
|
||||
// Apply the new crypto session and cas license.
|
||||
crypto_session_ = std::move(next_crypto_session_);
|
||||
cas_license_ = std::move(next_cas_license_);
|
||||
media_id_ = std::move(next_media_id_);
|
||||
|
||||
// Store offline license.
|
||||
if (!device_file.empty()) {
|
||||
std::string filename = GenerateLicenseFilename(media_id_->content_id(),
|
||||
media_id_->provider_id());
|
||||
StoreFile(*file_system_, filename, device_file);
|
||||
// license_id will be the filename without ".lic" extension.
|
||||
license_id =
|
||||
filename.substr(0, filename.size() - strlen(kLicenseFileNameSuffix));
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
CasStatus WidevineCas::RemoveLicense(const std::string& file_name) {
|
||||
// Check if the license is in use. If it is, besides removing the license,
|
||||
// update policy in current license. Else, we just directly remove it.
|
||||
if (media_id_ == nullptr) {
|
||||
return CasStatus(CasStatusCode::kCasLicenseError, "No media id");
|
||||
}
|
||||
// Remove the license file given the file_name user provides.
|
||||
if (!RemoveFile(*file_system_, file_name)) {
|
||||
return CasStatus(CasStatusCode::kInvalidLicenseFile,
|
||||
"unable to remove license file from disk");
|
||||
}
|
||||
LOGI("Remove license file from disk successfully.");
|
||||
|
||||
std::string current_license_filename = license_id_ + kLicenseFileNameSuffix;
|
||||
if (file_name == current_license_filename) {
|
||||
// Update license policy for the in-used license. Plugin will not allowed to
|
||||
// play stream, store and renew license unless a new plugin instance is
|
||||
// created.
|
||||
std::unique_lock<std::mutex> locker(lock_);
|
||||
cas_license_->UpdateLicenseForLicenseRemove();
|
||||
}
|
||||
|
||||
return CasStatusCode::kNoError;
|
||||
}
|
||||
|
||||
bool WidevineCas::is_provisioned() const {
|
||||
return (!(device_certificate_.empty() || wrapped_rsa_key_.empty()));
|
||||
}
|
||||
|
||||
CasStatus WidevineCas::ProcessCAPrivateData(const CasData& private_data,
|
||||
std::string* init_data) {
|
||||
if (init_data == nullptr) {
|
||||
return CasStatus(CasStatusCode::kInvalidParameter,
|
||||
"missing output buffer for init_data");
|
||||
}
|
||||
// Parse provider and content id from CA descriptor.
|
||||
video_widevine::CaDescriptorPrivateData descriptor;
|
||||
descriptor.ParseFromArray(private_data.data(), private_data.size());
|
||||
if (!descriptor.has_content_id() || !descriptor.has_provider()) {
|
||||
return CasStatus(CasStatusCode::kInvalidParameter,
|
||||
"unable to parse private data");
|
||||
}
|
||||
|
||||
// Build PSSH of type ENTITLEMENT.
|
||||
video_widevine::WidevinePsshData pssh;
|
||||
pssh.set_provider(descriptor.provider());
|
||||
pssh.set_content_id(descriptor.content_id());
|
||||
for (int i = 0; i < descriptor.group_ids_size(); ++i) {
|
||||
pssh.add_group_ids(descriptor.group_ids(i));
|
||||
}
|
||||
pssh.set_type(video_widevine::WidevinePsshData::ENTITLEMENT);
|
||||
if (is_entitlement_rotation_enabled_) {
|
||||
pssh.set_entitlement_period_index(entitlement_period_index_);
|
||||
}
|
||||
pssh.SerializeToString(init_data);
|
||||
return CasStatusCode::kNoError;
|
||||
}
|
||||
|
||||
CasStatus WidevineCas::ProcessSessionCAPrivateData(
|
||||
const WvCasSessionId& session_id, const CasData& private_data,
|
||||
std::string* init_data) {
|
||||
if (!WidevineCasSessionMap::instance().GetSession(session_id)) {
|
||||
return CasStatus(CasStatusCode::kCasLicenseError, "invalid session id");
|
||||
}
|
||||
return ProcessCAPrivateData(private_data, init_data);
|
||||
}
|
||||
|
||||
CasStatus WidevineCas::GetUniqueID(std::string* buffer) {
|
||||
return crypto_session_->GetDeviceID(buffer);
|
||||
}
|
||||
|
||||
CasStatus WidevineCas::HandleStoredDrmCert(const std::string& certificate) {
|
||||
if (certificate.empty()) {
|
||||
return CasStatus(CasStatusCode::kCasLicenseError, "empty certificate data");
|
||||
}
|
||||
CasStatus status = cas_license_->HandleStoredDrmCert(
|
||||
certificate, &device_certificate_, &wrapped_rsa_key_);
|
||||
return status;
|
||||
}
|
||||
|
||||
CasStatus WidevineCas::HandleSetParentalControlAge(const CasData& data) {
|
||||
if (data.empty()) {
|
||||
return CasStatus(CasStatusCode::kCasLicenseError,
|
||||
"missing value of parental control min age");
|
||||
}
|
||||
parental_control_age_ = data[0];
|
||||
LOGI("Parental control age set to: %d", parental_control_age_);
|
||||
return CasStatusCode::kNoError;
|
||||
}
|
||||
|
||||
CasStatus WidevineCas::RecordLicenseId(const std::string& license_id) {
|
||||
requested_license_id_ = license_id;
|
||||
LOGI("License id selected is: %s", requested_license_id_.c_str());
|
||||
return CasStatusCode::kNoError;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> WidevineCas::GenerateFingerprintingEventMessage(
|
||||
const video_widevine::Fingerprinting& fingerprinting) const {
|
||||
std::vector<uint8_t> message;
|
||||
for (int i = 0; i < fingerprinting.channels_size(); ++i) {
|
||||
const std::string& channel = fingerprinting.channels(i);
|
||||
message.push_back(
|
||||
static_cast<uint8_t>(FingerprintingFieldType::FINGERPRINTING_CHANNEL));
|
||||
message.push_back((channel.size() >> 8) & 0xff);
|
||||
message.push_back(channel.size() & 0xff);
|
||||
message.insert(message.end(), channel.begin(), channel.end());
|
||||
}
|
||||
|
||||
if (fingerprinting.has_control()) {
|
||||
message.push_back(
|
||||
static_cast<uint8_t>(FingerprintingFieldType::FINGERPRINTING_CONTROL));
|
||||
const std::string& control = fingerprinting.control();
|
||||
message.push_back((control.size() >> 8) & 0xff);
|
||||
message.push_back(control.size() & 0xff);
|
||||
message.insert(message.end(), control.begin(), control.end());
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> WidevineCas::GenerateServiceBlockingEventMessage(
|
||||
const video_widevine::ServiceBlocking& service_blocking) const {
|
||||
std::vector<uint8_t> message;
|
||||
// Process service blocking channels.
|
||||
for (int i = 0; i < service_blocking.channels_size(); ++i) {
|
||||
const std::string& channel = service_blocking.channels(i);
|
||||
message.push_back(static_cast<uint8_t>(
|
||||
ServiceBlockingFieldType::SERVICE_BLOCKING_CHANNEL));
|
||||
message.push_back((channel.size() >> 8) & 0xff);
|
||||
message.push_back(channel.size() & 0xff);
|
||||
message.insert(message.end(), channel.begin(), channel.end());
|
||||
}
|
||||
|
||||
// Process service blocking device_groups.
|
||||
for (int i = 0; i < service_blocking.device_groups_size(); ++i) {
|
||||
const std::string& device_group = service_blocking.device_groups(i);
|
||||
message.push_back(static_cast<uint8_t>(
|
||||
ServiceBlockingFieldType::SERVICE_BLOCKING_DEVICE_GROUP));
|
||||
message.push_back((device_group.size() >> 8) & 0xff);
|
||||
message.push_back(device_group.size() & 0xff);
|
||||
message.insert(message.end(), device_group.begin(), device_group.end());
|
||||
}
|
||||
|
||||
// Process service blocking start_time_sec.
|
||||
if (service_blocking.has_start_time_sec()) {
|
||||
message.push_back(static_cast<uint8_t>(
|
||||
ServiceBlockingFieldType::SERVICE_BLOCKING_START_TIME_SECONDS));
|
||||
// Timestamp is always 8 bytes (64 bits).
|
||||
message.push_back(0);
|
||||
message.push_back(8);
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
message.push_back((service_blocking.start_time_sec() >> (8 * (7 - i))) &
|
||||
0xff);
|
||||
}
|
||||
}
|
||||
|
||||
// Process service blocking end_time_sec.
|
||||
if (service_blocking.has_end_time_sec()) {
|
||||
message.push_back(static_cast<uint8_t>(
|
||||
ServiceBlockingFieldType::SERVICE_BLOCKING_END_TIME_SECONDS));
|
||||
// Timestamp is always 8 bytes (64 bits).
|
||||
message.push_back(0);
|
||||
message.push_back(8);
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
message.push_back((service_blocking.end_time_sec() >> (8 * (7 - i))) &
|
||||
0xff);
|
||||
}
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
std::unique_ptr<const EmmParser> WidevineCas::getEmmParser(
|
||||
const CasEmm& emm) const {
|
||||
return EmmParser::Create(emm);
|
||||
}
|
||||
|
||||
void WidevineCas::TryExtractEntitlementPeriodIndex(const CasEcm& ecm) {
|
||||
std::unique_ptr<EcmParser> ecm_parser = getEcmParser(ecm);
|
||||
if (ecm_parser == nullptr) {
|
||||
LOGE("ECM parser failed for extracting entitlement period index");
|
||||
return;
|
||||
}
|
||||
if (ecm_parser->is_entitlement_rotation_enabled()) {
|
||||
is_entitlement_rotation_enabled_ = true;
|
||||
entitlement_period_index_ = ecm_parser->entitlement_period_index();
|
||||
LOGI("Entitlement key rotation enabled. Current index: %d",
|
||||
entitlement_period_index_);
|
||||
}
|
||||
}
|
||||
|
||||
bool WidevineCas::TryReuseStoredLicense(const std::string& filename) {
|
||||
// Read the file with |filename| from the file system.
|
||||
std::string license_file;
|
||||
if (!ReadFileFromStorage(*file_system_, filename, &license_file)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If entitlement rotation is enabled, check if the entitlement period in the
|
||||
// license is outdated.
|
||||
if (media_id_->is_entitlement_rotation_enabled()) {
|
||||
uint32_t stored_index;
|
||||
CasStatus status = CasLicense::GetEntitlementPeriodIndexFromStoredLicense(
|
||||
license_file, stored_index);
|
||||
if (!status.ok()) {
|
||||
LOGW(
|
||||
"Failed to retrieve entitlement period index from stored license. "
|
||||
"code: %d, message: %s",
|
||||
status.status_code(), status.error_string().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (media_id_->entitlement_period_index() != stored_index) {
|
||||
LOGI("Stored license has mismatch entitlement period index.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Load the stored license to the session.
|
||||
CasStatus status =
|
||||
cas_license_->HandleStoredLicense(wrapped_rsa_key_, license_file);
|
||||
if (!status.ok()) {
|
||||
LOGW("Failed to load stored license. code: %d, message: %s",
|
||||
status.status_code(), status.error_string().c_str());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void WidevineCas::CheckEntitlementPeriodUpdate(uint32_t period_index,
|
||||
uint32_t window_left) {
|
||||
if (period_index == media_id_->entitlement_period_index()) {
|
||||
return;
|
||||
}
|
||||
// If the index changed unexpectedly, we request a new license immediately. If
|
||||
// it is increased by 1, we decide if a new license should be generated based
|
||||
// on |window_left|.
|
||||
if (period_index != media_id_->entitlement_period_index() + 1 ||
|
||||
GetRandom(window_left) == 1) {
|
||||
video_widevine::WidevinePsshData pssh;
|
||||
if (!pssh.ParseFromString(media_id_->get_init_data())) {
|
||||
LOGE("Cannot parse init data");
|
||||
return;
|
||||
}
|
||||
pssh.set_entitlement_period_index(period_index);
|
||||
generateEntitlementPeriodUpdateRequest(pssh.SerializeAsString());
|
||||
}
|
||||
}
|
||||
|
||||
void WidevineCas::StopTimer() {
|
||||
if (policy_timer_.IsRunning()) {
|
||||
policy_timer_.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace wvcas
|
||||
202
plugin/src/widevine_cas_session.cpp
Normal file
202
plugin/src/widevine_cas_session.cpp
Normal file
@@ -0,0 +1,202 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
#include "widevine_cas_session.h"
|
||||
|
||||
#include <cas_events.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
#include "log.h"
|
||||
#include "media_cas.pb.h"
|
||||
|
||||
namespace wvcas {
|
||||
|
||||
KeySlot& CasKeySlotData::operator[](KeySlotId slot_id) {
|
||||
return keys_[static_cast<int>(slot_id)];
|
||||
}
|
||||
|
||||
const KeySlot& CasKeySlotData::operator[](KeySlotId slot_id) const {
|
||||
return keys_[static_cast<int>(slot_id)];
|
||||
}
|
||||
|
||||
WidevineCasSession::~WidevineCasSession() {
|
||||
if (crypto_session_ != nullptr) {
|
||||
crypto_session_->RemoveEntitledKeySession(key_session_id_);
|
||||
}
|
||||
}
|
||||
|
||||
CasStatus WidevineCasSession::initialize(
|
||||
std::shared_ptr<CryptoSession> crypto_session,
|
||||
CasEventListener* event_listener, WvCasSessionId* session_id) {
|
||||
std::unique_lock<std::mutex> lock(crypto_lock_);
|
||||
if (crypto_session == nullptr) {
|
||||
LOGE(
|
||||
"WidevineCasSession::initialize: missing input parameter "
|
||||
"crypto_session");
|
||||
return CasStatus(CasStatusCode::kInvalidParameter,
|
||||
"missing input parameter crypto_session");
|
||||
}
|
||||
crypto_session_ = std::move(crypto_session);
|
||||
CasStatus status =
|
||||
crypto_session_->CreateEntitledKeySession(&key_session_id_);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
status = crypto_session_->GetOEMKeyToken(key_session_id_,
|
||||
external_key_session_id_);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
session_id->assign(external_key_session_id_.begin(),
|
||||
external_key_session_id_.end());
|
||||
|
||||
event_listener_ = event_listener;
|
||||
return CasStatusCode::kNoError;
|
||||
}
|
||||
|
||||
CasStatus WidevineCasSession::resetCryptoSession(
|
||||
std::shared_ptr<CryptoSession> crypto_session) {
|
||||
std::unique_lock<std::mutex> lock(crypto_lock_);
|
||||
if (crypto_session == nullptr) {
|
||||
return CasStatus(CasStatusCode::kInvalidParameter,
|
||||
"Can not reset crypto session to null");
|
||||
}
|
||||
crypto_session_ = std::move(crypto_session);
|
||||
return crypto_session_->ReassociateEntitledKeySession(key_session_id_);
|
||||
}
|
||||
|
||||
CasStatus WidevineCasSession::processEcm(const CasEcm& ecm,
|
||||
uint8_t parental_control_age,
|
||||
const std::string& license_group_id) {
|
||||
std::unique_lock<std::mutex> lock(crypto_lock_);
|
||||
if (ecm != current_ecm_) {
|
||||
LOGD("WidevineCasSession::processEcm: received new ecm");
|
||||
std::unique_ptr<EcmParser> ecm_parser = getEcmParser(ecm);
|
||||
if (ecm_parser == nullptr) {
|
||||
return CasStatus(CasStatusCode::kInvalidParameter, "invalid ecm");
|
||||
}
|
||||
if (!license_group_id.empty() &&
|
||||
!ecm_parser->set_group_id(license_group_id)) {
|
||||
return CasStatus(CasStatusCode::kInvalidParameter, "invalid group id");
|
||||
}
|
||||
|
||||
ecm_age_restriction_ = ecm_parser->age_restriction();
|
||||
// Parental control check.
|
||||
if (parental_control_age > 0 &&
|
||||
parental_control_age < ecm_age_restriction_) {
|
||||
const std::string message(1, parental_control_age);
|
||||
return CasStatus(CasStatusCode::kAccessDeniedByParentalControl, message);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> message;
|
||||
if (!ecm_parser->fingerprinting().control().empty()) {
|
||||
message.push_back(static_cast<uint8_t>(
|
||||
SessionFingerprintingFieldType::SESSION_FINGERPRINTING_CONTROL));
|
||||
const std::string control = ecm_parser->fingerprinting().control();
|
||||
message.push_back((control.size() >> 8) & 0xff);
|
||||
message.push_back(control.size() & 0xff);
|
||||
message.insert(message.end(), control.begin(), control.end());
|
||||
}
|
||||
if (message != last_fingerprinting_message_) {
|
||||
last_fingerprinting_message_ = message;
|
||||
if (event_listener_ == nullptr) {
|
||||
LOGW("event_listener is null. Fingerprinting info ignored!");
|
||||
} else {
|
||||
event_listener_->OnSessionFingerprintingUpdated(
|
||||
external_key_session_id_, message);
|
||||
}
|
||||
}
|
||||
|
||||
message.clear();
|
||||
for (int i = 0; i < ecm_parser->service_blocking().device_groups_size();
|
||||
++i) {
|
||||
message.push_back(
|
||||
static_cast<uint8_t>(SessionServiceBlockingFieldType::
|
||||
SESSION_SERVICE_BLOCKING_DEVICE_GROUP));
|
||||
const std::string device_group =
|
||||
ecm_parser->service_blocking().device_groups(i);
|
||||
message.push_back((device_group.size() >> 8) & 0xff);
|
||||
message.push_back(device_group.size() & 0xff);
|
||||
message.insert(message.end(), device_group.begin(), device_group.end());
|
||||
}
|
||||
if (message != last_service_blocking_message_) {
|
||||
last_service_blocking_message_ = message;
|
||||
if (event_listener_ == nullptr) {
|
||||
LOGW("event_listener is null. Service blocking info ignored!");
|
||||
} else {
|
||||
event_listener_->OnSessionServiceBlockingUpdated(
|
||||
external_key_session_id_, message);
|
||||
}
|
||||
}
|
||||
|
||||
entitlement_period_index_ = ecm_parser->entitlement_period_index();
|
||||
entitlement_rotation_window_left_ =
|
||||
ecm_parser->entitlement_rotation_window_left();
|
||||
|
||||
bool load_even = false;
|
||||
bool load_odd = false;
|
||||
KeySlotId keyslot_id = KeySlotId::kEvenKeySlot;
|
||||
// Temporary key slots to only have successfully loaded keys in |keys_|.
|
||||
CasKeySlotData keys;
|
||||
do {
|
||||
if (keys_[keyslot_id].wrapped_key !=
|
||||
ecm_parser->wrapped_key_data(keyslot_id)) {
|
||||
KeySlot& key = keys[keyslot_id];
|
||||
key.key_id = ecm_parser->content_key_id(keyslot_id);
|
||||
key.wrapped_key = ecm_parser->wrapped_key_data(keyslot_id);
|
||||
key.wrapped_key_iv = ecm_parser->wrapped_key_iv(keyslot_id);
|
||||
key.entitlement_key_id = ecm_parser->entitlement_key_id(keyslot_id);
|
||||
key.cipher_mode = ecm_parser->crypto_mode();
|
||||
key.content_iv = ecm_parser->content_iv(keyslot_id);
|
||||
if (keyslot_id == KeySlotId::kEvenKeySlot) {
|
||||
load_even = true;
|
||||
} else {
|
||||
load_odd = true;
|
||||
}
|
||||
if (key.content_iv.size() == 8) {
|
||||
key.content_iv.resize(16, 0);
|
||||
}
|
||||
}
|
||||
if (!ecm_parser->rotation_enabled() ||
|
||||
keyslot_id == KeySlotId::kOddKeySlot) {
|
||||
break;
|
||||
}
|
||||
keyslot_id = KeySlotId::kOddKeySlot;
|
||||
} while (true);
|
||||
if (load_even || load_odd) {
|
||||
CasStatus status = crypto_session_->LoadCasECMKeys(
|
||||
key_session_id_,
|
||||
(load_even ? &keys[KeySlotId::kEvenKeySlot] : nullptr),
|
||||
(load_odd ? &keys[KeySlotId::kOddKeySlot] : nullptr));
|
||||
if (status.status_code() != CasStatusCode::kNoError) {
|
||||
LOGE("WidevineCasSession::processEcm: error %d, msg %s",
|
||||
status.status_code(), status.error_string().c_str());
|
||||
return status;
|
||||
}
|
||||
|
||||
// Don't update on failure, to not to lose still working key_id.
|
||||
if (load_even) {
|
||||
keys_[KeySlotId::kEvenKeySlot] = keys[KeySlotId::kEvenKeySlot];
|
||||
}
|
||||
if (load_odd) {
|
||||
keys_[KeySlotId::kOddKeySlot] = keys[KeySlotId::kOddKeySlot];
|
||||
}
|
||||
}
|
||||
current_ecm_ = ecm;
|
||||
}
|
||||
return CasStatusCode::kNoError;
|
||||
}
|
||||
|
||||
std::unique_ptr<EcmParser> WidevineCasSession::getEcmParser(
|
||||
const CasEcm& ecm) const {
|
||||
return EcmParser::Create(ecm);
|
||||
}
|
||||
|
||||
const char* WidevineCasSession::securityLevel() {
|
||||
return crypto_session_->SecurityLevel();
|
||||
}
|
||||
|
||||
} // namespace wvcas
|
||||
47
plugin/src/widevine_cas_session_map.cpp
Normal file
47
plugin/src/widevine_cas_session_map.cpp
Normal file
@@ -0,0 +1,47 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#include "widevine_cas_session_map.h"
|
||||
|
||||
namespace wvcas {
|
||||
|
||||
WidevineCasSessionMap& WidevineCasSessionMap::instance() {
|
||||
static WidevineCasSessionMap the_cas_session_map;
|
||||
return the_cas_session_map;
|
||||
}
|
||||
|
||||
bool WidevineCasSessionMap::AddSession(const WvCasSessionId& cas_session_id,
|
||||
CasSessionPtr session) {
|
||||
std::unique_lock<std::mutex> lock(lock_);
|
||||
std::pair<CasSessionMap::iterator, bool> entry =
|
||||
map_.emplace(cas_session_id, session);
|
||||
return entry.second;
|
||||
}
|
||||
|
||||
CasSessionPtr WidevineCasSessionMap::GetSession(
|
||||
const WvCasSessionId& cas_session_id) const {
|
||||
std::unique_lock<std::mutex> lock(lock_);
|
||||
CasSessionMap::const_iterator it = map_.find(cas_session_id);
|
||||
if (it == map_.end()) {
|
||||
return CasSessionPtr(nullptr);
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
void WidevineCasSessionMap::RemoveSession(
|
||||
const WvCasSessionId& cas_session_id) {
|
||||
std::unique_lock<std::mutex> lock(lock_);
|
||||
map_.erase(cas_session_id);
|
||||
}
|
||||
|
||||
std::vector<CasSessionPtr> WidevineCasSessionMap::GetAllSessions() const {
|
||||
std::unique_lock<std::mutex> lock(lock_);
|
||||
std::vector<CasSessionPtr> sessions;
|
||||
for (const auto& session : map_) {
|
||||
sessions.push_back(session.second);
|
||||
}
|
||||
return sessions;
|
||||
}
|
||||
|
||||
} // namespace wvcas
|
||||
95
plugin/src/widevine_media_cas.cpp
Normal file
95
plugin/src/widevine_media_cas.cpp
Normal file
@@ -0,0 +1,95 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#include "widevine_media_cas.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "cas_util.h"
|
||||
#include "crypto_session.h"
|
||||
#include "utils/Errors.h"
|
||||
#include "widevine_media_cas_plugin.h"
|
||||
|
||||
using android::BAD_VALUE;
|
||||
using android::OK;
|
||||
|
||||
// Widevine Technologies CA system ID.
|
||||
static constexpr int32_t kWidevineCAID = 0x4AD4;
|
||||
// New Widevine CAS IDs 0x56C0 to 0x56C9 (all inclusive).
|
||||
static constexpr int32_t kWidevineNewCasIdLowerBound = 0x56C0;
|
||||
static constexpr int32_t kWidevineNewCasIdUpperBound = 0x56C9;
|
||||
// Total number of supported Widevine CAS ids.
|
||||
static constexpr size_t kWidevineCasIdCount = 11;
|
||||
static constexpr char kName[] = "WidevineCas";
|
||||
|
||||
// Implements extern android::CasFactory *createCasFactory() entry point.
|
||||
CasFactory* createCasFactory() {
|
||||
return wvcas::WidevineCasFactory::createCasFactory();
|
||||
}
|
||||
|
||||
namespace wvcas {
|
||||
|
||||
/************************ Cas factory implementation *********************/
|
||||
|
||||
// The widevine cas implementation of the cas plugin factory.
|
||||
WidevineCasFactory* WidevineCasFactory::createCasFactory() {
|
||||
return new WidevineCasFactory();
|
||||
}
|
||||
|
||||
bool WidevineCasFactory::isSystemIdSupported(int32_t CA_system_id) const {
|
||||
return (CA_system_id == kWidevineCAID) ||
|
||||
(CA_system_id >= kWidevineNewCasIdLowerBound &&
|
||||
CA_system_id <= kWidevineNewCasIdUpperBound);
|
||||
}
|
||||
|
||||
status_t WidevineCasFactory::queryPlugins(
|
||||
std::vector<CasPluginDescriptor>* descriptors) const {
|
||||
if (nullptr == descriptors) {
|
||||
return BAD_VALUE;
|
||||
}
|
||||
descriptors->clear();
|
||||
descriptors->reserve(kWidevineCasIdCount);
|
||||
descriptors->push_back({kWidevineCAID, String8(kName)});
|
||||
for (int32_t new_id = kWidevineNewCasIdLowerBound;
|
||||
new_id <= kWidevineNewCasIdUpperBound; ++new_id) {
|
||||
descriptors->push_back({new_id, String8(kName)});
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
status_t WidevineCasFactory::createPlugin(int32_t CA_system_id, void* appData,
|
||||
CasPluginCallback callback,
|
||||
CasPlugin** plugin) {
|
||||
if (nullptr == plugin || !isSystemIdSupported(CA_system_id)) {
|
||||
return BAD_VALUE;
|
||||
}
|
||||
|
||||
std::unique_ptr<WidevineCasPlugin> new_plugin =
|
||||
make_unique<WidevineCasPlugin>(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<WidevineCasPlugin> new_plugin =
|
||||
make_unique<WidevineCasPlugin>(appData, callback);
|
||||
status_t status = new_plugin->initialize();
|
||||
if (status != OK) {
|
||||
return status;
|
||||
}
|
||||
*plugin = new_plugin.release();
|
||||
return OK;
|
||||
}
|
||||
|
||||
} // namespace wvcas
|
||||
600
plugin/src/widevine_media_cas_plugin.cpp
Normal file
600
plugin/src/widevine_media_cas_plugin.cpp
Normal file
@@ -0,0 +1,600 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#include "widevine_media_cas_plugin.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
#include <utils/String8.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
|
||||
#include "cas_events.h"
|
||||
#include "cas_properties.h"
|
||||
#include "cas_status.h"
|
||||
#include "cas_types.h"
|
||||
#include "cas_util.h"
|
||||
#include "log.h"
|
||||
#include "media/stagefright/MediaErrors.h"
|
||||
|
||||
using android::BAD_VALUE;
|
||||
using android::INVALID_OPERATION;
|
||||
using android::OK;
|
||||
|
||||
namespace wvcas {
|
||||
|
||||
WidevineCasPlugin::WidevineCasPlugin(void* appData, CasPluginCallback callback)
|
||||
: app_data_(appData),
|
||||
callback_(callback),
|
||||
callback_ext_(nullptr),
|
||||
widevine_cas_api_(make_unique<WidevineCas>()) {}
|
||||
|
||||
WidevineCasPlugin::WidevineCasPlugin(void* appData,
|
||||
CasPluginCallbackExt callback)
|
||||
: app_data_(appData),
|
||||
callback_(nullptr),
|
||||
callback_ext_(callback),
|
||||
widevine_cas_api_(make_unique<WidevineCas>()) {}
|
||||
|
||||
WidevineCasPlugin::~WidevineCasPlugin() { widevine_cas_api_->StopTimer(); }
|
||||
|
||||
status_t WidevineCasPlugin::initialize() {
|
||||
CasStatus status = widevine_cas_api_->initialize(this);
|
||||
if (!status.ok()) {
|
||||
return android::ERROR_CAS_UNKNOWN;
|
||||
}
|
||||
|
||||
std::string version;
|
||||
if (Properties::GetWvCasPluginVersion(version)) {
|
||||
LOGI("Widevine CAS plugin version: %s", version.c_str());
|
||||
} else {
|
||||
LOGW("Failed to get Widevine CAS plugin version.");
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
bool WidevineCasPlugin::is_provisioned() const {
|
||||
return widevine_cas_api_->is_provisioned();
|
||||
}
|
||||
|
||||
status_t WidevineCasPlugin::setStatusCallback(
|
||||
CasPluginStatusCallback /*callback*/) {
|
||||
// TODO(chelu): support status callback.
|
||||
return OK;
|
||||
}
|
||||
|
||||
status_t WidevineCasPlugin::setPrivateData(const CasData& privateData) {
|
||||
// Can get PSSH from multiple streams and from provision call.
|
||||
// Only need to request a license once.
|
||||
if (is_emm_request_sent_ || privateData.empty()) {
|
||||
return OK;
|
||||
}
|
||||
CasStatus status =
|
||||
widevine_cas_api_->ProcessCAPrivateData(privateData, &init_data_);
|
||||
if (!status.ok()) {
|
||||
return android::ERROR_CAS_UNKNOWN;
|
||||
}
|
||||
if (widevine_cas_api_->is_provisioned()) {
|
||||
return requestLicense(init_data_);
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
status_t WidevineCasPlugin::openSession(CasSessionId* sessionId) {
|
||||
if (nullptr == sessionId) {
|
||||
return BAD_VALUE;
|
||||
}
|
||||
if (!is_provisioned()) {
|
||||
LOGE("Sessions can only be opened after privisioned.");
|
||||
return android::ERROR_CAS_NOT_PROVISIONED;
|
||||
}
|
||||
|
||||
WvCasSessionId new_session_id;
|
||||
CasStatus status = widevine_cas_api_->openSession(&new_session_id);
|
||||
if (!status.ok()) {
|
||||
return android::ERROR_CAS_SESSION_NOT_OPENED;
|
||||
}
|
||||
sessionId->assign(new_session_id.begin(), new_session_id.end());
|
||||
|
||||
// This is the backward compatible 4 bytes session_id.
|
||||
int32_t session_id_int32 = 0;
|
||||
if (new_session_id.size() == 4) {
|
||||
// CasSessionId is expected to be in little endian order.
|
||||
for (int i = new_session_id.size() - 1; i >= 0; --i) {
|
||||
session_id_int32 = (session_id_int32 << 8) | new_session_id[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Not a session event, so CasSessionId in callback is null.
|
||||
CallBack(app_data_, CAS_SESSION_ID, session_id_int32, sessionId->data(),
|
||||
sessionId->size(), nullptr);
|
||||
return OK;
|
||||
}
|
||||
|
||||
status_t WidevineCasPlugin::openSession(uint32_t intent, uint32_t mode,
|
||||
CasSessionId* sessionId) {
|
||||
// TODO(chelu): return error on unsupported intent/mode.
|
||||
return openSession(sessionId);
|
||||
}
|
||||
|
||||
status_t WidevineCasPlugin::closeSession(const CasSessionId& sessionId) {
|
||||
CasStatus status = widevine_cas_api_->closeSession(sessionId);
|
||||
if (!status.ok()) {
|
||||
return android::ERROR_CAS_SESSION_NOT_OPENED;
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
status_t WidevineCasPlugin::setSessionPrivateData(const CasSessionId& sessionId,
|
||||
const CasData& privateData) {
|
||||
// Can get PSSH from multiple streams and from provision call.
|
||||
// Only need to request a license once.
|
||||
if (is_emm_request_sent_ || privateData.empty()) {
|
||||
return OK;
|
||||
}
|
||||
|
||||
// Doesn't matter which session, CA descriptor applies to all of them.
|
||||
CasStatus status = widevine_cas_api_->ProcessSessionCAPrivateData(
|
||||
sessionId, privateData, &init_data_);
|
||||
if (!status.ok()) {
|
||||
return android::ERROR_CAS_SESSION_NOT_OPENED;
|
||||
}
|
||||
if (widevine_cas_api_->is_provisioned()) {
|
||||
return requestLicense(init_data_);
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
status_t WidevineCasPlugin::processEcm(const CasSessionId& sessionId,
|
||||
const CasEcm& ecm) {
|
||||
LOGI("WidevineCasPlugin::processEcm");
|
||||
CasStatus status = widevine_cas_api_->processEcm(sessionId, ecm);
|
||||
if (!status.ok()) {
|
||||
CasData error(status.error_string().begin(), status.error_string().end());
|
||||
switch (status.status_code()) {
|
||||
case CasStatusCode::kDeferedEcmProcessing:
|
||||
return android::ERROR_CAS_DECRYPT_UNIT_NOT_INITIALIZED;
|
||||
case CasStatusCode::kAccessDeniedByParentalControl:
|
||||
CallBack(reinterpret_cast<void*>(app_data_),
|
||||
ACCESS_DENIED_BY_PARENTAL_CONTROL, /*arg=*/0, &error[0],
|
||||
error.size(), &sessionId);
|
||||
return android::ERROR_CAS_DECRYPT;
|
||||
default:
|
||||
CallBack(reinterpret_cast<void*>(app_data_), CAS_ERROR,
|
||||
static_cast<int32_t>(status.status_code()), &error[0],
|
||||
error.size(), &sessionId);
|
||||
// TODO(jfore): Can this error value be more specific? Is it because we
|
||||
// don't have a license? Or the key ids don't match? Or sunspots?
|
||||
return android::ERROR_CAS_UNKNOWN;
|
||||
}
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
status_t WidevineCasPlugin::processEmm(const CasEmm& emm) {
|
||||
LOGI("WidevineCasPlugin::processEmm");
|
||||
if (!widevine_cas_api_->processEmm(emm).ok()) {
|
||||
return android::ERROR_CAS_UNKNOWN;
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
status_t WidevineCasPlugin::sendEvent(int32_t event, int32_t arg,
|
||||
const CasData& eventData) {
|
||||
CasStatus status = processEvent(event, arg, eventData, /*sessionId=*/nullptr);
|
||||
if (status.status_code() != CasStatusCode::kNoError) {
|
||||
CasData error(status.error_string().begin(), status.error_string().end());
|
||||
CallBack(reinterpret_cast<void*>(app_data_), CAS_ERROR,
|
||||
static_cast<int32_t>(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<void*>(app_data_), CAS_ERROR,
|
||||
static_cast<int32_t>(status.status_code()), &error[0],
|
||||
error.size(), &sessionId);
|
||||
return android::ERROR_CAS_UNKNOWN;
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
status_t WidevineCasPlugin::provision(const String8& provisionString) {
|
||||
// Store |provisionString| for future use. If |provisionString| is not empty
|
||||
// the value takes priority over data in CA descriptor.
|
||||
init_data_ = std::string(provisionString.c_str(),
|
||||
provisionString.c_str() + provisionString.length());
|
||||
|
||||
if (is_provisioned()) {
|
||||
CallBack(reinterpret_cast<void*>(app_data_), INDIVIDUALIZATION_COMPLETE, 0,
|
||||
nullptr, 0, nullptr);
|
||||
if (!init_data_.empty()) {
|
||||
return requestLicense(init_data_);
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
std::string provisioning_request;
|
||||
CasStatus status = widevine_cas_api_->generateDeviceProvisioningRequest(
|
||||
&provisioning_request);
|
||||
if (!status.ok()) {
|
||||
return INVALID_OPERATION;
|
||||
}
|
||||
|
||||
if (!provisioning_request.empty()) {
|
||||
std::vector<uint8_t> callback_data(provisioning_request.begin(),
|
||||
provisioning_request.end());
|
||||
CallBack(reinterpret_cast<void*>(app_data_), INDIVIDUALIZATION_REQUEST, 0,
|
||||
callback_data.data(), callback_data.size(), nullptr);
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
status_t WidevineCasPlugin::requestLicense(const std::string& init_data) {
|
||||
std::string signed_license_request;
|
||||
std::string license_id;
|
||||
CasStatus status = widevine_cas_api_->generateEntitlementRequest(
|
||||
init_data, &signed_license_request, license_id);
|
||||
if (!status.ok()) {
|
||||
LOGE("WidevineCas generate entitlement request failed: %d",
|
||||
status.status_code());
|
||||
return INVALID_OPERATION;
|
||||
}
|
||||
|
||||
// If populated we need to send |signed_license_request| to the server.
|
||||
// Otherwise signal license complete.
|
||||
if (!signed_license_request.empty()) {
|
||||
CallBack(reinterpret_cast<void*>(app_data_), LICENSE_REQUEST,
|
||||
LICENSE_REQUEST,
|
||||
reinterpret_cast<uint8_t*>(&signed_license_request[0]),
|
||||
signed_license_request.size(), nullptr);
|
||||
} else {
|
||||
CallBack(reinterpret_cast<void*>(app_data_), LICENSE_CAS_READY,
|
||||
LICENSE_CAS_READY, reinterpret_cast<uint8_t*>(&license_id[0]),
|
||||
license_id.size(), nullptr);
|
||||
}
|
||||
is_emm_request_sent_ = true;
|
||||
return OK;
|
||||
}
|
||||
|
||||
status_t WidevineCasPlugin::refreshEntitlements(
|
||||
int32_t /*refreshType*/, const CasData& /*refreshData*/) {
|
||||
return OK;
|
||||
}
|
||||
|
||||
CasStatus WidevineCasPlugin::processEvent(int32_t event, int32_t arg,
|
||||
const CasData& eventData,
|
||||
const CasSessionId* sessionId) {
|
||||
switch (event) {
|
||||
case INDIVIDUALIZATION_RESPONSE:
|
||||
return HandleIndividualizationResponse(eventData);
|
||||
case TEST_FOR_ECHO:
|
||||
CallBack(reinterpret_cast<void*>(app_data_), ECHO, 0, nullptr, 0,
|
||||
sessionId);
|
||||
break;
|
||||
case LICENSE_RESPONSE:
|
||||
return HandleEntitlementResponse(eventData);
|
||||
case LICENSE_RENEWAL_RESPONSE:
|
||||
return HandleEntitlementRenewalResponse(eventData);
|
||||
case QUERY_UNIQUE_ID:
|
||||
return HandleUniqueIDQuery();
|
||||
case SET_PARENTAL_CONTROL_AGE:
|
||||
return HandleSetParentalControlAge(eventData);
|
||||
case LICENSE_REMOVAL:
|
||||
return HandleLicenseRemoval(eventData);
|
||||
case ASSIGN_LICENSE_ID:
|
||||
return HandleAssignLicenseID(eventData);
|
||||
case QUERY_WV_CAS_PLUGIN_VERSION:
|
||||
return HandlePluginVersionQuery();
|
||||
case LICENSE_ENTITLEMENT_PERIOD_UPDATE_RESPONSE:
|
||||
return HandleEntitlementPeriodUpdateResponse(eventData);
|
||||
default:
|
||||
return CasStatusCode::kUnknownEvent;
|
||||
}
|
||||
return CasStatusCode::kNoError;
|
||||
}
|
||||
|
||||
CasStatus WidevineCasPlugin::HandleIndividualizationResponse(
|
||||
const CasData& response) {
|
||||
if (response.empty()) {
|
||||
return CasStatus(CasStatusCode::kCasLicenseError,
|
||||
"empty individualization response");
|
||||
}
|
||||
std::string resp_string(response.begin(), response.end());
|
||||
CasStatus status = widevine_cas_api_->handleProvisioningResponse(resp_string);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
CallBack(reinterpret_cast<void*>(app_data_), INDIVIDUALIZATION_COMPLETE, 0,
|
||||
nullptr, 0, nullptr);
|
||||
|
||||
if (!init_data_.empty() && !is_emm_request_sent_) {
|
||||
LOGD("Making license request with provisioned PSSH");
|
||||
if (requestLicense(init_data_) != OK) {
|
||||
return CasStatus(CasStatusCode::kCasLicenseError,
|
||||
"failed to generate license request");
|
||||
}
|
||||
}
|
||||
return CasStatusCode::kNoError;
|
||||
}
|
||||
|
||||
CasStatus WidevineCasPlugin::HandleEntitlementResponse(
|
||||
const CasData& response) {
|
||||
if (response.empty()) {
|
||||
return CasStatus(CasStatusCode::kCasLicenseError, "empty emm response");
|
||||
}
|
||||
std::string resp_string(response.begin(), response.end());
|
||||
std::string license_id;
|
||||
std::string multi_content_license_info;
|
||||
std::string group_license_info;
|
||||
CasStatus status = widevine_cas_api_->handleEntitlementResponse(
|
||||
resp_string, license_id, multi_content_license_info, group_license_info);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
CallBack(reinterpret_cast<void*>(app_data_), LICENSE_CAS_READY,
|
||||
LICENSE_CAS_READY, reinterpret_cast<uint8_t*>(&license_id[0]),
|
||||
license_id.size(), nullptr);
|
||||
if (!multi_content_license_info.empty()) {
|
||||
CallBack(reinterpret_cast<void*>(app_data_), MULTI_CONTENT_LICENSE_INFO,
|
||||
MULTI_CONTENT_LICENSE_INFO,
|
||||
reinterpret_cast<uint8_t*>(&multi_content_license_info[0]),
|
||||
multi_content_license_info.size(), nullptr);
|
||||
}
|
||||
if (!group_license_info.empty()) {
|
||||
CallBack(reinterpret_cast<void*>(app_data_), GROUP_LICENSE_INFO,
|
||||
GROUP_LICENSE_INFO,
|
||||
reinterpret_cast<uint8_t*>(&group_license_info[0]),
|
||||
group_license_info.size(), nullptr);
|
||||
}
|
||||
return CasStatusCode::kNoError;
|
||||
}
|
||||
|
||||
CasStatus WidevineCasPlugin::HandleEntitlementRenewalResponse(
|
||||
const CasData& response) {
|
||||
if (response.empty()) {
|
||||
return CasStatus(CasStatusCode::kCasLicenseError, "empty emm response");
|
||||
}
|
||||
std::string resp_string(response.begin(), response.end());
|
||||
std::string license_id;
|
||||
CasStatus status = widevine_cas_api_->handleEntitlementRenewalResponse(
|
||||
resp_string, license_id);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
CallBack(reinterpret_cast<void*>(app_data_), LICENSE_CAS_RENEWAL_READY,
|
||||
LICENSE_CAS_RENEWAL_READY,
|
||||
reinterpret_cast<uint8_t*>(&license_id[0]), license_id.size(),
|
||||
nullptr);
|
||||
return CasStatusCode::kNoError;
|
||||
}
|
||||
|
||||
CasStatus WidevineCasPlugin::HandleEntitlementPeriodUpdateResponse(
|
||||
const CasData& response) {
|
||||
if (response.empty()) {
|
||||
return CasStatus(CasStatusCode::kCasLicenseError, "empty license response");
|
||||
}
|
||||
std::string resp_string(response.begin(), response.end());
|
||||
std::string license_id;
|
||||
CasStatus status = widevine_cas_api_->handleEntitlementPeriodUpdateResponse(
|
||||
resp_string, license_id);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
CallBack(
|
||||
reinterpret_cast<void*>(app_data_), LICENSE_ENTITLEMENT_PERIOD_UPDATED,
|
||||
LICENSE_ENTITLEMENT_PERIOD_UPDATED,
|
||||
reinterpret_cast<uint8_t*>(&license_id[0]), license_id.size(), nullptr);
|
||||
return CasStatusCode::kNoError;
|
||||
}
|
||||
|
||||
CasStatus WidevineCasPlugin::HandleUniqueIDQuery() {
|
||||
std::string buffer;
|
||||
CasStatus status = widevine_cas_api_->GetUniqueID(&buffer);
|
||||
if (!status.ok()) {
|
||||
CasData error(status.error_string().begin(), status.error_string().end());
|
||||
CallBack(reinterpret_cast<void*>(app_data_), CAS_ERROR,
|
||||
static_cast<int32_t>(status.status_code()), &error[0],
|
||||
error.size(), nullptr);
|
||||
return status;
|
||||
} else {
|
||||
CallBack(reinterpret_cast<void*>(app_data_), UNIQUE_ID,
|
||||
static_cast<int32_t>(0), reinterpret_cast<uint8_t*>(&buffer[0]),
|
||||
buffer.size(), nullptr);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
CasStatus WidevineCasPlugin::HandleSetParentalControlAge(const CasData& data) {
|
||||
return widevine_cas_api_->HandleSetParentalControlAge(data);
|
||||
}
|
||||
|
||||
CasStatus WidevineCasPlugin::HandleLicenseRemoval(const CasData& license_id) {
|
||||
if (license_id.empty()) {
|
||||
return CasStatus(CasStatusCode::kInvalidParameter, "empty license id");
|
||||
}
|
||||
std::string license_id_str(license_id.begin(), license_id.end());
|
||||
|
||||
std::string file_name = license_id_str + ".lic";
|
||||
CasStatus status = widevine_cas_api_->RemoveLicense(file_name);
|
||||
if (!status.ok()) {
|
||||
CasData error(status.error_string().begin(), status.error_string().end());
|
||||
CallBack(reinterpret_cast<void*>(app_data_), CAS_ERROR,
|
||||
static_cast<int32_t>(status.status_code()), &error[0],
|
||||
error.size(), nullptr);
|
||||
return status;
|
||||
}
|
||||
CallBack(reinterpret_cast<void*>(app_data_), LICENSE_REMOVED, LICENSE_REMOVED,
|
||||
reinterpret_cast<uint8_t*>(&license_id_str[0]),
|
||||
license_id_str.size(), nullptr);
|
||||
return CasStatusCode::kNoError;
|
||||
}
|
||||
|
||||
CasStatus WidevineCasPlugin::HandleAssignLicenseID(
|
||||
const wvcas::CasData& license_id) {
|
||||
if (license_id.empty()) {
|
||||
return CasStatus(CasStatusCode::kInvalidParameter, "empty license id");
|
||||
}
|
||||
std::string license_id_str(license_id.begin(), license_id.end());
|
||||
CasStatus status = widevine_cas_api_->RecordLicenseId(license_id_str);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
CallBack(reinterpret_cast<void*>(app_data_), LICENSE_ID_ASSIGNED,
|
||||
LICENSE_ID_ASSIGNED, reinterpret_cast<uint8_t*>(&license_id_str[0]),
|
||||
license_id_str.size(), nullptr);
|
||||
return CasStatusCode::kNoError;
|
||||
}
|
||||
|
||||
CasStatus WidevineCasPlugin::HandlePluginVersionQuery() {
|
||||
std::string version;
|
||||
if (!Properties::GetWvCasPluginVersion(version)) {
|
||||
return CasStatus(CasStatusCode::kUnknownError,
|
||||
"unable to get plugin version");
|
||||
}
|
||||
CallBack(reinterpret_cast<void*>(app_data_), WV_CAS_PLUGIN_VERSION,
|
||||
static_cast<int32_t>(0), reinterpret_cast<uint8_t*>(&version[0]),
|
||||
version.size(), /*sessionId=*/nullptr);
|
||||
return CasStatusCode::kNoError;
|
||||
}
|
||||
|
||||
void WidevineCasPlugin::OnSessionRenewalNeeded() {
|
||||
LOGI("OnSessionRenewalNeeded");
|
||||
std::string renewal_request;
|
||||
CasStatus status =
|
||||
widevine_cas_api_->generateEntitlementRenewalRequest(&renewal_request);
|
||||
if (!status.ok()) {
|
||||
LOGE("unable to generate a license renewal request: %s",
|
||||
status.error_string().c_str());
|
||||
return;
|
||||
}
|
||||
CallBack(reinterpret_cast<void*>(app_data_), LICENSE_RENEWAL_REQUEST,
|
||||
LICENSE_RENEWAL_REQUEST,
|
||||
reinterpret_cast<uint8_t*>(&renewal_request[0]),
|
||||
renewal_request.size(), nullptr);
|
||||
}
|
||||
|
||||
// TODO(jfore): Hook up usage of the key status once the license service
|
||||
// populates policy information and drop the status logging. b/129482318
|
||||
void WidevineCasPlugin::OnSessionKeysChange(const KeyStatusMap& keys_status,
|
||||
bool has_new_usable_key) {
|
||||
LOGI("OnSessionKeysChange: Has new usable key : %s",
|
||||
(has_new_usable_key ? "true" : "false"));
|
||||
|
||||
for (auto& key : keys_status) {
|
||||
LOGI("%d", key.second);
|
||||
}
|
||||
}
|
||||
|
||||
// Send the license expiration timestamp via this existing callback to app.
|
||||
// Callback will be triggered once license is installed or license expiration
|
||||
// time getting update.
|
||||
// TODO(b/163427255): Should we combine with license_id?
|
||||
void WidevineCasPlugin::OnExpirationUpdate(int64_t new_expiry_time_seconds) {
|
||||
LOGI("OnExpirationUpdate");
|
||||
CallBack(reinterpret_cast<void*>(app_data_), LICENSE_NEW_EXPIRY_TIME,
|
||||
LICENSE_NEW_EXPIRY_TIME,
|
||||
reinterpret_cast<uint8_t*>(&new_expiry_time_seconds), 8, nullptr);
|
||||
}
|
||||
|
||||
void WidevineCasPlugin::OnNewRenewalServerUrl(
|
||||
const std::string& renewal_server_url) {
|
||||
CasData url(renewal_server_url.begin(), renewal_server_url.end());
|
||||
CallBack(reinterpret_cast<void*>(app_data_), LICENSE_RENEWAL_URL, 0, &url[0],
|
||||
url.size(), nullptr);
|
||||
}
|
||||
|
||||
void WidevineCasPlugin::OnLicenseExpiration() {
|
||||
LOGI("OnLicenseExpiration");
|
||||
CallBack(reinterpret_cast<void*>(app_data_),
|
||||
android::ERROR_CAS_LICENSE_EXPIRED,
|
||||
android::ERROR_CAS_LICENSE_EXPIRED, nullptr, 0, nullptr);
|
||||
}
|
||||
|
||||
void WidevineCasPlugin::OnAgeRestrictionUpdated(const WvCasSessionId& sessionId,
|
||||
uint8_t ecm_age_restriction) {
|
||||
LOGI("OnAgeRestrictionUpdated");
|
||||
CallBack(reinterpret_cast<void*>(app_data_), AGE_RESTRICTION_UPDATED,
|
||||
/*arg=*/0, &ecm_age_restriction, 1, &sessionId);
|
||||
}
|
||||
|
||||
void WidevineCasPlugin::OnSessionFingerprintingUpdated(
|
||||
const WvCasSessionId& sessionId, const CasData& fingerprinting) {
|
||||
LOGI("OnSessionFingerprintingUpdated");
|
||||
CallBack(reinterpret_cast<void*>(app_data_),
|
||||
SESSION_FINGERPRINTING_INFO, /*arg=*/
|
||||
0,
|
||||
fingerprinting.empty() ? nullptr
|
||||
: const_cast<uint8_t*>(&fingerprinting[0]),
|
||||
fingerprinting.size(), &sessionId);
|
||||
}
|
||||
|
||||
void WidevineCasPlugin::OnSessionServiceBlockingUpdated(
|
||||
const WvCasSessionId& sessionId, const CasData& service_blocking) {
|
||||
LOGI("OnSessionServiceBlockingUpdated");
|
||||
CallBack(reinterpret_cast<void*>(app_data_),
|
||||
SESSION_SERVICE_BLOCKING_INFO, /*arg=*/
|
||||
0,
|
||||
service_blocking.empty()
|
||||
? nullptr
|
||||
: const_cast<uint8_t*>(&service_blocking[0]),
|
||||
service_blocking.size(), &sessionId);
|
||||
}
|
||||
|
||||
void WidevineCasPlugin::OnFingerprintingUpdated(const CasData& fingerprinting) {
|
||||
if (fingerprinting.empty()) {
|
||||
return;
|
||||
}
|
||||
LOGI("OnFingerprintingUpdated");
|
||||
CallBack(reinterpret_cast<void*>(app_data_), FINGERPRINTING_INFO, /*arg=*/0,
|
||||
const_cast<uint8_t*>(&fingerprinting[0]), fingerprinting.size(),
|
||||
nullptr);
|
||||
}
|
||||
|
||||
void WidevineCasPlugin::OnServiceBlockingUpdated(
|
||||
const CasData& service_blocking) {
|
||||
if (service_blocking.empty()) {
|
||||
return;
|
||||
}
|
||||
LOGI("OnServiceBlockingUpdated");
|
||||
CallBack(reinterpret_cast<void*>(app_data_), SERVICE_BLOCKING_INFO, /*arg=*/0,
|
||||
const_cast<uint8_t*>(&service_blocking[0]), service_blocking.size(),
|
||||
nullptr);
|
||||
}
|
||||
|
||||
void WidevineCasPlugin::OnEntitlementPeriodUpdateNeeded(
|
||||
const std::string& signed_license_request) {
|
||||
LOGI("OnEntitlementPeriodUpdateNeeded");
|
||||
if (!signed_license_request.empty()) {
|
||||
CallBack(reinterpret_cast<void*>(app_data_),
|
||||
LICENSE_ENTITLEMENT_PERIOD_UPDATE_REQUEST,
|
||||
LICENSE_ENTITLEMENT_PERIOD_UPDATE_REQUEST,
|
||||
const_cast<uint8_t*>(
|
||||
reinterpret_cast<const uint8_t*>(&signed_license_request[0])),
|
||||
signed_license_request.size(), nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void WidevineCasPlugin::CallBack(void* appData, int32_t event, int32_t arg,
|
||||
uint8_t* data, size_t size,
|
||||
const CasSessionId* sessionId) const {
|
||||
if (callback_ext_ != nullptr) {
|
||||
callback_ext_(appData, event, arg, data, size, sessionId);
|
||||
} else {
|
||||
callback_(appData, event, arg, data, size);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace wvcas
|
||||
21
protos/Android.bp
Normal file
21
protos/Android.bp
Normal file
@@ -0,0 +1,21 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// Builds libcas_protos.a
|
||||
// Generates *.a, *.pb.h and *.pb.cc for *.proto files.
|
||||
//
|
||||
|
||||
cc_library_static {
|
||||
|
||||
name: "libcas_protos",
|
||||
|
||||
proprietary: true,
|
||||
|
||||
srcs: [
|
||||
"media_cas.proto",
|
||||
"device_files.proto",
|
||||
"license_protocol.proto",
|
||||
],
|
||||
|
||||
proto: {
|
||||
export_proto_headers: true,
|
||||
},
|
||||
}
|
||||
115
protos/device_files.proto
Normal file
115
protos/device_files.proto
Normal file
@@ -0,0 +1,115 @@
|
||||
// ----------------------------------------------------------------------------
|
||||
// device_files.proto
|
||||
// ----------------------------------------------------------------------------
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
//
|
||||
// Description:
|
||||
// Format of various files stored at the device.
|
||||
//
|
||||
syntax = "proto2";
|
||||
|
||||
package video_widevine_client.sdk;
|
||||
|
||||
// need this if we are using libprotobuf-cpp-2.3.0-lite
|
||||
option optimize_for = LITE_RUNTIME;
|
||||
|
||||
message NameValue {
|
||||
optional string name = 1;
|
||||
optional string value = 2;
|
||||
}
|
||||
|
||||
message DeviceCertificate {
|
||||
optional bytes certificate = 1;
|
||||
optional bytes wrapped_private_key = 2;
|
||||
}
|
||||
|
||||
message License {
|
||||
enum LicenseState {
|
||||
ACTIVE = 1;
|
||||
RELEASING = 2;
|
||||
}
|
||||
|
||||
optional LicenseState state = 1;
|
||||
optional bytes pssh_data = 2;
|
||||
optional bytes license_request = 3;
|
||||
optional bytes license = 4;
|
||||
optional bytes renewal_request = 5;
|
||||
optional bytes renewal = 6;
|
||||
optional bytes release_server_url = 7;
|
||||
optional int64 playback_start_time = 8 [default = 0];
|
||||
optional int64 last_playback_time = 9 [default = 0];
|
||||
repeated NameValue app_parameters = 10;
|
||||
// This will be 0/missing if the grace period has not expired; otherwise it
|
||||
// contains the playback_start_time we should use as an override. This is
|
||||
// ignored if there is no grace period.
|
||||
optional int64 grace_period_end_time = 11 [default = 0];
|
||||
optional bytes usage_entry = 12;
|
||||
optional int64 usage_entry_number = 13;
|
||||
}
|
||||
|
||||
message UsageInfo {
|
||||
message ProviderSession {
|
||||
optional bytes token = 1;
|
||||
optional bytes license_request = 2;
|
||||
optional bytes license = 3;
|
||||
optional bytes key_set_id = 4;
|
||||
optional bytes usage_entry = 5;
|
||||
optional int64 usage_entry_number = 6;
|
||||
}
|
||||
|
||||
repeated ProviderSession sessions = 1;
|
||||
}
|
||||
|
||||
message HlsAttributes {
|
||||
enum Method {
|
||||
AES_128 = 1;
|
||||
SAMPLE_AES = 2;
|
||||
}
|
||||
optional Method method = 1;
|
||||
optional bytes media_segment_iv = 2;
|
||||
}
|
||||
|
||||
message UsageTableInfo {
|
||||
message UsageEntryInfo {
|
||||
enum UsageEntryStorage {
|
||||
LICENSE = 1;
|
||||
USAGE_INFO = 2;
|
||||
UNKNOWN = 3;
|
||||
}
|
||||
|
||||
optional UsageEntryStorage storage = 1;
|
||||
optional bytes key_set_id = 2;
|
||||
optional bytes usage_info_file_name = 3; // hash of the app_id
|
||||
}
|
||||
|
||||
optional bytes usage_table_header = 1;
|
||||
repeated UsageEntryInfo usage_entry_info = 2;
|
||||
}
|
||||
|
||||
message File {
|
||||
enum FileType {
|
||||
DEVICE_CERTIFICATE = 1;
|
||||
LICENSE = 2;
|
||||
USAGE_INFO = 3;
|
||||
HLS_ATTRIBUTES = 4;
|
||||
USAGE_TABLE_INFO = 5;
|
||||
}
|
||||
|
||||
enum FileVersion { VERSION_1 = 1; }
|
||||
|
||||
optional FileType type = 1;
|
||||
optional FileVersion version = 2 [default = VERSION_1];
|
||||
optional DeviceCertificate device_certificate = 3;
|
||||
optional License license = 4;
|
||||
optional UsageInfo usage_info = 5;
|
||||
optional HlsAttributes hls_attributes = 6;
|
||||
optional UsageTableInfo usage_table_info = 7;
|
||||
}
|
||||
|
||||
message HashedFile {
|
||||
optional bytes file = 1;
|
||||
// A raw (not hex-encoded) SHA256, taken over the bytes of 'file'.
|
||||
optional bytes hash = 2;
|
||||
}
|
||||
1261
protos/license_protocol.proto
Normal file
1261
protos/license_protocol.proto
Normal file
File diff suppressed because it is too large
Load Diff
146
protos/media_cas.proto
Normal file
146
protos/media_cas.proto
Normal file
@@ -0,0 +1,146 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
syntax = "proto2";
|
||||
|
||||
package video_widevine;
|
||||
|
||||
option optimize_for = LITE_RUNTIME;
|
||||
|
||||
// Widevine private data in the CA descriptor.
|
||||
message CaDescriptorPrivateData {
|
||||
// Provider name.
|
||||
optional string provider = 1;
|
||||
|
||||
// Content ID.
|
||||
optional bytes content_id = 2;
|
||||
|
||||
// Deprecated.
|
||||
repeated bytes deprecated_entitlement_key_ids = 3;
|
||||
|
||||
// The groups ids this channel belongs to.
|
||||
repeated bytes group_ids = 4;
|
||||
}
|
||||
|
||||
// Widevine fingerprinting.
|
||||
message Fingerprinting {
|
||||
// Channels that will be applied with the controls.
|
||||
repeated bytes channels = 1;
|
||||
// Fingerprinting controls are opaque to Widevine.
|
||||
optional bytes control = 2;
|
||||
}
|
||||
|
||||
// Widevine service blocking.
|
||||
message ServiceBlocking {
|
||||
// Channels that will be blocked.
|
||||
repeated bytes channels = 1;
|
||||
// Device groups that will be blocked. Group definition is opaque to Widevine.
|
||||
repeated bytes device_groups = 2;
|
||||
// Blocking start time in seconds since epoch. Start time is "immediate" if
|
||||
// this field is not set.
|
||||
optional int64 start_time_sec = 3;
|
||||
// Required. Blocking end time in seconds since epoch.
|
||||
optional int64 end_time_sec = 4;
|
||||
}
|
||||
|
||||
// The payload field for an EMM.
|
||||
message EmmPayload {
|
||||
repeated Fingerprinting fingerprinting = 1;
|
||||
repeated ServiceBlocking service_blocking = 2;
|
||||
// Epoch time in seconds. The time when the EMM is generated.
|
||||
optional int64 timestamp_secs = 3;
|
||||
}
|
||||
|
||||
message SignedEmmPayload {
|
||||
// Serialized EmmPayload.
|
||||
optional bytes serialized_payload = 1;
|
||||
// ECC (Elliptic Curve Cryptography) signature of |serialized_payload|.
|
||||
optional bytes signature = 2;
|
||||
}
|
||||
|
||||
message EcmMetaData {
|
||||
enum CipherMode {
|
||||
UNSPECIFIED = 0;
|
||||
AES_CBC = 1;
|
||||
AES_CTR = 2;
|
||||
DVB_CSA2 = 3;
|
||||
DVB_CSA3 = 4;
|
||||
AES_OFB = 5;
|
||||
AES_SCTE52 = 6;
|
||||
AES_ECB = 7;
|
||||
}
|
||||
// Required. The cipher mode used to encrypt/decrypt the content.
|
||||
optional CipherMode cipher_mode = 1;
|
||||
// Optional. The minimum age required to watch the content. The value
|
||||
// represents actual age, with 0 means no restriction.
|
||||
optional uint32 age_restriction = 2 [default = 0];
|
||||
// If specified, it means entitlement key rotation is enabled. The value will
|
||||
// be included in the license request. The server is expected to return
|
||||
// entitlement keys accordingly (e.g., keys for |entitlement_period_index| and
|
||||
// |entitlement_period_index| + 1).
|
||||
optional uint32 entitlement_period_index = 3;
|
||||
// Used only if entitlement key rotation is enabled. This parameter controls
|
||||
// the probability of requesting a new license by clients upon receiving this
|
||||
// ECM. The purpose is to spread out requests to avoid request storms. A
|
||||
// client will request a new license with possibility = 1 /
|
||||
// |entitlement_rotation_window_left|.
|
||||
optional uint32 entitlement_rotation_window_left = 4 [default = 1];
|
||||
}
|
||||
|
||||
message EcmKeyData {
|
||||
// The wrapped content key data (aka control word).
|
||||
// Required.
|
||||
optional bytes wrapped_key_data = 1;
|
||||
// The ID of the entitlement key used to wrap the content key. The secure key
|
||||
// data associated with this ID is held by the license server. The client gets
|
||||
// the key from the license server through a license request.
|
||||
// Required for the even key data, optional for the odd key data if it is the
|
||||
// same as the even key data.
|
||||
optional bytes entitlement_key_id = 2;
|
||||
// IV for decrypting the wrapped_key_data.
|
||||
// Required for the even key data, optional for the odd key data if it is the
|
||||
// same as the even key data.
|
||||
optional bytes wrapped_key_iv = 3;
|
||||
// IV for decrypting the content stream.
|
||||
// Optional. If not specified in the even key data, 8 bytes 0x00 will be used;
|
||||
// If not specified in the odd key data, the same content iv in the even key
|
||||
// data will be used.
|
||||
optional bytes content_iv = 4;
|
||||
}
|
||||
|
||||
message EcmGroupKeyData {
|
||||
// Group id of this key data.
|
||||
optional bytes group_id = 1;
|
||||
// Required. The key data for the even slot. Fields wrapped_key_iv and
|
||||
// content_iv may be omitted if it is the same as EcmPayload.even_key_data.
|
||||
optional EcmKeyData even_key_data = 2;
|
||||
// Optional. The key data for the odd slot if key rotation is enabled. Fields
|
||||
// wrapped_key_iv and content_iv may be omitted if it is the same as
|
||||
// EcmPayload.odd_key_data.
|
||||
optional EcmKeyData odd_key_data = 3;
|
||||
}
|
||||
|
||||
message EcmPayload {
|
||||
// Required. Meta info carried by the ECM.
|
||||
optional EcmMetaData meta_data = 1;
|
||||
// Required. The key data for the even slot.
|
||||
optional EcmKeyData even_key_data = 2;
|
||||
// Optional. The key data for the odd slot if key rotation is enabled.
|
||||
optional EcmKeyData odd_key_data = 3;
|
||||
// Optional. Widevine fingerprinting information.
|
||||
optional Fingerprinting fingerprinting = 4;
|
||||
// Optional. Widevine service blocking information.
|
||||
optional ServiceBlocking service_blocking = 5;
|
||||
// If a channel belongs to a group, the content keys can additionally be
|
||||
// encrypted by the group entitlement keys.
|
||||
repeated EcmGroupKeyData group_key_data = 6;
|
||||
}
|
||||
|
||||
// The payload field for an ECM with signature.
|
||||
message SignedEcmPayload {
|
||||
// Serialized EcmPayload.
|
||||
optional bytes serialized_payload = 1;
|
||||
// ECC (Elliptic Curve Cryptography) signature of |serialized_payload|.
|
||||
optional bytes signature = 2;
|
||||
}
|
||||
42
tests/Android.bp
Normal file
42
tests/Android.bp
Normal file
@@ -0,0 +1,42 @@
|
||||
cc_binary {
|
||||
name: "wv_cas_tests",
|
||||
proprietary: true,
|
||||
srcs: [
|
||||
"src/cas_license_test.cpp",
|
||||
"src/cas_session_map_test.cpp",
|
||||
"src/crypto_session_test.cpp",
|
||||
"src/ecm_parser_test.cpp",
|
||||
"src/ecm_parser_v2_test.cpp",
|
||||
"src/ecm_parser_v3_test.cpp",
|
||||
"src/emm_parser_test.cpp",
|
||||
"src/license_key_status_test.cpp",
|
||||
"src/policy_engine_test.cpp",
|
||||
"src/test_properties.cpp",
|
||||
"src/timer_test.cpp",
|
||||
"src/widevine_cas_api_test.cpp",
|
||||
"src/widevine_cas_session_test.cpp",
|
||||
"src/widevine_media_cas_plugin_test.cpp",
|
||||
"src/wv_cas_test_main.cpp",
|
||||
],
|
||||
header_libs: [
|
||||
"//vendor/widevine/libwvmediacas/oemcrypto:oemcastroheaders",
|
||||
"media_plugin_headers",
|
||||
],
|
||||
static_libs: [
|
||||
"//vendor/widevine/libwvmediacas/wvutil:libcasutil",
|
||||
"//vendor/widevine/libwvmediacas/plugin:libwvcasplugins",
|
||||
"//vendor/widevine/libwvmediacas/protos:libcas_protos",
|
||||
"libgmock",
|
||||
"libgtest",
|
||||
],
|
||||
shared_libs: [
|
||||
"libcrypto",
|
||||
"libutils",
|
||||
"liblog",
|
||||
"libprotobuf-cpp-lite",
|
||||
"libhidlbase",
|
||||
],
|
||||
proto: {
|
||||
type: "lite",
|
||||
},
|
||||
}
|
||||
777
tests/src/cas_license_test.cpp
Normal file
777
tests/src/cas_license_test.cpp
Normal file
@@ -0,0 +1,777 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#include "cas_license.h"
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <openssl/sha.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "cas_status.h"
|
||||
#include "cas_util.h"
|
||||
#include "device_files.pb.h"
|
||||
#include "license_protocol.pb.h"
|
||||
#include "mock_crypto_session.h"
|
||||
#include "string_conversions.h"
|
||||
|
||||
namespace wvcas {
|
||||
namespace {
|
||||
|
||||
using ::testing::_;
|
||||
using ::testing::AllOf;
|
||||
using ::testing::DoAll;
|
||||
using ::testing::Eq;
|
||||
using ::testing::Invoke;
|
||||
using ::testing::IsNull;
|
||||
using ::testing::NotNull;
|
||||
using ::testing::Pointee;
|
||||
using ::testing::Return;
|
||||
using ::testing::ReturnRef;
|
||||
using ::testing::SetArgPointee;
|
||||
using ::testing::SetArgReferee;
|
||||
using ::testing::StrictMock;
|
||||
|
||||
using wvutil::Base64Decode;
|
||||
using wvutil::Base64SafeDecode;
|
||||
using wvutil::Base64SafeEncodeNoPad;
|
||||
|
||||
using video_widevine_client::sdk::DeviceCertificate;
|
||||
using video_widevine_client::sdk::File;
|
||||
using video_widevine_client::sdk::HashedFile;
|
||||
|
||||
static constexpr char kKeyboxToken[] = "KeyBoxToken";
|
||||
static constexpr char kExpectedRenewalRequest[] = "ExpectedRenewalRequest";
|
||||
static constexpr char kExpectedSignature[] = "ExpectedSignature";
|
||||
static constexpr char kDeviceRsaKey[] = "DeviceRSAKeyDeviceRSAKeyDeviceRSAKey";
|
||||
static constexpr char kDeviceRsaKeyIV[] = "0123456789abcdef";
|
||||
static constexpr char kInitializationData[] = "CasInitializationData";
|
||||
static constexpr char kSessionKey[] = "fedcba9876543210";
|
||||
static constexpr uint32_t kNonce = 0x5555;
|
||||
static constexpr size_t kExpectedKeyboxSizeBytes = 72;
|
||||
static constexpr char kJsonStartSubstr[] = "\"signedResponse\": \"";
|
||||
static constexpr char kJsonEndSubstr[] = "\"";
|
||||
static constexpr char kKeyIDVideo[] = "KeyIdVideo";
|
||||
static constexpr char kKeyIDAudio[] = "KeyIdAudio";
|
||||
static constexpr char kKeyVideoIV[] = "KeyVideoIV";
|
||||
static constexpr char kKeyAudioIV[] = "KeyAudioIV";
|
||||
static constexpr char kKeyVideo[] = "KeyVideo";
|
||||
static constexpr char kKeyAudio[] = "KeyAudio";
|
||||
static constexpr char kKeyControlVideo[] = "KeyControlVideo";
|
||||
static constexpr char kKeyControlAudio[] = "KeyControlAudio";
|
||||
static constexpr char kKeyControlIVVideo[] = "KeyControlIVVideo";
|
||||
static constexpr char kKeyControlIVAudio[] = "KeyControlIVAudio";
|
||||
static constexpr char kTrackTypeVideo[] = "Video";
|
||||
static constexpr char kTrackTypeAudio[] = "Audio";
|
||||
static constexpr char kKeyCompanyName[] = "company_name";
|
||||
static constexpr char kKeyModelName[] = "model_name";
|
||||
static constexpr char kKeyArchitectureName[] = "architecture_name";
|
||||
static constexpr char kKeyDeviceName[] = "device_name";
|
||||
static constexpr char kKeyProductName[] = "product_name";
|
||||
static constexpr char kKeyBuildInfo[] = "build_info";
|
||||
static constexpr char kKeyDeviceId[] = "device_id";
|
||||
static constexpr char kKeyOemCryptoSecurityPatchLevel[] =
|
||||
"oem_crypto_security_patch_level";
|
||||
static constexpr char kRenewalSereverURL[] = "ExpectedRenewalURL";
|
||||
static constexpr char kCoreMessage[] = "CoreMessage";
|
||||
|
||||
typedef StrictMock<MockCryptoSession> StrictMockCryptoSession;
|
||||
|
||||
class MockPolicyEngine : public wvcas::PolicyEngine {
|
||||
public:
|
||||
MockPolicyEngine() {}
|
||||
~MockPolicyEngine() override {}
|
||||
MOCK_METHOD2(initialize,
|
||||
void(std::shared_ptr<wvcas::CryptoSession> 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<MockPolicyEngine> StrictMockPolicyEngine;
|
||||
|
||||
class TestCasLicense : public wvcas::CasLicense {
|
||||
public:
|
||||
explicit TestCasLicense() {}
|
||||
~TestCasLicense() override{};
|
||||
std::unique_ptr<wvcas::PolicyEngine> GetPolicyEngine() override {
|
||||
policy_engine_ = pass_thru_.get();
|
||||
return std::move(pass_thru_);
|
||||
}
|
||||
std::unique_ptr<StrictMockPolicyEngine> pass_thru_ =
|
||||
make_unique<StrictMockPolicyEngine>();
|
||||
StrictMockPolicyEngine* policy_engine_ = pass_thru_.get();
|
||||
};
|
||||
|
||||
class CasLicenseTest : public ::testing::TestWithParam<bool> {
|
||||
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<StrictMockCryptoSession> 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<uint8_t> 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<const unsigned char*>(data.data());
|
||||
unsigned char* output = reinterpret_cast<unsigned char*>(&(*hash)[0]);
|
||||
SHA256(input, data.size(), output);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string CreateEntitlementResponse() {
|
||||
video_widevine::License license;
|
||||
auto* key = license.add_key();
|
||||
key->set_type(video_widevine::License_KeyContainer::ENTITLEMENT);
|
||||
|
||||
video_widevine::SignedMessage signed_message;
|
||||
license.SerializeToString(signed_message.mutable_msg());
|
||||
signed_message.set_type(video_widevine::SignedMessage::CAS_LICENSE);
|
||||
signed_message.set_signature(kExpectedSignature);
|
||||
signed_message.set_session_key(kExpectedSignature);
|
||||
signed_message.set_oemcrypto_core_message(kCoreMessage);
|
||||
return signed_message.SerializeAsString();
|
||||
}
|
||||
|
||||
std::string CreateEntitlementRenewalResponse() {
|
||||
video_widevine::License license;
|
||||
video_widevine::SignedMessage signed_message;
|
||||
license.SerializeToString(signed_message.mutable_msg());
|
||||
signed_message.set_type(video_widevine::SignedMessage::CAS_LICENSE);
|
||||
signed_message.set_signature(kExpectedSignature);
|
||||
signed_message.set_oemcrypto_core_message(kCoreMessage);
|
||||
return signed_message.SerializeAsString();
|
||||
}
|
||||
|
||||
std::string CreateLicenseFileData() {
|
||||
File file;
|
||||
file.set_type(File::LICENSE);
|
||||
|
||||
video_widevine::SignedMessage license_request;
|
||||
license_request.set_msg("license_request");
|
||||
license_request.set_signature("license_request_signature");
|
||||
license_request.set_type(video_widevine::SignedMessage::CAS_LICENSE_REQUEST);
|
||||
|
||||
video_widevine::SignedMessage renewal_request;
|
||||
renewal_request.set_msg("renewal_request");
|
||||
renewal_request.set_signature("renewal_request_signature");
|
||||
renewal_request.set_type(video_widevine::SignedMessage::CAS_LICENSE_REQUEST);
|
||||
|
||||
video_widevine_client::sdk::License* license = file.mutable_license();
|
||||
license_request.SerializeToString(license->mutable_license_request());
|
||||
license->set_license(CreateEntitlementResponse());
|
||||
renewal_request.SerializeToString(license->mutable_renewal_request());
|
||||
license->set_renewal(CreateEntitlementRenewalResponse());
|
||||
|
||||
HashedFile hashed_file;
|
||||
file.SerializeToString(hashed_file.mutable_file());
|
||||
Hash(hashed_file.file(), hashed_file.mutable_hash());
|
||||
|
||||
return hashed_file.SerializeAsString();
|
||||
}
|
||||
|
||||
TEST_F(CasLicenseTest, GenerateDeviceProvisioningRequest) {
|
||||
strict_mock_ = std::make_shared<StrictMockCryptoSession>();
|
||||
TestCasLicense cas_license;
|
||||
EXPECT_CALL(*cas_license.policy_engine_, initialize(_, _));
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
cas_license.initialize(strict_mock_, nullptr).status_code());
|
||||
|
||||
std::string serialized_provisioning_request;
|
||||
std::string expected_signature(kExpectedSignature);
|
||||
|
||||
EXPECT_CALL(*strict_mock_, provisioning_method())
|
||||
.WillRepeatedly(Return(wvcas::Keybox));
|
||||
EXPECT_CALL(*strict_mock_, GetKeyData(NotNull(), NotNull()))
|
||||
.WillOnce(DoAll(Invoke(this, &CasLicenseTest::SetKeyboxData),
|
||||
Return(wvcas::CasStatusCode::kNoError)));
|
||||
EXPECT_CALL(*strict_mock_, supported_certificates())
|
||||
.WillOnce(Return(wvcas::SupportedCertificates(0x13)));
|
||||
EXPECT_CALL(*strict_mock_, GenerateNonce(NotNull()))
|
||||
.WillOnce(DoAll(SetArgPointee<0>(kNonce),
|
||||
Return(wvcas::CasStatusCode::kNoError)));
|
||||
EXPECT_CALL(*strict_mock_, GenerateDerivedKeys(_, _, _, _))
|
||||
.WillOnce(Return(wvcas::CasStatusCode::kNoError));
|
||||
EXPECT_CALL(*strict_mock_,
|
||||
PrepareAndSignProvisioningRequest(_, NotNull(), NotNull(), _, _))
|
||||
.WillOnce(DoAll(SetArgPointee<2>(kExpectedSignature),
|
||||
SetArgReferee<3>(false),
|
||||
Return(wvcas::CasStatusCode::kNoError)));
|
||||
EXPECT_CALL(*strict_mock_, GetDeviceID(NotNull()))
|
||||
.WillOnce(Return(wvcas::CasStatusCode::kNoError));
|
||||
|
||||
wvcas::CasStatus status = cas_license.GenerateDeviceProvisioningRequest(
|
||||
&serialized_provisioning_request);
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError, status.status_code());
|
||||
|
||||
// Verify the provisioning request.
|
||||
video_widevine::SignedProvisioningMessage signed_message;
|
||||
auto data = Base64Decode(serialized_provisioning_request);
|
||||
EXPECT_TRUE(signed_message.ParseFromArray(&data[0], data.size()));
|
||||
EXPECT_EQ(kExpectedSignature, signed_message.signature());
|
||||
EXPECT_EQ(video_widevine::SignedProvisioningMessage::PROVISIONING_20,
|
||||
signed_message.protocol_version());
|
||||
|
||||
video_widevine::ProvisioningRequest provisioning_request;
|
||||
EXPECT_TRUE(provisioning_request.ParseFromString(signed_message.message()));
|
||||
|
||||
auto& client_id = provisioning_request.client_id();
|
||||
EXPECT_EQ(video_widevine::ClientIdentification::KEYBOX, client_id.type());
|
||||
std::string token(kKeyboxToken);
|
||||
token.resize(kExpectedKeyboxSizeBytes, 0);
|
||||
EXPECT_EQ(token, client_id.token());
|
||||
|
||||
ASSERT_EQ(sizeof(uint32_t), provisioning_request.nonce().size());
|
||||
EXPECT_EQ(kNonce, *reinterpret_cast<const uint32_t*>(
|
||||
provisioning_request.nonce().data()));
|
||||
}
|
||||
|
||||
TEST_F(CasLicenseTest, HandleProvisioningResponse) {
|
||||
const std::string provisioning_response = CreateProvisioningResponse();
|
||||
|
||||
strict_mock_ = std::make_shared<StrictMockCryptoSession>();
|
||||
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<StrictMockCryptoSession>();
|
||||
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<StrictMockCryptoSession>();
|
||||
TestCasLicense cas_license;
|
||||
EXPECT_CALL(*cas_license.policy_engine_, initialize(_, _));
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
cas_license.initialize(strict_mock_, nullptr).status_code());
|
||||
|
||||
EXPECT_CALL(*strict_mock_, GetOEMPublicCertificate(_, _))
|
||||
.WillOnce(Return(wvcas::CasStatusCode::kCryptoSessionError));
|
||||
|
||||
EXPECT_CALL(*strict_mock_, APIVersion(_))
|
||||
.WillOnce(Return(wvcas::CasStatusCode::kNoError));
|
||||
|
||||
EXPECT_CALL(*strict_mock_, GenerateNonce(_))
|
||||
.WillOnce(Return(wvcas::CasStatusCode::kNoError));
|
||||
EXPECT_CALL(*strict_mock_,
|
||||
PrepareAndSignLicenseRequest(_, NotNull(), NotNull(), _, _))
|
||||
.WillOnce(DoAll(SetArgPointee<2>(kExpectedSignature),
|
||||
SetArgReferee<3>(false),
|
||||
Return(wvcas::CasStatusCode::kNoError)));
|
||||
EXPECT_CALL(*strict_mock_, LoadDeviceRSAKey(_, _));
|
||||
|
||||
std::string serialized_entitlement_request;
|
||||
wvcas::CasStatus status = cas_license.GenerateEntitlementRequest(
|
||||
kInitializationData, device_certificate_, wrapped_rsa_key_,
|
||||
wvcas::LicenseType::kStreaming, &serialized_entitlement_request);
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError, status.status_code());
|
||||
}
|
||||
|
||||
TEST_F(CasLicenseTest, HandleEntitlementResponse) {
|
||||
strict_mock_ = std::make_shared<StrictMockCryptoSession>();
|
||||
|
||||
TestCasLicense cas_license;
|
||||
EXPECT_CALL(*cas_license.policy_engine_, initialize(_, _));
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
cas_license.initialize(strict_mock_, nullptr).status_code());
|
||||
|
||||
EXPECT_CALL(*strict_mock_, GetOEMPublicCertificate(_, _))
|
||||
.WillOnce(Return(wvcas::CasStatusCode::kCryptoSessionError));
|
||||
EXPECT_CALL(*strict_mock_, APIVersion(_))
|
||||
.WillOnce(Return(wvcas::CasStatusCode::kNoError));
|
||||
|
||||
EXPECT_CALL(*strict_mock_, GenerateNonce(_))
|
||||
.WillOnce(Return(wvcas::CasStatusCode::kNoError));
|
||||
EXPECT_CALL(*strict_mock_,
|
||||
PrepareAndSignLicenseRequest(_, NotNull(), NotNull(), _, _))
|
||||
.WillOnce(DoAll(SetArgPointee<2>(kExpectedSignature),
|
||||
SetArgReferee<3>(false),
|
||||
Return(wvcas::CasStatusCode::kNoError)));
|
||||
EXPECT_CALL(*strict_mock_, LoadDeviceRSAKey(_, _));
|
||||
|
||||
std::string serialized_entitlement_request;
|
||||
wvcas::CasStatus status = cas_license.GenerateEntitlementRequest(
|
||||
kInitializationData, device_certificate_, wrapped_rsa_key_,
|
||||
wvcas::LicenseType::kStreaming, &serialized_entitlement_request);
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError, status.status_code());
|
||||
|
||||
video_widevine::SignedMessage signed_message;
|
||||
ASSERT_TRUE(signed_message.ParseFromString(serialized_entitlement_request));
|
||||
|
||||
const std::string entitlement_response = CreateEntitlementResponse();
|
||||
|
||||
EXPECT_CALL(*strict_mock_, DeriveKeysFromSessionKey(_, _, _, _, _, _))
|
||||
.WillRepeatedly(Return(wvcas::CasStatusCode::kNoError));
|
||||
EXPECT_CALL(*strict_mock_, LoadLicense(_, _, _))
|
||||
.WillRepeatedly(Return(wvcas::CasStatusCode::kNoError));
|
||||
|
||||
// Valid.
|
||||
EXPECT_CALL(*cas_license.policy_engine_, SetLicense(_));
|
||||
EXPECT_CALL(*cas_license.policy_engine_, CanPersist())
|
||||
.WillOnce(Return(false));
|
||||
status = cas_license.HandleEntitlementResponse(entitlement_response,
|
||||
/*device_file=*/nullptr);
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError, status.status_code());
|
||||
// Not a group license.
|
||||
EXPECT_TRUE(cas_license.GetGroupId().empty());
|
||||
EXPECT_TRUE(cas_license.GetContentIdList().empty());
|
||||
EXPECT_FALSE(cas_license.IsGroupLicense());
|
||||
EXPECT_FALSE(cas_license.IsMultiContentLicense());
|
||||
|
||||
// Valid with device file.
|
||||
EXPECT_CALL(*cas_license.policy_engine_, SetLicense(_));
|
||||
EXPECT_CALL(*cas_license.policy_engine_, CanPersist())
|
||||
.WillOnce(Return(false));
|
||||
std::string device_file;
|
||||
status =
|
||||
cas_license.HandleEntitlementResponse(entitlement_response, &device_file);
|
||||
EXPECT_TRUE(device_file.empty());
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError, status.status_code());
|
||||
|
||||
// Valid with device file and can_persist = true.
|
||||
EXPECT_CALL(*cas_license.policy_engine_, SetLicense(_));
|
||||
EXPECT_CALL(*cas_license.policy_engine_, CanPersist()).WillOnce(Return(true));
|
||||
status =
|
||||
cas_license.HandleEntitlementResponse(entitlement_response, &device_file);
|
||||
EXPECT_FALSE(device_file.empty());
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError, status.status_code());
|
||||
|
||||
HashedFile hashed_file;
|
||||
ASSERT_TRUE(hashed_file.ParseFromString(device_file));
|
||||
std::string hash;
|
||||
Hash(hashed_file.file(), &hash);
|
||||
EXPECT_EQ(std::vector<uint8_t>(hash.begin(), hash.end()),
|
||||
std::vector<uint8_t>(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<uint8_t>(emm_request.begin(), emm_request.end()),
|
||||
std::vector<uint8_t>(license_file.license_request().begin(),
|
||||
license_file.license_request().end()));
|
||||
EXPECT_EQ(std::vector<uint8_t>(entitlement_response.begin(),
|
||||
entitlement_response.end()),
|
||||
std::vector<uint8_t>(license_file.license().begin(),
|
||||
license_file.license().end()));
|
||||
}
|
||||
|
||||
TEST_P(CasLicenseTest, GenerateRenewalRequest) {
|
||||
strict_mock_ = std::make_shared<StrictMockCryptoSession>();
|
||||
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<StrictMockCryptoSession>();
|
||||
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<uint8_t>(hash.begin(), hash.end()),
|
||||
std::vector<uint8_t>(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<uint8_t>(renewal_request.begin(), renewal_request.end()),
|
||||
std::vector<uint8_t>(license_file.renewal_request().begin(),
|
||||
license_file.renewal_request().end()));
|
||||
EXPECT_EQ(
|
||||
std::vector<uint8_t>(renewal_response.begin(), renewal_response.end()),
|
||||
std::vector<uint8_t>(license_file.renewal().begin(),
|
||||
license_file.renewal().end()));
|
||||
}
|
||||
|
||||
TEST_F(CasLicenseTest, IsExpired) {
|
||||
TestCasLicense cas_license;
|
||||
strict_mock_ = std::make_shared<StrictMockCryptoSession>();
|
||||
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<StrictMockCryptoSession>();
|
||||
EXPECT_CALL(*cas_license.policy_engine_, initialize(_, _));
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
cas_license.initialize(strict_mock_, nullptr).status_code());
|
||||
|
||||
std::string license_file_data = CreateLicenseFileData();
|
||||
EXPECT_CALL(*strict_mock_, LoadDeviceRSAKey(_, _));
|
||||
EXPECT_CALL(*strict_mock_, DeriveKeysFromSessionKey(_, _, _, _, _, _))
|
||||
.WillRepeatedly(Return(wvcas::CasStatusCode::kNoError));
|
||||
EXPECT_CALL(*strict_mock_, LoadLicense(_, _, _))
|
||||
.WillRepeatedly(Return(wvcas::CasStatusCode::kNoError));
|
||||
EXPECT_CALL(*strict_mock_, LoadRenewal(_, _, _))
|
||||
.WillRepeatedly(Return(wvcas::CasStatusCode::kNoError));
|
||||
EXPECT_CALL(*cas_license.policy_engine_, SetLicense(_));
|
||||
EXPECT_CALL(*cas_license.policy_engine_, UpdateLicense(_));
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
cas_license.HandleStoredLicense(wrapped_rsa_key_, license_file_data)
|
||||
.status_code());
|
||||
}
|
||||
|
||||
TEST_F(CasLicenseTest, HandleMultiContentEntitlementResponse) {
|
||||
strict_mock_ = std::make_shared<StrictMockCryptoSession>();
|
||||
TestCasLicense cas_license;
|
||||
EXPECT_CALL(*cas_license.policy_engine_, initialize(_, _));
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
cas_license.initialize(strict_mock_, nullptr).status_code());
|
||||
EXPECT_CALL(*strict_mock_, GetOEMPublicCertificate(_, _))
|
||||
.WillOnce(Return(wvcas::CasStatusCode::kCryptoSessionError));
|
||||
EXPECT_CALL(*strict_mock_, APIVersion(_))
|
||||
.WillOnce(Return(wvcas::CasStatusCode::kNoError));
|
||||
EXPECT_CALL(*strict_mock_, GenerateNonce(_))
|
||||
.WillOnce(Return(wvcas::CasStatusCode::kNoError));
|
||||
EXPECT_CALL(*strict_mock_,
|
||||
PrepareAndSignLicenseRequest(_, NotNull(), NotNull(), _, _))
|
||||
.WillOnce(DoAll(SetArgPointee<2>(kExpectedSignature),
|
||||
SetArgReferee<3>(false),
|
||||
Return(wvcas::CasStatusCode::kNoError)));
|
||||
EXPECT_CALL(*strict_mock_, LoadDeviceRSAKey(_, _));
|
||||
|
||||
std::string serialized_entitlement_request;
|
||||
wvcas::CasStatus status = cas_license.GenerateEntitlementRequest(
|
||||
kInitializationData, device_certificate_, wrapped_rsa_key_,
|
||||
wvcas::LicenseType::kStreaming, &serialized_entitlement_request);
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError, status.status_code());
|
||||
|
||||
// Create multi content entitlement response.
|
||||
video_widevine::LicenseCategorySpec license_category_spec;
|
||||
license_category_spec.set_license_category(
|
||||
video_widevine::LicenseCategorySpec::MULTI_CONTENT_LICENSE);
|
||||
license_category_spec.set_group_id("group_id");
|
||||
video_widevine::License license;
|
||||
*license.mutable_license_category_spec() = license_category_spec;
|
||||
video_widevine::License::KeyContainer::KeyCategorySpec key_category_spec;
|
||||
key_category_spec.set_content_id("content_id_1");
|
||||
auto* key = license.add_key();
|
||||
key->set_type(video_widevine::License_KeyContainer::ENTITLEMENT);
|
||||
*key->mutable_key_category_spec() = key_category_spec;
|
||||
key = license.add_key();
|
||||
key->set_type(video_widevine::License_KeyContainer::ENTITLEMENT);
|
||||
*key->mutable_key_category_spec() = key_category_spec;
|
||||
key = license.add_key();
|
||||
key->set_type(video_widevine::License_KeyContainer::ENTITLEMENT);
|
||||
key_category_spec.set_content_id("content_id_2");
|
||||
*key->mutable_key_category_spec() = key_category_spec;
|
||||
|
||||
video_widevine::SignedMessage signed_message;
|
||||
license.SerializeToString(signed_message.mutable_msg());
|
||||
signed_message.set_type(video_widevine::SignedMessage::CAS_LICENSE);
|
||||
signed_message.set_signature(kExpectedSignature);
|
||||
signed_message.set_session_key(kExpectedSignature);
|
||||
signed_message.set_oemcrypto_core_message(kCoreMessage);
|
||||
std::string entitlement_response = signed_message.SerializeAsString();
|
||||
|
||||
EXPECT_CALL(*strict_mock_, DeriveKeysFromSessionKey(_, _, _, _, _, _))
|
||||
.WillRepeatedly(Return(wvcas::CasStatusCode::kNoError));
|
||||
EXPECT_CALL(*strict_mock_, LoadLicense(_, _, _))
|
||||
.WillRepeatedly(Return(wvcas::CasStatusCode::kNoError));
|
||||
EXPECT_CALL(*cas_license.policy_engine_, SetLicense(_));
|
||||
EXPECT_CALL(*cas_license.policy_engine_, CanPersist()).WillOnce(Return(true));
|
||||
|
||||
status = cas_license.HandleEntitlementResponse(entitlement_response,
|
||||
/*device_file=*/nullptr);
|
||||
EXPECT_EQ(status.status_code(), wvcas::CasStatusCode::kNoError);
|
||||
// Not a group license.
|
||||
EXPECT_EQ(cas_license.GetGroupId(), "group_id");
|
||||
EXPECT_THAT(cas_license.GetContentIdList(),
|
||||
testing::ElementsAre("content_id_1", "content_id_2"));
|
||||
EXPECT_FALSE(cas_license.IsGroupLicense());
|
||||
EXPECT_TRUE(cas_license.IsMultiContentLicense());
|
||||
}
|
||||
|
||||
TEST(GetEntitlementPeriodIndexTest, ValidIndexSuccess) {
|
||||
uint32_t expected_index = 123;
|
||||
// Create a valid pssh with entitlement key period index.
|
||||
video_widevine::WidevinePsshData pssh;
|
||||
pssh.set_entitlement_period_index(expected_index);
|
||||
// Create a license request containing the pssh.
|
||||
video_widevine::LicenseRequest license_request;
|
||||
license_request.mutable_content_id()->mutable_cenc_id_deprecated()->add_pssh(
|
||||
pssh.SerializeAsString());
|
||||
// Create a license file.
|
||||
File file;
|
||||
file.set_type(File::LICENSE);
|
||||
license_request.SerializeToString(
|
||||
file.mutable_license()->mutable_license_request());
|
||||
// Hash the created file
|
||||
HashedFile hashed_file;
|
||||
file.SerializeToString(hashed_file.mutable_file());
|
||||
Hash(hashed_file.file(), hashed_file.mutable_hash());
|
||||
uint32_t actual_index;
|
||||
|
||||
EXPECT_TRUE(CasLicense::GetEntitlementPeriodIndexFromStoredLicense(
|
||||
hashed_file.SerializeAsString(), actual_index)
|
||||
.ok());
|
||||
|
||||
EXPECT_EQ(actual_index, expected_index);
|
||||
}
|
||||
|
||||
TEST(GetEntitlementPeriodIndexTest, PsshHasNoIndexFail) {
|
||||
// Create a valid pssh without entitlement key period index.
|
||||
video_widevine::WidevinePsshData pssh;
|
||||
pssh.set_content_id("content_id");
|
||||
// Create a license request containing the pssh.
|
||||
video_widevine::LicenseRequest license_request;
|
||||
license_request.mutable_content_id()->mutable_cenc_id_deprecated()->add_pssh(
|
||||
pssh.SerializeAsString());
|
||||
// Create a license file.
|
||||
File file;
|
||||
file.set_type(File::LICENSE);
|
||||
license_request.SerializeToString(
|
||||
file.mutable_license()->mutable_license_request());
|
||||
// Hash the created file
|
||||
HashedFile hashed_file;
|
||||
file.SerializeToString(hashed_file.mutable_file());
|
||||
Hash(hashed_file.file(), hashed_file.mutable_hash());
|
||||
uint32_t actual_index;
|
||||
|
||||
EXPECT_FALSE(CasLicense::GetEntitlementPeriodIndexFromStoredLicense(
|
||||
hashed_file.SerializeAsString(), actual_index)
|
||||
.ok());
|
||||
}
|
||||
|
||||
TEST(GetEntitlementPeriodIndexTest, NoLicenseDataFail) {
|
||||
File file;
|
||||
file.set_type(File::LICENSE);
|
||||
// Hash the created file
|
||||
HashedFile hashed_file;
|
||||
file.SerializeToString(hashed_file.mutable_file());
|
||||
Hash(hashed_file.file(), hashed_file.mutable_hash());
|
||||
uint32_t actual_index;
|
||||
|
||||
EXPECT_FALSE(CasLicense::GetEntitlementPeriodIndexFromStoredLicense(
|
||||
hashed_file.SerializeAsString(), actual_index)
|
||||
.ok());
|
||||
}
|
||||
|
||||
TEST(GetEntitlementPeriodIndexTest, InvalidHashFail) {
|
||||
// Create a valid pssh with entitlement key period index.
|
||||
video_widevine::WidevinePsshData pssh;
|
||||
pssh.set_entitlement_period_index(123);
|
||||
// Create a license request containing the pssh.
|
||||
video_widevine::LicenseRequest license_request;
|
||||
license_request.mutable_content_id()->mutable_cenc_id_deprecated()->add_pssh(
|
||||
pssh.SerializeAsString());
|
||||
// Create a license file.
|
||||
File file;
|
||||
file.set_type(File::LICENSE);
|
||||
license_request.SerializeToString(
|
||||
file.mutable_license()->mutable_license_request());
|
||||
// Hash the created file
|
||||
HashedFile hashed_file;
|
||||
file.SerializeToString(hashed_file.mutable_file());
|
||||
hashed_file.set_hash("invalid_hash");
|
||||
uint32_t actual_index;
|
||||
|
||||
EXPECT_FALSE(CasLicense::GetEntitlementPeriodIndexFromStoredLicense(
|
||||
hashed_file.SerializeAsString(), actual_index)
|
||||
.ok());
|
||||
}
|
||||
|
||||
TEST(GetEntitlementPeriodIndexTest, InvalidFileFail) {
|
||||
uint32_t actual_index;
|
||||
EXPECT_FALSE(CasLicense::GetEntitlementPeriodIndexFromStoredLicense(
|
||||
"invalid file", actual_index)
|
||||
.ok());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace wvcas
|
||||
25
tests/src/cas_session_map_test.cpp
Normal file
25
tests/src/cas_session_map_test.cpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "widevine_cas_session.h"
|
||||
#include "widevine_cas_session_map.h"
|
||||
|
||||
using wvcas::CasSessionPtr;
|
||||
using wvcas::WidevineCasSession;
|
||||
using wvcas::WidevineCasSessionMap;
|
||||
|
||||
TEST(WidevineCasSessionMap, HappyPath) {
|
||||
std::vector<uint8_t> base_key = {0x01, 0x02, 0x03};
|
||||
WidevineCasSessionMap& map = WidevineCasSessionMap::instance();
|
||||
CasSessionPtr session = std::make_shared<WidevineCasSession>();
|
||||
|
||||
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);
|
||||
}
|
||||
1057
tests/src/crypto_session_test.cpp
Normal file
1057
tests/src/crypto_session_test.cpp
Normal file
File diff suppressed because it is too large
Load Diff
130
tests/src/ecm_parser_test.cpp
Normal file
130
tests/src/ecm_parser_test.cpp
Normal file
@@ -0,0 +1,130 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#include "ecm_parser.h"
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <tuple>
|
||||
|
||||
#include "media_cas.pb.h"
|
||||
|
||||
namespace wvcas {
|
||||
namespace {
|
||||
|
||||
constexpr int kCasIdSizeBytes = 2;
|
||||
constexpr int kVersionSizeBytes = 1;
|
||||
constexpr int kEcmHeaderSizeBytes = kCasIdSizeBytes + kVersionSizeBytes;
|
||||
constexpr int kEcmVersion2 = 2;
|
||||
constexpr int kEcmVersion3 = 3;
|
||||
|
||||
constexpr size_t kValidEcmV2SizeBytes = 165;
|
||||
|
||||
constexpr uint16_t kSectionHeader1 = 0x80;
|
||||
constexpr uint16_t kSectionHeader2 = 0x81;
|
||||
constexpr uint8_t kPointerFieldZero = 0x00;
|
||||
|
||||
constexpr uint16_t kWidevineCasId = 0x4AD4;
|
||||
// New Widevine CAS IDs 0x56C0 to 0x56C9 (all inclusive).
|
||||
constexpr uint16_t kWidevineNewCasIdLowerBound = 0x56C0;
|
||||
constexpr uint16_t kWidevineNewCasIdUpperBound = 0x56C9;
|
||||
|
||||
std::vector<uint8_t> BuildEcm(uint16_t cas_id, uint8_t version) {
|
||||
std::vector<uint8_t> ecm_data;
|
||||
ecm_data.resize(kEcmHeaderSizeBytes);
|
||||
ecm_data[0] = cas_id >> 8;
|
||||
ecm_data[1] = cas_id & 0xff;
|
||||
ecm_data[2] = version;
|
||||
|
||||
// Put some dummy data to make the ECM a valid one.
|
||||
if (version <= 2) {
|
||||
ecm_data.resize(kValidEcmV2SizeBytes);
|
||||
} else {
|
||||
video_widevine::EcmPayload ecm_payload;
|
||||
ecm_payload.mutable_even_key_data()->set_entitlement_key_id("123");
|
||||
video_widevine::SignedEcmPayload signed_ecm_payload;
|
||||
signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString());
|
||||
ecm_data.resize(ecm_data.size() + signed_ecm_payload.ByteSize());
|
||||
signed_ecm_payload.SerializeToArray(ecm_data.data() + kEcmHeaderSizeBytes,
|
||||
signed_ecm_payload.ByteSize());
|
||||
}
|
||||
return ecm_data;
|
||||
}
|
||||
|
||||
// Verifies ECM parser can be created with different version.
|
||||
class EcmParserVersionTest : public testing::Test,
|
||||
public ::testing::WithParamInterface<uint8_t> {};
|
||||
|
||||
TEST_P(EcmParserVersionTest, CreateSuccess) {
|
||||
std::vector<uint8_t> ecm_data = BuildEcm(kWidevineCasId, GetParam());
|
||||
ASSERT_TRUE(wvcas::EcmParser::Create(ecm_data) != nullptr);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(EcmParserVersionTest, EcmParserVersionTest,
|
||||
::testing::Values(kEcmVersion2, kEcmVersion3));
|
||||
|
||||
// Verifies CAS ID returned by the parser must be expected ones.
|
||||
class EcmParserCasIdTest
|
||||
: public testing::Test,
|
||||
public ::testing::WithParamInterface<::testing::tuple<uint16_t, bool>> {};
|
||||
|
||||
TEST_P(EcmParserCasIdTest, ValidateCasIds) {
|
||||
const uint16_t cas_id = ::testing::get<0>(GetParam());
|
||||
std::vector<uint8_t> ecm_data = BuildEcm(cas_id, kEcmVersion2);
|
||||
|
||||
const bool is_valid_id = ::testing::get<1>(GetParam());
|
||||
if (is_valid_id) {
|
||||
ASSERT_TRUE(wvcas::EcmParser::Create(ecm_data) != nullptr);
|
||||
} else {
|
||||
ASSERT_TRUE(wvcas::EcmParser::Create(ecm_data) == nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(EcmWithLegacyCasId, EcmParserCasIdTest,
|
||||
::testing::Values(std::make_tuple(kWidevineCasId,
|
||||
true)));
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
EcmWithNewCasId, EcmParserCasIdTest,
|
||||
::testing::Combine(
|
||||
::testing::Range(static_cast<uint16_t>(kWidevineNewCasIdLowerBound),
|
||||
static_cast<uint16_t>(kWidevineNewCasIdUpperBound +
|
||||
1)),
|
||||
::testing::Values(true)));
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
EcmWithInvalidCasId, EcmParserCasIdTest,
|
||||
::testing::Combine(::testing::Values(0, kWidevineCasId - 1,
|
||||
kWidevineCasId + 1,
|
||||
kWidevineNewCasIdLowerBound - 1,
|
||||
kWidevineNewCasIdUpperBound + 1),
|
||||
::testing::Values(false)));
|
||||
|
||||
// Verifies Section header and pointer field may be prepended to ECM.
|
||||
class EcmParserSectionHeaderTest
|
||||
: public testing::Test,
|
||||
public ::testing::WithParamInterface<uint8_t> {};
|
||||
|
||||
TEST_P(EcmParserSectionHeaderTest, EcmWithSectionHeaderOnly) {
|
||||
std::vector<uint8_t> ecm_data = BuildEcm(kWidevineCasId, kEcmVersion2);
|
||||
const std::vector<uint8_t> section_header = {GetParam(), 0, 0};
|
||||
ecm_data.insert(ecm_data.begin(), section_header.begin(),
|
||||
section_header.end());
|
||||
ASSERT_TRUE(wvcas::EcmParser::Create(ecm_data) != nullptr);
|
||||
}
|
||||
|
||||
TEST_P(EcmParserSectionHeaderTest, EcmWithSectionHeaderAndPointerField) {
|
||||
std::vector<uint8_t> ecm_data = BuildEcm(kWidevineCasId, kEcmVersion2);
|
||||
const std::vector<uint8_t> section_header = {kPointerFieldZero, GetParam(), 0,
|
||||
0};
|
||||
ecm_data.insert(ecm_data.begin(), section_header.begin(),
|
||||
section_header.end());
|
||||
ASSERT_TRUE(wvcas::EcmParser::Create(ecm_data) != nullptr);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(EcmWithSectionHeader, EcmParserSectionHeaderTest,
|
||||
::testing::Values(kSectionHeader1, kSectionHeader2));
|
||||
|
||||
} // namespace
|
||||
} // namespace wvcas
|
||||
270
tests/src/ecm_parser_v2_test.cpp
Normal file
270
tests/src/ecm_parser_v2_test.cpp
Normal file
@@ -0,0 +1,270 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#include "ecm_parser_v2.h"
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int kCasIdSizeBytes = 2;
|
||||
constexpr int kModeSizeBytes = 1;
|
||||
constexpr int kVersionSizeBytes = 1;
|
||||
constexpr int kIVFlagsSizeBytes = 1;
|
||||
constexpr int kEntitlementKeyIDSizeBytes = 16;
|
||||
constexpr int kContentKeyIDSizeBytes = 16;
|
||||
constexpr int kContentKeyDataSize = 16;
|
||||
constexpr int kWrappedKeyIVSizeBytes = 16;
|
||||
|
||||
constexpr int kEcmDescriptorSizeBytes =
|
||||
kCasIdSizeBytes + kModeSizeBytes + kVersionSizeBytes + kIVFlagsSizeBytes;
|
||||
|
||||
constexpr int kECMVersion = 2;
|
||||
// The cipher mode flags field in the ECM V2 is 4 bits.
|
||||
constexpr uint8_t kAESCBCCryptoModeFlagsVal = (0x0 << 1);
|
||||
constexpr uint8_t kAESCTRCryptoModeFlagsVal = (0x1 << 1);
|
||||
constexpr uint8_t kDvbCsa2CryptoModeFlagsVal = (0x2 << 1);
|
||||
constexpr uint8_t kDvbCsa3CryptoModeFlagsVal = (0x3 << 1);
|
||||
constexpr uint8_t kDvbOFBCryptoModeFlagsVal = (0x4 << 1);
|
||||
constexpr uint8_t kDvbSCTECryptoModeFlagsVal = (0x5 << 1);
|
||||
constexpr uint8_t kRotationFlag = (0x1 << 0);
|
||||
constexpr uint8_t kContentIVSizeFlag = (0x1 << 6);
|
||||
|
||||
constexpr uint8_t kEntitlementKeyIDFill = '1';
|
||||
constexpr uint8_t kEvenContentKeyIDFill = '2';
|
||||
constexpr uint8_t kEvenContentKeyDataFill = '3';
|
||||
constexpr uint8_t kEvenWrappedKeyIVFill = '4';
|
||||
constexpr uint8_t kEvenContentKeyIVFill = '5';
|
||||
constexpr uint8_t kOddContentKeyIDFill = '6';
|
||||
constexpr uint8_t kOddContentKeyDataFill = '7';
|
||||
constexpr uint8_t kOddWrappedKeyIVFill = '8';
|
||||
constexpr uint8_t kOddContentKeyIVFill = '9';
|
||||
|
||||
constexpr size_t kMaxEcmSizeBytes = 184;
|
||||
constexpr uint16_t kWidevineCasId = 0x4AD4;
|
||||
|
||||
} // namespace
|
||||
|
||||
class EcmParserV2Test : public testing::Test {
|
||||
protected:
|
||||
void SetUp() { BuildEcm(/*with_rotation=*/true, /*content_iv_flag=*/false); }
|
||||
size_t ContentKeyIVSize(bool content_iv_flag);
|
||||
size_t CalculateEcmSize(bool with_rotation, bool content_iv_flag = false);
|
||||
void BuildEcm(bool with_rotation, bool content_iv_flag);
|
||||
|
||||
std::vector<uint8_t> ecm_data_;
|
||||
std::unique_ptr<wvcas::EcmParserV2> parser_;
|
||||
};
|
||||
|
||||
size_t EcmParserV2Test::ContentKeyIVSize(bool content_iv_flag) {
|
||||
// Content key iv is 8 bytes if Content_IV flag is zero, and 16 bytes
|
||||
// othersize.
|
||||
return content_iv_flag ? 16 : 8;
|
||||
}
|
||||
|
||||
size_t EcmParserV2Test::CalculateEcmSize(bool with_rotation,
|
||||
bool content_iv_flag) {
|
||||
size_t ecm_key_data_size =
|
||||
kContentKeyIDSizeBytes + kContentKeyDataSize + kWrappedKeyIVSizeBytes +
|
||||
kEntitlementKeyIDSizeBytes + ContentKeyIVSize(content_iv_flag);
|
||||
return kEcmDescriptorSizeBytes + ecm_key_data_size * (with_rotation ? 2 : 1);
|
||||
}
|
||||
|
||||
void EcmParserV2Test::BuildEcm(bool with_rotation, bool content_iv_flag) {
|
||||
ecm_data_.clear();
|
||||
ecm_data_.reserve(CalculateEcmSize(with_rotation, content_iv_flag));
|
||||
ecm_data_.resize(kCasIdSizeBytes, 0);
|
||||
ecm_data_[0] = kWidevineCasId >> 8;
|
||||
ecm_data_[1] = kWidevineCasId & 0xff;
|
||||
ecm_data_.resize(ecm_data_.size() + kVersionSizeBytes, kECMVersion);
|
||||
ecm_data_.resize(ecm_data_.size() + kModeSizeBytes,
|
||||
kAESCBCCryptoModeFlagsVal);
|
||||
uint8_t iv_flag_value = content_iv_flag ? kContentIVSizeFlag : 0;
|
||||
ecm_data_.resize(ecm_data_.size() + kIVFlagsSizeBytes, iv_flag_value);
|
||||
ASSERT_EQ(kEcmDescriptorSizeBytes, ecm_data_.size());
|
||||
|
||||
// Even key fields.
|
||||
ecm_data_.resize(ecm_data_.size() + kEntitlementKeyIDSizeBytes,
|
||||
kEntitlementKeyIDFill);
|
||||
ecm_data_.resize(ecm_data_.size() + kContentKeyIDSizeBytes,
|
||||
kEvenContentKeyIDFill);
|
||||
ecm_data_.resize(ecm_data_.size() + kContentKeyDataSize,
|
||||
kEvenContentKeyDataFill);
|
||||
ecm_data_.resize(ecm_data_.size() + kWrappedKeyIVSizeBytes,
|
||||
kEvenWrappedKeyIVFill);
|
||||
ecm_data_.resize(ecm_data_.size() + ContentKeyIVSize(content_iv_flag),
|
||||
kEvenContentKeyIVFill);
|
||||
ASSERT_EQ(CalculateEcmSize(false, content_iv_flag), ecm_data_.size());
|
||||
|
||||
if (with_rotation) {
|
||||
// Entitlement key id field for odd key.
|
||||
ecm_data_.resize(ecm_data_.size() + kEntitlementKeyIDSizeBytes,
|
||||
kEntitlementKeyIDFill);
|
||||
ecm_data_.resize(ecm_data_.size() + kContentKeyIDSizeBytes,
|
||||
kOddContentKeyIDFill);
|
||||
ecm_data_.resize(ecm_data_.size() + kContentKeyDataSize,
|
||||
kOddContentKeyDataFill);
|
||||
ecm_data_.resize(ecm_data_.size() + kWrappedKeyIVSizeBytes,
|
||||
kOddWrappedKeyIVFill);
|
||||
ecm_data_.resize(ecm_data_.size() + ContentKeyIVSize(content_iv_flag),
|
||||
kOddContentKeyIVFill);
|
||||
ASSERT_EQ(CalculateEcmSize(true, content_iv_flag), ecm_data_.size());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(EcmParserV2Test, FieldsWithoutKeyRotation) {
|
||||
bool content_key_iv_16b = false;
|
||||
ecm_data_.resize(CalculateEcmSize(false, content_key_iv_16b));
|
||||
ASSERT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_));
|
||||
ASSERT_FALSE(parser_->rotation_enabled());
|
||||
|
||||
std::vector<uint8_t> test_data;
|
||||
test_data.resize(kEntitlementKeyIDSizeBytes, kEntitlementKeyIDFill);
|
||||
EXPECT_EQ(test_data,
|
||||
parser_->entitlement_key_id(wvcas::KeySlotId::kEvenKeySlot));
|
||||
|
||||
test_data.clear();
|
||||
test_data.resize(kContentKeyIDSizeBytes, kEvenContentKeyIDFill);
|
||||
EXPECT_EQ(test_data, parser_->content_key_id(wvcas::KeySlotId::kEvenKeySlot));
|
||||
|
||||
test_data.clear();
|
||||
test_data.resize(kContentKeyIDSizeBytes, kEvenContentKeyDataFill);
|
||||
EXPECT_EQ(test_data,
|
||||
parser_->wrapped_key_data(wvcas::KeySlotId::kEvenKeySlot));
|
||||
|
||||
test_data.clear();
|
||||
test_data.resize(kWrappedKeyIVSizeBytes, kEvenWrappedKeyIVFill);
|
||||
EXPECT_EQ(test_data, parser_->wrapped_key_iv(wvcas::KeySlotId::kEvenKeySlot));
|
||||
|
||||
test_data.clear();
|
||||
test_data.resize(ContentKeyIVSize(content_key_iv_16b), kEvenContentKeyIVFill);
|
||||
EXPECT_EQ(test_data, parser_->content_iv(wvcas::KeySlotId::kEvenKeySlot));
|
||||
|
||||
EXPECT_TRUE(parser_->content_key_id(wvcas::KeySlotId::kOddKeySlot).empty());
|
||||
EXPECT_TRUE(parser_->wrapped_key_data(wvcas::KeySlotId::kOddKeySlot).empty());
|
||||
EXPECT_TRUE(parser_->wrapped_key_iv(wvcas::KeySlotId::kOddKeySlot).empty());
|
||||
EXPECT_TRUE(parser_->content_iv(wvcas::KeySlotId::kOddKeySlot).empty());
|
||||
}
|
||||
|
||||
TEST_F(EcmParserV2Test, FieldsWithKeyRotation) {
|
||||
ecm_data_[3] |= kRotationFlag;
|
||||
ASSERT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_));
|
||||
ASSERT_TRUE(parser_->rotation_enabled());
|
||||
|
||||
std::vector<uint8_t> test_data;
|
||||
test_data.resize(kEntitlementKeyIDSizeBytes, kEntitlementKeyIDFill);
|
||||
EXPECT_EQ(test_data,
|
||||
parser_->entitlement_key_id(wvcas::KeySlotId::kEvenKeySlot));
|
||||
|
||||
test_data.clear();
|
||||
test_data.resize(kContentKeyIDSizeBytes, kEvenContentKeyIDFill);
|
||||
EXPECT_EQ(test_data, parser_->content_key_id(wvcas::KeySlotId::kEvenKeySlot));
|
||||
|
||||
test_data.clear();
|
||||
test_data.resize(kContentKeyIDSizeBytes, kEvenContentKeyDataFill);
|
||||
EXPECT_EQ(test_data,
|
||||
parser_->wrapped_key_data(wvcas::KeySlotId::kEvenKeySlot));
|
||||
|
||||
test_data.clear();
|
||||
test_data.resize(kWrappedKeyIVSizeBytes, kEvenWrappedKeyIVFill);
|
||||
EXPECT_EQ(test_data, parser_->wrapped_key_iv(wvcas::KeySlotId::kEvenKeySlot));
|
||||
|
||||
test_data.clear();
|
||||
bool content_key_iv_16b = false;
|
||||
test_data.resize(ContentKeyIVSize(content_key_iv_16b), kEvenContentKeyIVFill);
|
||||
EXPECT_EQ(test_data, parser_->content_iv(wvcas::KeySlotId::kEvenKeySlot));
|
||||
|
||||
test_data.clear();
|
||||
test_data.resize(kContentKeyIDSizeBytes, kOddContentKeyIDFill);
|
||||
EXPECT_EQ(test_data, parser_->content_key_id(wvcas::KeySlotId::kOddKeySlot));
|
||||
|
||||
test_data.clear();
|
||||
test_data.resize(kContentKeyIDSizeBytes, kOddContentKeyDataFill);
|
||||
EXPECT_EQ(test_data,
|
||||
parser_->wrapped_key_data(wvcas::KeySlotId::kOddKeySlot));
|
||||
|
||||
test_data.clear();
|
||||
test_data.resize(kWrappedKeyIVSizeBytes, kOddWrappedKeyIVFill);
|
||||
EXPECT_EQ(test_data, parser_->wrapped_key_iv(wvcas::KeySlotId::kOddKeySlot));
|
||||
|
||||
test_data.clear();
|
||||
test_data.resize(ContentKeyIVSize(content_key_iv_16b), kOddContentKeyIVFill);
|
||||
EXPECT_EQ(test_data, parser_->content_iv(wvcas::KeySlotId::kOddKeySlot));
|
||||
}
|
||||
|
||||
TEST_F(EcmParserV2Test, create) {
|
||||
EXPECT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_));
|
||||
|
||||
ecm_data_.resize(4);
|
||||
EXPECT_FALSE(wvcas::EcmParserV2::create(ecm_data_, &parser_));
|
||||
|
||||
ecm_data_.resize(4 + CalculateEcmSize(false));
|
||||
EXPECT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_));
|
||||
|
||||
ecm_data_.resize(kMaxEcmSizeBytes);
|
||||
EXPECT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_));
|
||||
|
||||
ecm_data_.resize(CalculateEcmSize(true));
|
||||
EXPECT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_));
|
||||
EXPECT_FALSE(wvcas::EcmParserV2::create(ecm_data_, nullptr));
|
||||
|
||||
ecm_data_.resize(CalculateEcmSize(true));
|
||||
EXPECT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_));
|
||||
EXPECT_FALSE(wvcas::EcmParserV2::create(ecm_data_, nullptr));
|
||||
}
|
||||
|
||||
TEST_F(EcmParserV2Test, crypto_mode) {
|
||||
ASSERT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_));
|
||||
EXPECT_EQ(parser_->crypto_mode(), wvcas::CryptoMode::kAesCBC);
|
||||
|
||||
ecm_data_[3] = kAESCTRCryptoModeFlagsVal;
|
||||
ASSERT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_));
|
||||
EXPECT_EQ(parser_->crypto_mode(), wvcas::CryptoMode::kAesCTR);
|
||||
|
||||
ecm_data_[3] = kDvbCsa2CryptoModeFlagsVal;
|
||||
ASSERT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_));
|
||||
EXPECT_EQ(parser_->crypto_mode(), wvcas::CryptoMode::kDvbCsa2);
|
||||
|
||||
ecm_data_[3] = kDvbCsa3CryptoModeFlagsVal;
|
||||
ASSERT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_));
|
||||
EXPECT_EQ(parser_->crypto_mode(), wvcas::CryptoMode::kDvbCsa3);
|
||||
|
||||
ecm_data_[3] = kDvbOFBCryptoModeFlagsVal;
|
||||
ASSERT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_));
|
||||
EXPECT_EQ(parser_->crypto_mode(), wvcas::CryptoMode::kAesOFB);
|
||||
|
||||
ecm_data_[3] = kDvbSCTECryptoModeFlagsVal;
|
||||
ASSERT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_));
|
||||
EXPECT_EQ(parser_->crypto_mode(), wvcas::CryptoMode::kAesSCTE);
|
||||
}
|
||||
|
||||
TEST_F(EcmParserV2Test, ContentKeyIVSizes) {
|
||||
bool with_rotation = true;
|
||||
bool iv_flag = false;
|
||||
ecm_data_.resize(CalculateEcmSize(with_rotation, iv_flag));
|
||||
ASSERT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_));
|
||||
EXPECT_EQ(parser_->content_iv_size(), ContentKeyIVSize(iv_flag));
|
||||
|
||||
iv_flag = true;
|
||||
ecm_data_[4] = kContentIVSizeFlag;
|
||||
ecm_data_.resize(CalculateEcmSize(with_rotation, iv_flag));
|
||||
ASSERT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_));
|
||||
EXPECT_EQ(parser_->content_iv_size(), ContentKeyIVSize(iv_flag));
|
||||
}
|
||||
|
||||
TEST_F(EcmParserV2Test, AgeRestriction) {
|
||||
ASSERT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_));
|
||||
EXPECT_EQ(0, parser_->age_restriction());
|
||||
|
||||
uint8_t age_restriction = 16;
|
||||
ecm_data_[4] |= age_restriction << 1;
|
||||
ASSERT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_));
|
||||
EXPECT_EQ(age_restriction, parser_->age_restriction());
|
||||
}
|
||||
|
||||
TEST_F(EcmParserV2Test, Version) {
|
||||
EXPECT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_));
|
||||
EXPECT_EQ(parser_->version(), kECMVersion);
|
||||
}
|
||||
443
tests/src/ecm_parser_v3_test.cpp
Normal file
443
tests/src/ecm_parser_v3_test.cpp
Normal file
@@ -0,0 +1,443 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#include "ecm_parser_v3.h"
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "media_cas.pb.h"
|
||||
|
||||
namespace wvcas {
|
||||
namespace {
|
||||
|
||||
using video_widevine::EcmGroupKeyData;
|
||||
using video_widevine::EcmMetaData;
|
||||
using video_widevine::EcmPayload;
|
||||
using video_widevine::SignedEcmPayload;
|
||||
|
||||
constexpr int kEcmVersion = 3;
|
||||
constexpr uint16_t kWidevineCasId = 0x4AD4;
|
||||
constexpr int kEcmHeaderSizeByte = 3;
|
||||
|
||||
constexpr char kWrappedKeyIv[] = "wrapped_key_iv..";
|
||||
constexpr char kWrappedKeyIv2[] = "wrapped_key_iv.2";
|
||||
constexpr char kEntitlementId[] = "entitlement_id..";
|
||||
constexpr char kEntitlementId2[] = "entitlement_id.2";
|
||||
constexpr char kContentIv[] = "c_iv....c_iv....";
|
||||
constexpr char kContentIv2[] = "c_iv....c_iv...2";
|
||||
constexpr char kWrappedContentKey[] = "wrapped_key.....";
|
||||
constexpr char kWrappedContentKey2[] = "wrapped_key....2";
|
||||
|
||||
void WriteEcmHeader(std::vector<uint8_t>* ecm) {
|
||||
ecm->push_back(kWidevineCasId >> 8);
|
||||
ecm->push_back(kWidevineCasId & 0xff);
|
||||
ecm->push_back(kEcmVersion);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> GenerateEcm(const SignedEcmPayload& signed_ecm_payload) {
|
||||
std::vector<uint8_t> ecm;
|
||||
WriteEcmHeader(&ecm);
|
||||
ecm.resize(kEcmHeaderSizeByte + signed_ecm_payload.ByteSize());
|
||||
signed_ecm_payload.SerializeToArray(ecm.data() + kEcmHeaderSizeByte,
|
||||
signed_ecm_payload.ByteSize());
|
||||
return ecm;
|
||||
}
|
||||
|
||||
TEST(EcmParserV3Test, CreateWithEmptyEcmFail) {
|
||||
std::vector<uint8_t> ecm;
|
||||
EXPECT_TRUE(EcmParserV3::Create(ecm) == nullptr);
|
||||
}
|
||||
|
||||
TEST(EcmParserV3Test, CreateWithOnlyEcmHeaderFail) {
|
||||
std::vector<uint8_t> ecm;
|
||||
WriteEcmHeader(&ecm);
|
||||
EXPECT_TRUE(EcmParserV3::Create(ecm) == nullptr);
|
||||
}
|
||||
|
||||
TEST(EcmParserV3Test, CreateWithInvalidSignedEcmPayloadFail) {
|
||||
std::vector<uint8_t> ecm;
|
||||
WriteEcmHeader(&ecm);
|
||||
// appends some chars as payload
|
||||
ecm.resize(100, 'x');
|
||||
|
||||
EXPECT_TRUE(EcmParserV3::Create(ecm) == nullptr);
|
||||
}
|
||||
|
||||
TEST(EcmParserV3Test, CreateWithInvalidSerializedEcmFail) {
|
||||
SignedEcmPayload signed_ecm_payload;
|
||||
signed_ecm_payload.set_serialized_payload("invalid");
|
||||
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
|
||||
|
||||
EXPECT_TRUE(EcmParserV3::Create(ecm) == nullptr);
|
||||
}
|
||||
|
||||
TEST(EcmParserV3Test, CreateWithEvenKeySuccess) {
|
||||
EcmPayload ecm_payload;
|
||||
ecm_payload.mutable_even_key_data()->set_entitlement_key_id(kEntitlementId);
|
||||
ecm_payload.mutable_even_key_data()->set_wrapped_key_data(kWrappedContentKey);
|
||||
ecm_payload.mutable_even_key_data()->set_content_iv(kContentIv);
|
||||
ecm_payload.mutable_even_key_data()->set_wrapped_key_iv(kWrappedKeyIv);
|
||||
SignedEcmPayload signed_ecm_payload;
|
||||
signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString());
|
||||
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
|
||||
|
||||
std::unique_ptr<EcmParserV3> parser = EcmParserV3::Create(ecm);
|
||||
|
||||
ASSERT_TRUE(parser != nullptr);
|
||||
EXPECT_EQ(parser->version(), kEcmVersion);
|
||||
EXPECT_EQ(parser->age_restriction(), 0);
|
||||
EXPECT_EQ(parser->crypto_mode(), CryptoMode::kInvalid);
|
||||
EXPECT_FALSE(parser->has_fingerprinting());
|
||||
EXPECT_FALSE(parser->has_service_blocking());
|
||||
EXPECT_EQ(parser->ecm_serialized_payload(), ecm_payload.SerializeAsString());
|
||||
EXPECT_TRUE(parser->signature().empty());
|
||||
EXPECT_FALSE(parser->rotation_enabled());
|
||||
EXPECT_EQ(parser->content_iv_size(), 16);
|
||||
std::vector<uint8_t> result =
|
||||
parser->entitlement_key_id(KeySlotId::kEvenKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kEntitlementId);
|
||||
result = parser->wrapped_key_data(KeySlotId::kEvenKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedContentKey);
|
||||
result = parser->content_iv(KeySlotId::kEvenKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kContentIv);
|
||||
result = parser->wrapped_key_iv(KeySlotId::kEvenKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedKeyIv);
|
||||
}
|
||||
|
||||
TEST(EcmParserV3Test, CreateWithEvenOddKeysSuccess) {
|
||||
EcmPayload ecm_payload;
|
||||
ecm_payload.mutable_even_key_data()->set_entitlement_key_id(kEntitlementId);
|
||||
ecm_payload.mutable_even_key_data()->set_wrapped_key_data(kWrappedContentKey);
|
||||
ecm_payload.mutable_even_key_data()->set_content_iv(kContentIv);
|
||||
ecm_payload.mutable_even_key_data()->set_wrapped_key_iv(kWrappedKeyIv);
|
||||
ecm_payload.mutable_odd_key_data()->set_entitlement_key_id(kEntitlementId2);
|
||||
ecm_payload.mutable_odd_key_data()->set_wrapped_key_data(kWrappedContentKey2);
|
||||
ecm_payload.mutable_odd_key_data()->set_content_iv(kContentIv2);
|
||||
ecm_payload.mutable_odd_key_data()->set_wrapped_key_iv(kWrappedKeyIv2);
|
||||
SignedEcmPayload signed_ecm_payload;
|
||||
signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString());
|
||||
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
|
||||
|
||||
std::unique_ptr<EcmParserV3> parser = EcmParserV3::Create(ecm);
|
||||
|
||||
ASSERT_TRUE(parser != nullptr);
|
||||
EXPECT_TRUE(parser->rotation_enabled());
|
||||
std::vector<uint8_t> result =
|
||||
parser->entitlement_key_id(KeySlotId::kEvenKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kEntitlementId);
|
||||
result = parser->wrapped_key_data(KeySlotId::kEvenKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedContentKey);
|
||||
result = parser->content_iv(KeySlotId::kEvenKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kContentIv);
|
||||
result = parser->wrapped_key_iv(KeySlotId::kEvenKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedKeyIv);
|
||||
result = parser->entitlement_key_id(KeySlotId::kOddKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kEntitlementId2);
|
||||
result = parser->wrapped_key_data(KeySlotId::kOddKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedContentKey2);
|
||||
result = parser->content_iv(KeySlotId::kOddKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kContentIv2);
|
||||
result = parser->wrapped_key_iv(KeySlotId::kOddKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedKeyIv2);
|
||||
}
|
||||
|
||||
TEST(EcmParserV3Test, CreateWithOmittedOddKeyFieldsSuccess) {
|
||||
EcmPayload ecm_payload;
|
||||
ecm_payload.mutable_even_key_data()->set_entitlement_key_id(kEntitlementId);
|
||||
ecm_payload.mutable_even_key_data()->set_wrapped_key_data(kWrappedContentKey);
|
||||
ecm_payload.mutable_even_key_data()->set_content_iv(kContentIv);
|
||||
ecm_payload.mutable_even_key_data()->set_wrapped_key_iv(kWrappedKeyIv);
|
||||
ecm_payload.mutable_odd_key_data()->set_wrapped_key_data(kWrappedContentKey2);
|
||||
SignedEcmPayload signed_ecm_payload;
|
||||
signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString());
|
||||
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
|
||||
|
||||
std::unique_ptr<EcmParserV3> parser = EcmParserV3::Create(ecm);
|
||||
|
||||
ASSERT_TRUE(parser != nullptr);
|
||||
EXPECT_TRUE(parser->rotation_enabled());
|
||||
std::vector<uint8_t> result =
|
||||
parser->entitlement_key_id(KeySlotId::kEvenKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kEntitlementId);
|
||||
result = parser->wrapped_key_data(KeySlotId::kEvenKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedContentKey);
|
||||
result = parser->content_iv(KeySlotId::kEvenKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kContentIv);
|
||||
result = parser->wrapped_key_iv(KeySlotId::kEvenKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedKeyIv);
|
||||
result = parser->entitlement_key_id(KeySlotId::kOddKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kEntitlementId);
|
||||
result = parser->wrapped_key_data(KeySlotId::kOddKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedContentKey2);
|
||||
result = parser->content_iv(KeySlotId::kOddKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kContentIv);
|
||||
result = parser->wrapped_key_iv(KeySlotId::kOddKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedKeyIv);
|
||||
}
|
||||
|
||||
TEST(EcmParserV3Test, AgeRestrictionSuccess) {
|
||||
const int expected_age_restriction = 3;
|
||||
EcmPayload ecm_payload;
|
||||
ecm_payload.mutable_meta_data()->set_age_restriction(
|
||||
expected_age_restriction);
|
||||
SignedEcmPayload signed_ecm_payload;
|
||||
signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString());
|
||||
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
|
||||
|
||||
std::unique_ptr<EcmParserV3> parser = EcmParserV3::Create(ecm);
|
||||
|
||||
ASSERT_TRUE(parser != nullptr);
|
||||
EXPECT_EQ(parser->age_restriction(), expected_age_restriction);
|
||||
}
|
||||
|
||||
class EcmParserV3AgeRestrictionTest
|
||||
: public testing::Test,
|
||||
public testing::WithParamInterface<uint8_t> {};
|
||||
|
||||
TEST_P(EcmParserV3AgeRestrictionTest, ExpectedAgeRestriction) {
|
||||
const uint8_t expected_age_restriction = GetParam();
|
||||
EcmPayload ecm_payload;
|
||||
ecm_payload.mutable_meta_data()->set_age_restriction(
|
||||
expected_age_restriction);
|
||||
SignedEcmPayload signed_ecm_payload;
|
||||
signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString());
|
||||
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
|
||||
|
||||
std::unique_ptr<EcmParserV3> parser = EcmParserV3::Create(ecm);
|
||||
|
||||
ASSERT_TRUE(parser != nullptr);
|
||||
EXPECT_EQ(parser->age_restriction(), expected_age_restriction);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(EcmParserV3AgeRestrictionTest,
|
||||
EcmParserV3AgeRestrictionTest,
|
||||
testing::Values(0, 3, 18));
|
||||
|
||||
class EcmParserV3CipherModeTest
|
||||
: public testing::Test,
|
||||
public testing::WithParamInterface<
|
||||
testing::tuple<CryptoMode, EcmMetaData::CipherMode>> {};
|
||||
|
||||
TEST_P(EcmParserV3CipherModeTest, ExpectedCipherMode) {
|
||||
const CryptoMode expected = std::get<0>(GetParam());
|
||||
EcmPayload ecm_payload;
|
||||
ecm_payload.mutable_meta_data()->set_cipher_mode(std::get<1>(GetParam()));
|
||||
SignedEcmPayload signed_ecm_payload;
|
||||
signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString());
|
||||
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
|
||||
|
||||
std::unique_ptr<EcmParserV3> parser = EcmParserV3::Create(ecm);
|
||||
|
||||
ASSERT_TRUE(parser != nullptr);
|
||||
EXPECT_EQ(parser->crypto_mode(), expected);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
EcmParserV3CipherModeTest, EcmParserV3CipherModeTest,
|
||||
testing::Values(
|
||||
std::make_tuple(CryptoMode::kAesCBC, EcmMetaData::AES_CBC),
|
||||
std::make_tuple(CryptoMode::kAesCTR, EcmMetaData::AES_CTR),
|
||||
std::make_tuple(CryptoMode::kDvbCsa2, EcmMetaData::DVB_CSA2),
|
||||
std::make_tuple(CryptoMode::kDvbCsa3, EcmMetaData::DVB_CSA3),
|
||||
std::make_tuple(CryptoMode::kAesOFB, EcmMetaData::AES_OFB),
|
||||
std::make_tuple(CryptoMode::kAesSCTE, EcmMetaData::AES_SCTE52),
|
||||
std::make_tuple(CryptoMode::kAesECB, EcmMetaData::AES_ECB)));
|
||||
|
||||
TEST(EcmParserV3Test, FingerprintingSuccess) {
|
||||
EcmPayload ecm_payload;
|
||||
ecm_payload.mutable_fingerprinting()->set_control("control");
|
||||
SignedEcmPayload signed_ecm_payload;
|
||||
signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString());
|
||||
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
|
||||
|
||||
std::unique_ptr<EcmParserV3> parser = EcmParserV3::Create(ecm);
|
||||
|
||||
ASSERT_TRUE(parser != nullptr);
|
||||
EXPECT_TRUE(parser->has_fingerprinting());
|
||||
EXPECT_EQ(parser->fingerprinting().SerializeAsString(),
|
||||
ecm_payload.fingerprinting().SerializeAsString());
|
||||
}
|
||||
|
||||
TEST(EcmParserV3Test, ServiceBlockingSuccess) {
|
||||
EcmPayload ecm_payload;
|
||||
ecm_payload.mutable_service_blocking()->add_device_groups("group");
|
||||
SignedEcmPayload signed_ecm_payload;
|
||||
signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString());
|
||||
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
|
||||
|
||||
std::unique_ptr<EcmParserV3> parser = EcmParserV3::Create(ecm);
|
||||
|
||||
ASSERT_TRUE(parser != nullptr);
|
||||
EXPECT_TRUE(parser->has_service_blocking());
|
||||
EXPECT_EQ(parser->service_blocking().SerializeAsString(),
|
||||
ecm_payload.service_blocking().SerializeAsString());
|
||||
}
|
||||
|
||||
TEST(EcmParserV3Test, SignatureSuccess) {
|
||||
const std::string expected_signature = "signature";
|
||||
SignedEcmPayload signed_ecm_payload;
|
||||
signed_ecm_payload.set_signature(expected_signature);
|
||||
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
|
||||
|
||||
std::unique_ptr<EcmParserV3> parser = EcmParserV3::Create(ecm);
|
||||
|
||||
ASSERT_TRUE(parser != nullptr);
|
||||
EXPECT_EQ(parser->signature(), expected_signature);
|
||||
}
|
||||
|
||||
TEST(EcmParserV3Test, SetGroupIdSuccess) {
|
||||
const std::string group_id = "group_id";
|
||||
const std::string group_id2 = "another_group";
|
||||
EcmPayload ecm_payload;
|
||||
EcmGroupKeyData* group_key_data = ecm_payload.add_group_key_data();
|
||||
group_key_data->set_group_id(group_id);
|
||||
group_key_data = ecm_payload.add_group_key_data();
|
||||
group_key_data->set_group_id(group_id2);
|
||||
SignedEcmPayload signed_ecm_payload;
|
||||
signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString());
|
||||
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
|
||||
|
||||
std::unique_ptr<EcmParserV3> parser = EcmParserV3::Create(ecm);
|
||||
|
||||
ASSERT_TRUE(parser != nullptr);
|
||||
EXPECT_TRUE(parser->set_group_id(group_id));
|
||||
EXPECT_TRUE(parser->set_group_id(group_id2));
|
||||
}
|
||||
|
||||
TEST(EcmParserV3Test, SetUnknownGroupIdFail) {
|
||||
EcmPayload ecm_payload;
|
||||
EcmGroupKeyData* group_key_data = ecm_payload.add_group_key_data();
|
||||
group_key_data->set_group_id("group_id");
|
||||
SignedEcmPayload signed_ecm_payload;
|
||||
signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString());
|
||||
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
|
||||
|
||||
std::unique_ptr<EcmParserV3> parser = EcmParserV3::Create(ecm);
|
||||
|
||||
ASSERT_TRUE(parser != nullptr);
|
||||
EXPECT_FALSE(parser->set_group_id("unknown"));
|
||||
}
|
||||
|
||||
TEST(EcmParserV3Test, ParserWithGroupIdSuccess) {
|
||||
const std::string group_id = "group_id";
|
||||
EcmPayload ecm_payload;
|
||||
EcmGroupKeyData* group_key_data = ecm_payload.add_group_key_data();
|
||||
group_key_data->set_group_id(group_id);
|
||||
group_key_data->mutable_even_key_data()->set_entitlement_key_id(
|
||||
kEntitlementId);
|
||||
group_key_data->mutable_even_key_data()->set_wrapped_key_data(
|
||||
kWrappedContentKey);
|
||||
group_key_data->mutable_even_key_data()->set_content_iv(kContentIv);
|
||||
group_key_data->mutable_even_key_data()->set_wrapped_key_iv(kWrappedKeyIv);
|
||||
ecm_payload.mutable_even_key_data()->set_entitlement_key_id(kEntitlementId2);
|
||||
ecm_payload.mutable_even_key_data()->set_wrapped_key_data(
|
||||
kWrappedContentKey2);
|
||||
ecm_payload.mutable_even_key_data()->set_content_iv(kContentIv2);
|
||||
ecm_payload.mutable_even_key_data()->set_wrapped_key_iv(kWrappedKeyIv2);
|
||||
|
||||
SignedEcmPayload signed_ecm_payload;
|
||||
signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString());
|
||||
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
|
||||
|
||||
std::unique_ptr<EcmParserV3> parser = EcmParserV3::Create(ecm);
|
||||
|
||||
ASSERT_TRUE(parser != nullptr);
|
||||
// If group Id is not set, the normal keys will be returned.
|
||||
std::vector<uint8_t> result =
|
||||
parser->entitlement_key_id(KeySlotId::kEvenKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kEntitlementId2);
|
||||
result = parser->wrapped_key_data(KeySlotId::kEvenKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedContentKey2);
|
||||
result = parser->content_iv(KeySlotId::kEvenKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kContentIv2);
|
||||
result = parser->wrapped_key_iv(KeySlotId::kEvenKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedKeyIv2);
|
||||
|
||||
// Now set the group id.
|
||||
EXPECT_TRUE(parser->set_group_id(group_id));
|
||||
result = parser->entitlement_key_id(KeySlotId::kEvenKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kEntitlementId);
|
||||
result = parser->wrapped_key_data(KeySlotId::kEvenKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedContentKey);
|
||||
result = parser->content_iv(KeySlotId::kEvenKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kContentIv);
|
||||
result = parser->wrapped_key_iv(KeySlotId::kEvenKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedKeyIv);
|
||||
}
|
||||
|
||||
TEST(EcmParserV3Test, ParserGroupKeysWithOmittedFieldsSuccess) {
|
||||
const std::string group_id = "group_id";
|
||||
EcmPayload ecm_payload;
|
||||
EcmGroupKeyData* group_key_data = ecm_payload.add_group_key_data();
|
||||
group_key_data->set_group_id(group_id);
|
||||
group_key_data->mutable_even_key_data()->set_entitlement_key_id(
|
||||
kEntitlementId);
|
||||
group_key_data->mutable_even_key_data()->set_wrapped_key_data(
|
||||
kWrappedContentKey);
|
||||
// Content IV and wrapped key iv is omitted in |group_key_data|/
|
||||
ecm_payload.mutable_even_key_data()->set_entitlement_key_id(kEntitlementId2);
|
||||
ecm_payload.mutable_even_key_data()->set_wrapped_key_data(
|
||||
kWrappedContentKey2);
|
||||
ecm_payload.mutable_even_key_data()->set_content_iv(kContentIv2);
|
||||
ecm_payload.mutable_even_key_data()->set_wrapped_key_iv(kWrappedKeyIv2);
|
||||
SignedEcmPayload signed_ecm_payload;
|
||||
signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString());
|
||||
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
|
||||
|
||||
std::unique_ptr<EcmParserV3> parser = EcmParserV3::Create(ecm);
|
||||
ASSERT_TRUE(parser != nullptr);
|
||||
EXPECT_TRUE(parser->set_group_id(group_id));
|
||||
|
||||
std::vector<uint8_t> result =
|
||||
parser->entitlement_key_id(KeySlotId::kEvenKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kEntitlementId);
|
||||
result = parser->wrapped_key_data(KeySlotId::kEvenKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedContentKey);
|
||||
// Content IV and wrapped key iv are from normal non-group key.
|
||||
result = parser->content_iv(KeySlotId::kEvenKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kContentIv2);
|
||||
result = parser->wrapped_key_iv(KeySlotId::kEvenKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedKeyIv2);
|
||||
}
|
||||
|
||||
TEST(EcmParserV3Test, EntitlementRotationEnabledSuccess) {
|
||||
const uint32_t entitlement_period_index = 10;
|
||||
const uint32_t entitlement_rotation_window_left = 100;
|
||||
EcmPayload ecm_payload;
|
||||
ecm_payload.mutable_meta_data()->set_entitlement_period_index(
|
||||
entitlement_period_index);
|
||||
ecm_payload.mutable_meta_data()->set_entitlement_rotation_window_left(
|
||||
entitlement_rotation_window_left);
|
||||
SignedEcmPayload signed_ecm_payload;
|
||||
signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString());
|
||||
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
|
||||
|
||||
std::unique_ptr<EcmParserV3> parser = EcmParserV3::Create(ecm);
|
||||
|
||||
ASSERT_TRUE(parser != nullptr);
|
||||
EXPECT_TRUE(parser->is_entitlement_rotation_enabled());
|
||||
EXPECT_EQ(parser->entitlement_period_index(), entitlement_period_index);
|
||||
EXPECT_EQ(parser->entitlement_rotation_window_left(),
|
||||
entitlement_rotation_window_left);
|
||||
}
|
||||
|
||||
TEST(EcmParserV3Test, EntitlementRotationDefaultDisabledSuccess) {
|
||||
EcmPayload ecm_payload;
|
||||
// Put something in the payload just to make the ECM valid.
|
||||
ecm_payload.mutable_even_key_data()->set_entitlement_key_id(kEntitlementId);
|
||||
SignedEcmPayload signed_ecm_payload;
|
||||
signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString());
|
||||
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
|
||||
|
||||
std::unique_ptr<EcmParserV3> parser = EcmParserV3::Create(ecm);
|
||||
|
||||
ASSERT_TRUE(parser != nullptr);
|
||||
EXPECT_FALSE(parser->is_entitlement_rotation_enabled());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace wvcas
|
||||
133
tests/src/emm_parser_test.cpp
Normal file
133
tests/src/emm_parser_test.cpp
Normal file
@@ -0,0 +1,133 @@
|
||||
// Copyright 2020 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#include "emm_parser.h"
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "cas_types.h"
|
||||
|
||||
namespace wvcas {
|
||||
namespace {
|
||||
|
||||
using video_widevine::EmmPayload;
|
||||
using video_widevine::SignedEmmPayload;
|
||||
|
||||
constexpr uint8_t kSectionHeader = 0x82;
|
||||
constexpr int64_t kDefaultTimestamp = 1598905921;
|
||||
constexpr char kDefaultSignature[] = "signature";
|
||||
|
||||
class EmmParserTest : public testing::Test {
|
||||
protected:
|
||||
EmmParserTest()
|
||||
: timestamp_(kDefaultTimestamp), signature_(kDefaultSignature) {}
|
||||
void SetSectionHeader(const std::vector<uint8_t> section_header) {
|
||||
section_header_.assign(section_header.begin(), section_header.end());
|
||||
}
|
||||
void SetTimestamp(uint64_t timestamp) { timestamp_ = timestamp; }
|
||||
void SetSignedEmm(const std::string& signed_emm) {
|
||||
serialized_signed_emm_ = signed_emm;
|
||||
}
|
||||
void SetEmmPayload(const std::string& serialized_payload) {
|
||||
serialized_emm_payload_ = serialized_payload;
|
||||
}
|
||||
void SetSignature(const std::string& signature) { signature_ = signature; }
|
||||
|
||||
std::vector<uint8_t> BuildEmm() const {
|
||||
std::vector<uint8_t> emm_data(section_header_.begin(),
|
||||
section_header_.end());
|
||||
if (!serialized_signed_emm_.empty()) {
|
||||
emm_data.insert(emm_data.end(), serialized_signed_emm_.begin(),
|
||||
serialized_signed_emm_.end());
|
||||
return emm_data;
|
||||
}
|
||||
|
||||
SignedEmmPayload signed_emm;
|
||||
if (serialized_emm_payload_.empty()) {
|
||||
EmmPayload emm_payload;
|
||||
emm_payload.set_timestamp_secs(timestamp_);
|
||||
emm_payload.SerializeToString(signed_emm.mutable_serialized_payload());
|
||||
} else {
|
||||
signed_emm.set_serialized_payload(serialized_emm_payload_);
|
||||
}
|
||||
signed_emm.set_signature(signature_);
|
||||
|
||||
emm_data.resize(emm_data.size() + signed_emm.ByteSizeLong());
|
||||
signed_emm.SerializeToArray(&emm_data[section_header_.size()],
|
||||
emm_data.size());
|
||||
return emm_data;
|
||||
}
|
||||
|
||||
void ValidateParserAgainstDefault(const EmmParser* const parser) {
|
||||
ASSERT_NE(parser, nullptr);
|
||||
EXPECT_EQ(parser->timestamp(), kDefaultTimestamp);
|
||||
EmmPayload expected_emm_payload;
|
||||
expected_emm_payload.set_timestamp_secs(timestamp_);
|
||||
EXPECT_EQ(parser->emm_payload().SerializeAsString(),
|
||||
expected_emm_payload.SerializeAsString());
|
||||
EXPECT_EQ(parser->signature(), kDefaultSignature);
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<uint8_t> section_header_;
|
||||
uint64_t timestamp_;
|
||||
std::string signature_;
|
||||
std::string serialized_signed_emm_;
|
||||
std::string serialized_emm_payload_;
|
||||
};
|
||||
|
||||
TEST_F(EmmParserTest, ParseDefaultSuccess) {
|
||||
auto parser = EmmParser::Create(BuildEmm());
|
||||
ValidateParserAgainstDefault(parser.get());
|
||||
}
|
||||
|
||||
TEST_F(EmmParserTest, EmmWithSectionHeaderSuccess) {
|
||||
SetSectionHeader({kSectionHeader, 0, 0});
|
||||
auto parser = EmmParser::Create(BuildEmm());
|
||||
ValidateParserAgainstDefault(parser.get());
|
||||
}
|
||||
|
||||
TEST_F(EmmParserTest, EmmWithSectionHeaderAndPointerFieldSuccess) {
|
||||
SetSectionHeader({0, kSectionHeader, 0, 0});
|
||||
auto parser = EmmParser::Create(BuildEmm());
|
||||
ValidateParserAgainstDefault(parser.get());
|
||||
}
|
||||
|
||||
TEST_F(EmmParserTest, EmmWithMalformedEmmCreateFail) {
|
||||
SetSignedEmm("some emm");
|
||||
EXPECT_THAT(EmmParser::Create(BuildEmm()), ::testing::IsNull());
|
||||
}
|
||||
|
||||
TEST_F(EmmParserTest, EmmWithMalformedPayloadCreateFail) {
|
||||
SetEmmPayload("some payload");
|
||||
EXPECT_THAT(EmmParser::Create(BuildEmm()), ::testing::IsNull());
|
||||
}
|
||||
|
||||
TEST_F(EmmParserTest, EmmWithNoSignatureCreateFail) {
|
||||
SetSignature("");
|
||||
EXPECT_THAT(EmmParser::Create(BuildEmm()), ::testing::IsNull());
|
||||
}
|
||||
|
||||
class EmmParserWrongPrefixTest
|
||||
: public EmmParserTest,
|
||||
public ::testing::WithParamInterface<std::vector<uint8_t>> {};
|
||||
|
||||
TEST_P(EmmParserWrongPrefixTest, EmmWithWrongPrefixCreateFail) {
|
||||
SetSectionHeader(GetParam());
|
||||
EXPECT_THAT(EmmParser::Create(BuildEmm()), ::testing::IsNull());
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
EmmParserWrongPrefixes, EmmParserWrongPrefixTest,
|
||||
::testing::Values(std::vector<uint8_t>({0}),
|
||||
std::vector<uint8_t>({1, 0, 0}),
|
||||
std::vector<uint8_t>({kSectionHeader, 0}),
|
||||
std::vector<uint8_t>({1, kSectionHeader, 0, 0})));
|
||||
|
||||
} // namespace
|
||||
} // namespace wvcas
|
||||
937
tests/src/license_key_status_test.cpp
Normal file
937
tests/src/license_key_status_test.cpp
Normal file
@@ -0,0 +1,937 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#include "license_key_status.h"
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "cas_types.h"
|
||||
|
||||
namespace wvcas {
|
||||
|
||||
namespace {
|
||||
|
||||
static const uint32_t dev_lo_res = 200;
|
||||
static const uint32_t dev_hi_res = 400;
|
||||
static const uint32_t dev_top_res = 800;
|
||||
|
||||
static const uint32_t key_lo_res_min = 151;
|
||||
static const uint32_t key_lo_res_max = 300;
|
||||
static const uint32_t key_hi_res_min = 301;
|
||||
static const uint32_t key_hi_res_max = 450;
|
||||
static const uint32_t key_top_res_min = 451;
|
||||
static const uint32_t key_top_res_max = 650;
|
||||
|
||||
// Content Keys
|
||||
static const KeyId ck_sw_crypto = "c_key_SW_SECURE_CRYPTO";
|
||||
static const KeyId ck_sw_decode = "c_key_SW_SECURE_DECODE";
|
||||
static const KeyId ck_hw_crypto = "c_key_HW_SECURE_CRYPTO";
|
||||
static const KeyId ck_hw_decode = "c_key_HW_SECURE_DECODE";
|
||||
static const KeyId ck_hw_secure = "c_key_HW_SECURE_ALL";
|
||||
|
||||
// Operator Session Keys
|
||||
static const KeyId osk_decrypt = "os_key_generic_decrypt";
|
||||
static const KeyId osk_encrypt = "os_key_generic_encrypt";
|
||||
static const KeyId osk_sign = "os_key_generic_sign";
|
||||
static const KeyId osk_verify = "os_key_generic_verify";
|
||||
static const KeyId osk_encrypt_decrypt = "os_key_generic_encrypt_decrypt";
|
||||
static const KeyId osk_sign_verify = "os_key_generic_sign_verify";
|
||||
static const KeyId osk_all = "os_key_generic_all";
|
||||
|
||||
// HDCP test keys
|
||||
static const KeyId ck_sw_crypto_NO_HDCP = "ck_sw_crypto_NO_HDCP";
|
||||
static const KeyId ck_hw_secure_NO_HDCP = "ck_hw_secure_NO_HDCP";
|
||||
static const KeyId ck_sw_crypto_HDCP_V2_1 = "ck_sw_crypto_HDCP_V2_1";
|
||||
static const KeyId ck_hw_secure_HDCP_V2_1 = "ck_hw_secure_HDCP_V2_1";
|
||||
static const KeyId ck_sw_crypto_HDCP_NO_OUTPUT = "ck_sw_crypto_HDCP_NO_OUT";
|
||||
static const KeyId ck_hw_secure_HDCP_NO_OUTPUT = "ck_hw_secure_HDCP_NO_OUT";
|
||||
|
||||
// Constraint test keys
|
||||
static const KeyId ck_NO_HDCP_lo_res = "ck_NO_HDCP_lo_res";
|
||||
static const KeyId ck_HDCP_NO_OUTPUT_hi_res = "ck_HDCP_NO_OUTPUT_hi_res";
|
||||
static const KeyId ck_HDCP_V2_1_max_res = "ck_HDCP_V2_1_max_res";
|
||||
static const KeyId ck_NO_HDCP_dual_res = "ck_NO_HDCP_dual_res";
|
||||
|
||||
} // namespace
|
||||
|
||||
// protobuf generated classes.
|
||||
using video_widevine::License;
|
||||
using video_widevine::LicenseIdentification;
|
||||
using video_widevine::OFFLINE;
|
||||
using video_widevine::STREAMING;
|
||||
|
||||
typedef ::video_widevine::License::KeyContainer KeyContainer;
|
||||
typedef KeyContainer::VideoResolutionConstraint VideoResolutionConstraint;
|
||||
|
||||
class LicenseKeysTest : public ::testing::Test {
|
||||
protected:
|
||||
enum KeyFlag { kKeyFlagNull, kKeyFlagFalse, kKeyFlagTrue };
|
||||
|
||||
static const KeyFlag kEncryptNull = kKeyFlagNull;
|
||||
static const KeyFlag kEncryptFalse = kKeyFlagFalse;
|
||||
static const KeyFlag kEncryptTrue = kKeyFlagTrue;
|
||||
static const KeyFlag kDecryptNull = kKeyFlagNull;
|
||||
static const KeyFlag kDecryptFalse = kKeyFlagFalse;
|
||||
static const KeyFlag kDecryptTrue = kKeyFlagTrue;
|
||||
static const KeyFlag kSignNull = kKeyFlagNull;
|
||||
static const KeyFlag kSignFalse = kKeyFlagFalse;
|
||||
static const KeyFlag kSignTrue = kKeyFlagTrue;
|
||||
static const KeyFlag kVerifyNull = kKeyFlagNull;
|
||||
static const KeyFlag kVerifyFalse = kKeyFlagFalse;
|
||||
static const KeyFlag kVerifyTrue = kKeyFlagTrue;
|
||||
|
||||
static const KeyFlag kContentSecureFalse = kKeyFlagFalse;
|
||||
static const KeyFlag kContentSecureTrue = kKeyFlagTrue;
|
||||
static const KeyFlag kContentClearFalse = kKeyFlagFalse;
|
||||
static const KeyFlag kContentClearTrue = kKeyFlagTrue;
|
||||
|
||||
virtual void SetUp() {
|
||||
LicenseIdentification* id = license_.mutable_id();
|
||||
id->set_version(1);
|
||||
id->set_type(STREAMING);
|
||||
}
|
||||
|
||||
virtual void AddContentKey(
|
||||
const KeyId& key_id, bool set_level = false,
|
||||
KeyContainer::SecurityLevel level = KeyContainer::SW_SECURE_CRYPTO,
|
||||
bool set_hdcp = false,
|
||||
KeyContainer::OutputProtection::HDCP hdcp_value =
|
||||
KeyContainer::OutputProtection::HDCP_NONE,
|
||||
bool set_constraints = false,
|
||||
std::vector<VideoResolutionConstraint>* 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<VideoResolutionConstraint>::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<VideoResolutionConstraint>* 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<VideoResolutionConstraint>& 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<VideoResolutionConstraint> 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<WidevinePsshData_EntitledKey> 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
|
||||
154
tests/src/mediacas_integration_test.cpp
Normal file
154
tests/src/mediacas_integration_test.cpp
Normal file
@@ -0,0 +1,154 @@
|
||||
// Dynamically generated header created during build. The Runtest entry point is
|
||||
// defined in gopkg_carchive.go
|
||||
|
||||
#include "golang/src/gowvcas_carchive.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
extern "C" int RunTest(GoString);
|
||||
|
||||
constexpr int kIntegrationTestPassed = 0;
|
||||
|
||||
// Invokes a test. Tests are named to allow them to be run individually. This
|
||||
// may be the best solution. The downside is the test prints PASS for each
|
||||
// individual test.
|
||||
|
||||
// It is also possible to not address them individually but run
|
||||
// them all as a batch. If running the tests as a single batch it may be
|
||||
// possible to pass command line flags to the test to indicate individual tests.
|
||||
// The downside of this is that it forces a user to pass in two command line
|
||||
// flags for each test.
|
||||
int RunNamedTest(const std::string testname) {
|
||||
GoString go_testname = {
|
||||
p : testname.data(),
|
||||
n : static_cast<unsigned>(testname.size())
|
||||
};
|
||||
return RunTest(go_testname);
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestCasFactoryCreation) {
|
||||
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestCasFactoryCreation"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestCreateCasPlugin) {
|
||||
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestCreateCasPlugin"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestCreateCasPluginWithNewCasIds) {
|
||||
EXPECT_EQ(kIntegrationTestPassed,
|
||||
RunNamedTest("TestCreateCasPluginWithNewCasIds"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestCreateCasPluginWithInvalidCasIds) {
|
||||
EXPECT_EQ(kIntegrationTestPassed,
|
||||
RunNamedTest("TestCreateCasPluginWithInvalidCasIds"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestCreateCasPluginWithSessionEvent) {
|
||||
EXPECT_EQ(kIntegrationTestPassed,
|
||||
RunNamedTest("TestCreateCasPluginWithSessionEvent"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestCreateCasPluginWithSessionEventWithNewCasIds) {
|
||||
EXPECT_EQ(kIntegrationTestPassed,
|
||||
RunNamedTest("TestCreateCasPluginWithSessionEventWithNewCasIds"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestCasPluginEventPassing) {
|
||||
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestCasPluginEventPassing"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestSessionFailWithoutProvisioning) {
|
||||
EXPECT_EQ(kIntegrationTestPassed,
|
||||
RunNamedTest("TestSessionFailWithoutProvisioning"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestUniqueIdQuery) {
|
||||
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestUniqueIdQuery"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestCasPluginProvision) {
|
||||
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestCasPluginProvision"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestCasPluginEmmRequestWithInitData) {
|
||||
EXPECT_EQ(kIntegrationTestPassed,
|
||||
RunNamedTest("TestCasPluginEmmRequestWithInitData"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestCasEmmRequestWithPrivateData) {
|
||||
EXPECT_EQ(kIntegrationTestPassed,
|
||||
RunNamedTest("TestCasEmmRequestWithPrivateData"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestCasWithOfflineEMM) {
|
||||
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestCasWithOfflineEMM"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestCasCanStoreOfflineEMM) {
|
||||
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestCasCanStoreOfflineEMM"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestCasCanNotStoreOfflineEMM) {
|
||||
EXPECT_EQ(kIntegrationTestPassed,
|
||||
RunNamedTest("TestCasCanNotStoreOfflineEMM"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestSession) {
|
||||
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestSession"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestCasRenewal) {
|
||||
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestCasRenewal"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestRestoreRenewalAndExpiredLicense) {
|
||||
EXPECT_EQ(kIntegrationTestPassed,
|
||||
RunNamedTest("TestRestoreRenewalAndExpiredLicense"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestLicenseExpiration) {
|
||||
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestLicenseExpiration"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestParentalControl) {
|
||||
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestParentalControl"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestRemoveLicense) {
|
||||
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestRemoveLicense"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestSessionEventPassing) {
|
||||
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestSessionEventPassing"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestProcessEcmV3) {
|
||||
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestProcessEcmV3"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestGroupLicense) {
|
||||
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestGroupLicense"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestMultiContentLicense) {
|
||||
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestMultiContentLicense"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestAssignGroupLicense) {
|
||||
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestAssignGroupLicense"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestLicenseRequestWithEntitlementPeriodIndex) {
|
||||
EXPECT_EQ(kIntegrationTestPassed,
|
||||
RunNamedTest("TestLicenseRequestWithEntitlementPeriodIndex"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestOfflineLicenseWithEntitlementPeriodIndex) {
|
||||
EXPECT_EQ(kIntegrationTestPassed,
|
||||
RunNamedTest("TestOfflineLicenseWithEntitlementPeriodIndex"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestNewLicenseRequestWithOutdatedOfflineLicense) {
|
||||
EXPECT_EQ(kIntegrationTestPassed,
|
||||
RunNamedTest("TestNewLicenseRequestWithOutdatedOfflineLicense"));
|
||||
}
|
||||
93
tests/src/mock_crypto_session.h
Normal file
93
tests/src/mock_crypto_session.h
Normal file
@@ -0,0 +1,93 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#ifndef MOCK_CRYPTO_SESSION_H
|
||||
#define MOCK_CRYPTO_SESSION_H
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "crypto_session.h"
|
||||
|
||||
class MockCryptoSession : public wvcas::CryptoSession {
|
||||
public:
|
||||
MockCryptoSession() {}
|
||||
virtual ~MockCryptoSession() {}
|
||||
|
||||
MOCK_METHOD0(initialize, wvcas::CasStatus());
|
||||
MOCK_METHOD0(reset, wvcas::CasStatus());
|
||||
MOCK_METHOD0(close, wvcas::CasStatus());
|
||||
MOCK_METHOD0(provisioning_method, wvcas::CasProvisioningMethod());
|
||||
MOCK_METHOD2(GetKeyData,
|
||||
wvcas::CasStatus(uint8_t* keyData, size_t* keyDataLength));
|
||||
MOCK_METHOD0(supported_certificates, wvcas::SupportedCertificates());
|
||||
MOCK_METHOD1(APIVersion, wvcas::CasStatus(uint32_t* api_version));
|
||||
MOCK_METHOD1(GenerateNonce, wvcas::CasStatus(uint32_t* nonce));
|
||||
MOCK_METHOD4(GenerateDerivedKeys,
|
||||
wvcas::CasStatus(const uint8_t* mac_key_context,
|
||||
uint32_t mac_key_context_length,
|
||||
const uint8_t* enc_key_context,
|
||||
uint32_t enc_key_context_length));
|
||||
MOCK_METHOD5(PrepareAndSignLicenseRequest,
|
||||
wvcas::CasStatus(const std::string& message,
|
||||
std::string* core_message,
|
||||
std::string* signature, bool&,
|
||||
OEMCrypto_SignatureHashAlgorithm&)
|
||||
);
|
||||
MOCK_METHOD3(PrepareAndSignRenewalRequest,
|
||||
wvcas::CasStatus(const std::string& message,
|
||||
std::string* core_message,
|
||||
std::string* signature));
|
||||
MOCK_METHOD5(PrepareAndSignProvisioningRequest,
|
||||
wvcas::CasStatus(const std::string& message,
|
||||
std::string* core_message,
|
||||
std::string* signature, bool&,
|
||||
OEMCrypto_SignatureHashAlgorithm&)
|
||||
);
|
||||
MOCK_METHOD4(LoadProvisioning,
|
||||
wvcas::CasStatus(const std::string& signed_message,
|
||||
const std::string& core_message,
|
||||
const std::string& signature,
|
||||
std::string* wrapped_private_key));
|
||||
MOCK_METHOD2(GetOEMPublicCertificate,
|
||||
wvcas::CasStatus(uint8_t* public_cert,
|
||||
size_t* public_cert_length));
|
||||
MOCK_METHOD5(GenerateRSASignature,
|
||||
wvcas::CasStatus(const uint8_t* message, size_t message_length,
|
||||
uint8_t* signature, size_t* signature_length,
|
||||
RSA_Padding_Scheme padding_scheme));
|
||||
MOCK_METHOD6(DeriveKeysFromSessionKey,
|
||||
wvcas::CasStatus(const uint8_t* enc_session_key,
|
||||
size_t enc_session_key_length,
|
||||
const uint8_t* mac_key_context,
|
||||
size_t mac_key_context_length,
|
||||
const uint8_t* enc_key_context,
|
||||
size_t enc_key_context_length));
|
||||
MOCK_METHOD3(LoadLicense, wvcas::CasStatus(const std::string& signed_message,
|
||||
const std::string& core_message,
|
||||
const std::string& signature));
|
||||
MOCK_METHOD3(LoadRenewal, wvcas::CasStatus(const std::string& signed_message,
|
||||
const std::string& core_message,
|
||||
const std::string& signature));
|
||||
MOCK_METHOD3(LoadCasECMKeys, wvcas::CasStatus(OEMCrypto_SESSION session,
|
||||
const wvcas::KeySlot* even_key,
|
||||
const wvcas::KeySlot* odd_key));
|
||||
MOCK_METHOD2(GetHdcpCapabilities, bool(wvcas::HdcpCapability* current,
|
||||
wvcas::HdcpCapability* max));
|
||||
MOCK_METHOD1(GetDeviceID, wvcas::CasStatus(std::string* buffer));
|
||||
MOCK_METHOD2(LoadDeviceRSAKey,
|
||||
wvcas::CasStatus(const uint8_t* wrapped_rsa_key,
|
||||
size_t wrapped_rsa_key_length));
|
||||
MOCK_METHOD1(CreateEntitledKeySession,
|
||||
wvcas::CasStatus(uint32_t* entitled_key_session_id));
|
||||
MOCK_METHOD1(RemoveEntitledKeySession,
|
||||
wvcas::CasStatus(uint32_t entitled_key_session_id));
|
||||
MOCK_METHOD(wvcas::CasStatus, ReassociateEntitledKeySession,
|
||||
(uint32_t entitled_key_session_id));
|
||||
MOCK_METHOD(wvcas::CasStatus, GetOEMKeyToken,
|
||||
(OEMCrypto_SESSION entitled_key_session_id,
|
||||
std::vector<uint8_t>& token));
|
||||
};
|
||||
|
||||
#endif // MOCK_CRYPTO_SESSION_H
|
||||
45
tests/src/mock_ecm_parser.h
Normal file
45
tests/src/mock_ecm_parser.h
Normal file
@@ -0,0 +1,45 @@
|
||||
// Copyright 2021 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#ifndef MOCK_ECM_PARSER_H
|
||||
#define MOCK_ECM_PARSER_H
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "ecm_parser.h"
|
||||
|
||||
class MockEcmParser : public wvcas::EcmParser {
|
||||
public:
|
||||
MOCK_METHOD(uint8_t, version, (), (const, override));
|
||||
MOCK_METHOD(uint8_t, age_restriction, (), (const, override));
|
||||
MOCK_METHOD(wvcas::CryptoMode, crypto_mode, (), (const, override));
|
||||
MOCK_METHOD(bool, rotation_enabled, (), (const, override));
|
||||
MOCK_METHOD(size_t, content_iv_size, (), (const, override));
|
||||
MOCK_METHOD(std::vector<uint8_t>, entitlement_key_id, (wvcas::KeySlotId id),
|
||||
(const, override));
|
||||
MOCK_METHOD(std::vector<uint8_t>, content_key_id, (wvcas::KeySlotId id),
|
||||
(const, override));
|
||||
MOCK_METHOD(std::vector<uint8_t>, wrapped_key_data, (wvcas::KeySlotId id),
|
||||
(const, override));
|
||||
MOCK_METHOD(std::vector<uint8_t>, wrapped_key_iv, (wvcas::KeySlotId id),
|
||||
(const, override));
|
||||
MOCK_METHOD(std::vector<uint8_t>, content_iv, (wvcas::KeySlotId id),
|
||||
(const, override));
|
||||
MOCK_METHOD(bool, set_group_id, (const std::string& group_id), (override));
|
||||
MOCK_METHOD(bool, has_fingerprinting, (), (const, override));
|
||||
MOCK_METHOD(video_widevine::Fingerprinting, fingerprinting, (),
|
||||
(const, override));
|
||||
MOCK_METHOD(bool, has_service_blocking, (), (const, override));
|
||||
MOCK_METHOD(video_widevine::ServiceBlocking, service_blocking, (),
|
||||
(const, override));
|
||||
MOCK_METHOD(std::string, ecm_serialized_payload, (), (const, override));
|
||||
MOCK_METHOD(std::string, signature, (), (const, override));
|
||||
MOCK_METHOD(bool, is_entitlement_rotation_enabled, (), (const, override));
|
||||
MOCK_METHOD(uint32_t, entitlement_period_index, (), (const, override));
|
||||
MOCK_METHOD(uint32_t, entitlement_rotation_window_left, (),
|
||||
(const, override));
|
||||
};
|
||||
|
||||
#endif // MOCK_ECM_PARSER_H
|
||||
47
tests/src/mock_event_listener.h
Normal file
47
tests/src/mock_event_listener.h
Normal file
@@ -0,0 +1,47 @@
|
||||
// Copyright 2021 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#ifndef MOCK_EVENT_LISTENER_H
|
||||
#define MOCK_EVENT_LISTENER_H
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "cas_types.h"
|
||||
|
||||
class MockEventListener : public wvcas::CasEventListener {
|
||||
public:
|
||||
MockEventListener() {}
|
||||
virtual ~MockEventListener() {}
|
||||
|
||||
MOCK_METHOD(void, OnSessionRenewalNeeded, (), (override));
|
||||
MOCK_METHOD(void, OnSessionKeysChange,
|
||||
(const wvcas::KeyStatusMap& keys_status, bool has_new_usable_key),
|
||||
(override));
|
||||
MOCK_METHOD(void, OnExpirationUpdate, (int64_t new_expiry_time_seconds),
|
||||
(override));
|
||||
MOCK_METHOD(void, OnNewRenewalServerUrl,
|
||||
(const std::string& renewal_server_url), (override));
|
||||
MOCK_METHOD(void, OnLicenseExpiration, (), (override));
|
||||
MOCK_METHOD(void, OnAgeRestrictionUpdated,
|
||||
(const wvcas::WvCasSessionId& sessionId,
|
||||
uint8_t ecm_age_restriction),
|
||||
(override));
|
||||
MOCK_METHOD(void, OnSessionFingerprintingUpdated,
|
||||
(const wvcas::WvCasSessionId& sessionId,
|
||||
const std::vector<uint8_t>& fingerprinting),
|
||||
(override));
|
||||
MOCK_METHOD(void, OnSessionServiceBlockingUpdated,
|
||||
(const wvcas::WvCasSessionId& sessionId,
|
||||
const std::vector<uint8_t>& service_blocking),
|
||||
(override));
|
||||
MOCK_METHOD(void, OnFingerprintingUpdated,
|
||||
(const std::vector<uint8_t>& fingerprinting), (override));
|
||||
MOCK_METHOD(void, OnServiceBlockingUpdated,
|
||||
(const std::vector<uint8_t>& service_blocking), (override));
|
||||
MOCK_METHOD(void, OnEntitlementPeriodUpdateNeeded,
|
||||
(const std::string& signed_license_request), (override));
|
||||
};
|
||||
|
||||
#endif // MOCK_EVENT_LISTENER_H
|
||||
340
tests/src/policy_engine_test.cpp
Normal file
340
tests/src/policy_engine_test.cpp
Normal file
@@ -0,0 +1,340 @@
|
||||
#include "policy_engine.h"
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "cas_types.h"
|
||||
#include "cas_util.h"
|
||||
#include "clock.h"
|
||||
#include "license_key_status.h"
|
||||
#include "license_protocol.pb.h"
|
||||
#include "mock_crypto_session.h"
|
||||
#include "mock_event_listener.h"
|
||||
|
||||
using ::testing::_;
|
||||
using ::testing::DoAll;
|
||||
using ::testing::NotNull;
|
||||
using ::testing::Return;
|
||||
using ::testing::Sequence;
|
||||
using ::testing::SetArgPointee;
|
||||
using ::testing::StrictMock;
|
||||
using wvcas::HdcpCapability;
|
||||
using wvcas::KeyAllowedUsage;
|
||||
using wvcas::KeyId;
|
||||
using wvcas::KeyStatus;
|
||||
using wvcas::KeyStatusMap;
|
||||
using wvcas::WidevinePsshData_EntitledKey;
|
||||
|
||||
namespace {
|
||||
const int64_t kDurationUnlimited = 0;
|
||||
const int64_t kLicenseStartTime = 1413517500; // ~ 01/01/2013
|
||||
const int64_t kPlaybackStartTime = kLicenseStartTime + 5;
|
||||
const int64_t kRentalDuration = 604800; // 7 days
|
||||
const int64_t kPlaybackDuration = 172800; // 48 hours
|
||||
const int64_t kLicenseDuration = kRentalDuration + kPlaybackDuration;
|
||||
const int64_t kLicenseRenewalPeriod = 120; // 2 minutes
|
||||
const int64_t kLicenseRenewalRetryInterval = 30; // 30 seconds
|
||||
const int64_t kLicenseRenewalRecoveryDuration = 30; // 30 seconds
|
||||
const int64_t kLowDuration = 300; // 5 minutes
|
||||
const int64_t kHighDuration =
|
||||
std::max(std::max(kRentalDuration, kPlaybackDuration), kLicenseDuration);
|
||||
const char* kRenewalServerUrl =
|
||||
"https://test.google.com/license/GetCencLicense";
|
||||
const KeyId kKeyId = "357adc89f1673433c36c621f1b5c41ee";
|
||||
const KeyId kEntitlementKeyId = "entitlementkeyid";
|
||||
const KeyId kAnotherKeyId = "another_key_id";
|
||||
const KeyId kSomeRandomKeyId = "some_random_key_id";
|
||||
const KeyId kUnknownKeyId = "some_random_unknown_key_id";
|
||||
} // namespace
|
||||
|
||||
class MockClock : public wvutil::Clock {
|
||||
public:
|
||||
MockClock() {}
|
||||
virtual ~MockClock() {}
|
||||
|
||||
MOCK_METHOD0(GetCurrentTime, int64_t());
|
||||
};
|
||||
|
||||
class MockLicenseKeys : public wvcas::LicenseKeys {
|
||||
public:
|
||||
MockLicenseKeys() {}
|
||||
virtual ~MockLicenseKeys() {}
|
||||
|
||||
MOCK_METHOD0(Empty, bool());
|
||||
MOCK_METHOD1(IsContentKey, bool(const KeyId& key_id));
|
||||
MOCK_METHOD1(CanDecryptContent, bool(const KeyId& key_id));
|
||||
MOCK_METHOD2(GetAllowedUsage,
|
||||
bool(const KeyId& key_id, KeyAllowedUsage* allowed_usage));
|
||||
MOCK_METHOD2(ApplyStatusChange,
|
||||
bool(KeyStatus new_status, bool* new_usable_keys));
|
||||
MOCK_METHOD1(GetKeyStatus, KeyStatus(const KeyId& key_id));
|
||||
MOCK_METHOD1(ExtractKeyStatuses, void(KeyStatusMap* content_keys));
|
||||
MOCK_METHOD1(MeetsConstraints, bool(const KeyId& key_id));
|
||||
MOCK_METHOD2(ApplyConstraints,
|
||||
void(uint32_t new_resolution, HdcpCapability new_hdcp_level));
|
||||
MOCK_METHOD1(SetFromLicense, void(const video_widevine::License& license));
|
||||
MOCK_METHOD1(SetEntitledKeys,
|
||||
void(const std::vector<WidevinePsshData_EntitledKey>& keys));
|
||||
};
|
||||
|
||||
class TestablePolicyEngine : public wvcas::PolicyEngine {
|
||||
std::unique_ptr<wvcas::LicenseKeys> CreateLicenseKeys() override {
|
||||
std::unique_ptr<StrictMock<MockLicenseKeys> > license_keys =
|
||||
make_unique<StrictMock<MockLicenseKeys> >();
|
||||
license_keys_ = license_keys.get();
|
||||
return license_keys;
|
||||
}
|
||||
std::unique_ptr<wvutil::Clock> CreateClock() override {
|
||||
std::unique_ptr<StrictMock<MockClock> > clock =
|
||||
make_unique<StrictMock<MockClock> >();
|
||||
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<StrictMock<MockCryptoSession> >();
|
||||
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<StrictMock<MockCryptoSession> > 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<WidevinePsshData_EntitledKey> 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_);
|
||||
}
|
||||
71
tests/src/test_properties.cpp
Normal file
71
tests/src/test_properties.cpp
Normal file
@@ -0,0 +1,71 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#include "cas_properties.h"
|
||||
|
||||
// TODO(widevine-eng): Reevaluate using "www" in the company name and model name
|
||||
// fields. For now this is consistent with the values used in unit cdm testing.
|
||||
static constexpr char kGenericCompanyName[] = "www";
|
||||
static constexpr char kGenericModelName[] = "www";
|
||||
static constexpr char kProductName[] = "WidevineCasTests";
|
||||
static constexpr char kKeyArchitectureName[] = "architecture_name";
|
||||
static constexpr char kKeyDeviceName[] = "device_name";
|
||||
static constexpr char kOemcPath[] = "cas_oemc_path.so";
|
||||
|
||||
namespace wvcas {
|
||||
|
||||
bool Properties::GetCompanyName(std::string* company_name) {
|
||||
if (company_name == nullptr) {
|
||||
return false;
|
||||
}
|
||||
*company_name = kGenericCompanyName;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Properties::GetModelName(std::string* model_name) {
|
||||
if (model_name == nullptr) {
|
||||
return false;
|
||||
}
|
||||
*model_name = kGenericModelName;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Properties::GetProductName(std::string* product_name) {
|
||||
if (product_name == nullptr) {
|
||||
return false;
|
||||
}
|
||||
*product_name = kProductName;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Properties::GetArchitectureName(std::string* arch_name) {
|
||||
if (arch_name == nullptr) {
|
||||
return false;
|
||||
}
|
||||
*arch_name = kKeyArchitectureName;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Properties::GetDeviceName(std::string* device_name) {
|
||||
if (device_name == nullptr) {
|
||||
return false;
|
||||
}
|
||||
*device_name = kKeyDeviceName;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Properties::GetOEMCryptoPath(std::string* path) {
|
||||
if (path == nullptr) {
|
||||
return false;
|
||||
}
|
||||
*path = kOemcPath;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Properties::GetWvCasPluginVersion(std::string& version) {
|
||||
version = "unit-test";
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace wvcas
|
||||
35
tests/src/timer_test.cpp
Normal file
35
tests/src/timer_test.cpp
Normal file
@@ -0,0 +1,35 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#include "timer.h"
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "clock.h"
|
||||
|
||||
class TimerTest : public wvutil::TimerHandler, public ::testing::Test {
|
||||
public:
|
||||
void OnTimerEvent() override { ++count_; }
|
||||
wvutil::Clock clock_;
|
||||
wvutil::Timer timer_;
|
||||
uint64_t event_time_ = 0;
|
||||
uint64_t count_ = 0;
|
||||
};
|
||||
|
||||
TEST_F(TimerTest, Timer) {
|
||||
timer_.Start(this, 1);
|
||||
EXPECT_TRUE(timer_.IsRunning());
|
||||
sleep(2);
|
||||
timer_.Stop();
|
||||
EXPECT_EQ(2, count_);
|
||||
EXPECT_FALSE(timer_.IsRunning());
|
||||
|
||||
timer_.Start(this, 1);
|
||||
EXPECT_TRUE(timer_.IsRunning());
|
||||
sleep(3);
|
||||
timer_.Stop();
|
||||
EXPECT_EQ(5, count_);
|
||||
EXPECT_FALSE(timer_.IsRunning());
|
||||
}
|
||||
1396
tests/src/widevine_cas_api_test.cpp
Normal file
1396
tests/src/widevine_cas_api_test.cpp
Normal file
File diff suppressed because it is too large
Load Diff
436
tests/src/widevine_cas_session_test.cpp
Normal file
436
tests/src/widevine_cas_session_test.cpp
Normal file
@@ -0,0 +1,436 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#include "widevine_cas_session.h"
|
||||
|
||||
#include <cas_events.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "cas_types.h"
|
||||
#include "cas_util.h"
|
||||
#include "media_cas.pb.h"
|
||||
#include "mock_crypto_session.h"
|
||||
#include "mock_ecm_parser.h"
|
||||
#include "mock_event_listener.h"
|
||||
#include "string_conversions.h"
|
||||
|
||||
namespace wvcas {
|
||||
namespace {
|
||||
using ::testing::_;
|
||||
using ::testing::DoAll;
|
||||
using ::testing::Eq;
|
||||
using ::testing::Invoke;
|
||||
using ::testing::NiceMock;
|
||||
using ::testing::NotNull;
|
||||
using ::testing::Return;
|
||||
using ::testing::SetArgPointee;
|
||||
using ::testing::StrictMock;
|
||||
|
||||
static const char kEvenEntitlementKeyId[] = "even_entitlement_key_id";
|
||||
static const char kOddEntitlementKeyId[] = "odd_entitlement_key_id";
|
||||
static const char kEvenKeyId[] = "even_key_id";
|
||||
static const char kOddKeyId[] = "odd_key_id";
|
||||
static const char kEvenWrappedKey[] = "even_wrapped_content_key";
|
||||
static const char kOddWrappedKey[] = "odd_wrapped_content_key";
|
||||
static const char kEvenWrappedKeyIv[] = "even_wrapped_content_key_iv";
|
||||
static const char kOddWrappedKeyIv[] = "odd_wrapped_content_key_iv";
|
||||
static const char kEvenContentIv[] = "even_content_iv";
|
||||
static const char kOddContentIv[] = "odd_content_iv";
|
||||
static const OEMCrypto_SESSION kEntitledKeySessionId = 0x1111;
|
||||
constexpr char kEmptyGroupId[] = "";
|
||||
|
||||
MATCHER(IsValidKeyEvenSlotData, "") {
|
||||
if (nullptr == arg) {
|
||||
*result_listener << " keyslot is nullptr";
|
||||
return false;
|
||||
}
|
||||
std::string value = kEvenEntitlementKeyId;
|
||||
if (arg->entitlement_key_id !=
|
||||
std::vector<uint8_t>(value.begin(), value.end())) {
|
||||
*result_listener << " entitlement key_id is invalid";
|
||||
return false;
|
||||
}
|
||||
value = kEvenKeyId;
|
||||
if (arg->key_id != std::vector<uint8_t>(value.begin(), value.end())) {
|
||||
*result_listener << " key_id is invalid";
|
||||
return false;
|
||||
}
|
||||
value = kEvenWrappedKey;
|
||||
if (arg->wrapped_key != std::vector<uint8_t>(value.begin(), value.end())) {
|
||||
*result_listener << " wrapped_key is invalid";
|
||||
return false;
|
||||
}
|
||||
value = kEvenWrappedKeyIv;
|
||||
if (arg->wrapped_key_iv != std::vector<uint8_t>(value.begin(), value.end())) {
|
||||
*result_listener << " wapped_key_iv is invalid";
|
||||
return false;
|
||||
}
|
||||
value = kEvenContentIv;
|
||||
if (arg->content_iv != std::vector<uint8_t>(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<uint8_t>(value.begin(), value.end())) {
|
||||
*result_listener << " entitlement key_id is invalid";
|
||||
return false;
|
||||
}
|
||||
value = kOddKeyId;
|
||||
if (arg->key_id != std::vector<uint8_t>(value.begin(), value.end())) {
|
||||
*result_listener << " key_id is invalid";
|
||||
return false;
|
||||
}
|
||||
value = kOddWrappedKey;
|
||||
if (arg->wrapped_key != std::vector<uint8_t>(value.begin(), value.end())) {
|
||||
*result_listener << " wrapped_key is invalid";
|
||||
return false;
|
||||
}
|
||||
value = kOddWrappedKeyIv;
|
||||
if (arg->wrapped_key_iv != std::vector<uint8_t>(value.begin(), value.end())) {
|
||||
*result_listener << " wapped_key_iv is invalid";
|
||||
return false;
|
||||
}
|
||||
value = kOddContentIv;
|
||||
if (arg->content_iv != std::vector<uint8_t>(value.begin(), value.end())) {
|
||||
*result_listener << " content_iv is invalid";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
class CasSessionTest : public ::testing::Test {
|
||||
public:
|
||||
CasSessionTest() {}
|
||||
virtual ~CasSessionTest() {}
|
||||
|
||||
void SetUp() override {
|
||||
mock_crypto_session_ = std::make_shared<MockCryptoSession>();
|
||||
ON_CALL(*mock_crypto_session_, CreateEntitledKeySession(NotNull()))
|
||||
.WillByDefault(DoAll(SetArgPointee<0>(kEntitledKeySessionId),
|
||||
Return(wvcas::CasStatusCode::kNoError)));
|
||||
ON_CALL(*mock_crypto_session_, GetOEMKeyToken(kEntitledKeySessionId, _))
|
||||
.WillByDefault([&](OEMCrypto_SESSION, std::vector<uint8_t>& token) {
|
||||
token.assign(expected_session_id_.begin(),
|
||||
expected_session_id_.end());
|
||||
return CasStatusCode::kNoError;
|
||||
});
|
||||
}
|
||||
|
||||
std::shared_ptr<MockCryptoSession> mock_crypto_session_;
|
||||
WvCasSessionId expected_session_id_ = {0x01, 0x02, 0x03};
|
||||
};
|
||||
|
||||
// Allow getEcmParser to return a mocked ecm.
|
||||
class TestCasSession : public wvcas::WidevineCasSession {
|
||||
public:
|
||||
TestCasSession() {}
|
||||
virtual ~TestCasSession() {}
|
||||
|
||||
std::unique_ptr<wvcas::EcmParser> getEcmParser(
|
||||
const wvcas::CasEcm& ecm) const override;
|
||||
|
||||
std::vector<uint8_t> 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<uint8_t>(key_id.begin(), key_id.end());
|
||||
}
|
||||
|
||||
std::vector<uint8_t> 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<uint8_t>(key_id.begin(), key_id.end());
|
||||
}
|
||||
|
||||
std::vector<uint8_t> 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<uint8_t>(key.begin(), key.end());
|
||||
}
|
||||
|
||||
std::vector<uint8_t> 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<uint8_t>(iv.begin(), iv.end());
|
||||
}
|
||||
|
||||
std::vector<uint8_t> 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<uint8_t>(iv.begin(), iv.end());
|
||||
}
|
||||
|
||||
void set_age_restriction(uint8_t age_restriction) {
|
||||
age_restriction_ = age_restriction;
|
||||
}
|
||||
|
||||
void set_fingerprinting_control(const std::string& control) {
|
||||
fingerprinting_.clear_control();
|
||||
if (!control.empty()) {
|
||||
fingerprinting_.set_control(control);
|
||||
}
|
||||
}
|
||||
|
||||
void set_service_blocking_groups(const std::vector<std::string>& groups) {
|
||||
service_blocking_.clear_device_groups();
|
||||
for (auto const& group : groups) {
|
||||
service_blocking_.add_device_groups(group);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t age_restriction_ = 0;
|
||||
video_widevine::Fingerprinting fingerprinting_;
|
||||
video_widevine::ServiceBlocking service_blocking_;
|
||||
};
|
||||
|
||||
std::unique_ptr<wvcas::EcmParser> TestCasSession::getEcmParser(
|
||||
const wvcas::CasEcm& ecm) const {
|
||||
std::unique_ptr<NiceMock<MockEcmParser>> mock_ecm_parser(
|
||||
new NiceMock<MockEcmParser>);
|
||||
ON_CALL(*mock_ecm_parser, age_restriction())
|
||||
.WillByDefault(Return(age_restriction_));
|
||||
ON_CALL(*mock_ecm_parser, crypto_mode())
|
||||
.WillByDefault(Return(wvcas::CryptoMode::kAesCTR));
|
||||
ON_CALL(*mock_ecm_parser, rotation_enabled()).WillByDefault(Return(true));
|
||||
ON_CALL(*mock_ecm_parser, entitlement_key_id(_))
|
||||
.WillByDefault(Invoke(this, &TestCasSession::entitlement_key_id));
|
||||
ON_CALL(*mock_ecm_parser, content_key_id(_))
|
||||
.WillByDefault(Invoke(this, &TestCasSession::content_key_id));
|
||||
ON_CALL(*mock_ecm_parser, wrapped_key_data(_))
|
||||
.WillByDefault(Invoke(this, &TestCasSession::wrapped_key_data));
|
||||
ON_CALL(*mock_ecm_parser, wrapped_key_iv(_))
|
||||
.WillByDefault(Invoke(this, &TestCasSession::wrapped_key_iv));
|
||||
ON_CALL(*mock_ecm_parser, content_iv(_))
|
||||
.WillByDefault(Invoke(this, &TestCasSession::content_iv));
|
||||
ON_CALL(*mock_ecm_parser, set_group_id(_)).WillByDefault(Return(true));
|
||||
ON_CALL(*mock_ecm_parser, has_fingerprinting())
|
||||
.WillByDefault(Return(fingerprinting_.has_control()));
|
||||
ON_CALL(*mock_ecm_parser, fingerprinting())
|
||||
.WillByDefault(Return(fingerprinting_));
|
||||
ON_CALL(*mock_ecm_parser, has_service_blocking())
|
||||
.WillByDefault(Return(service_blocking_.device_groups_size() > 0));
|
||||
ON_CALL(*mock_ecm_parser, service_blocking())
|
||||
.WillByDefault(Return(service_blocking_));
|
||||
return std::unique_ptr<wvcas::EcmParser>(mock_ecm_parser.release());
|
||||
}
|
||||
|
||||
TEST_F(CasSessionTest, sessionInitializeTest) {
|
||||
TestCasSession session;
|
||||
WvCasSessionId session_id;
|
||||
|
||||
ASSERT_EQ(session
|
||||
.initialize(mock_crypto_session_, /*event_listener=*/nullptr,
|
||||
&session_id)
|
||||
.status_code(),
|
||||
CasStatusCode::kNoError);
|
||||
|
||||
EXPECT_EQ(session_id, expected_session_id_);
|
||||
}
|
||||
|
||||
TEST_F(CasSessionTest, processEcm) {
|
||||
TestCasSession session;
|
||||
WvCasSessionId session_id;
|
||||
ASSERT_EQ(session
|
||||
.initialize(mock_crypto_session_, /*event_listener=*/nullptr,
|
||||
&session_id)
|
||||
.status_code(),
|
||||
CasStatusCode::kNoError);
|
||||
|
||||
wvcas::CasEcm ecm(184);
|
||||
EXPECT_CALL(*mock_crypto_session_,
|
||||
LoadCasECMKeys(kEntitledKeySessionId, IsValidKeyEvenSlotData(),
|
||||
IsValidKeyOddSlotData()));
|
||||
session.processEcm(ecm, 0, kEmptyGroupId);
|
||||
EXPECT_CALL(*mock_crypto_session_,
|
||||
RemoveEntitledKeySession(kEntitledKeySessionId));
|
||||
}
|
||||
|
||||
TEST_F(CasSessionTest, parentalControl) {
|
||||
TestCasSession session;
|
||||
WvCasSessionId session_id;
|
||||
ASSERT_EQ(session
|
||||
.initialize(mock_crypto_session_, /*event_listener=*/nullptr,
|
||||
&session_id)
|
||||
.status_code(),
|
||||
CasStatusCode::kNoError);
|
||||
|
||||
EXPECT_CALL(*mock_crypto_session_, LoadCasECMKeys(_, IsValidKeyEvenSlotData(),
|
||||
IsValidKeyOddSlotData()));
|
||||
wvcas::CasEcm ecm(184);
|
||||
session.set_age_restriction(0); // No restriction.
|
||||
// Different Ecm to make sure processEcm() processes this ecm.
|
||||
std::generate(ecm.begin(), ecm.end(), std::rand);
|
||||
ASSERT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
session.processEcm(ecm, 0, kEmptyGroupId).status_code());
|
||||
std::generate(ecm.begin(), ecm.end(), std::rand);
|
||||
ASSERT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
session.processEcm(ecm, 13, kEmptyGroupId).status_code());
|
||||
|
||||
// Parental control age must >= 10 (if non-zero).
|
||||
session.set_age_restriction(10);
|
||||
std::generate(ecm.begin(), ecm.end(), std::rand);
|
||||
ASSERT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
session.processEcm(ecm, 0, kEmptyGroupId).status_code());
|
||||
std::generate(ecm.begin(), ecm.end(), std::rand);
|
||||
ASSERT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
session.processEcm(ecm, 10, kEmptyGroupId).status_code());
|
||||
std::generate(ecm.begin(), ecm.end(), std::rand);
|
||||
ASSERT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
session.processEcm(ecm, 13, kEmptyGroupId).status_code());
|
||||
std::generate(ecm.begin(), ecm.end(), std::rand);
|
||||
ASSERT_EQ(wvcas::CasStatusCode::kAccessDeniedByParentalControl,
|
||||
session.processEcm(ecm, 3, kEmptyGroupId).status_code());
|
||||
EXPECT_CALL(*mock_crypto_session_, RemoveEntitledKeySession(_));
|
||||
}
|
||||
|
||||
TEST_F(CasSessionTest, FingerprintingSuccess) {
|
||||
TestCasSession session;
|
||||
auto mock_crypto = std::make_shared<MockCryptoSession>();
|
||||
MockEventListener mock_listener;
|
||||
WvCasSessionId session_id;
|
||||
ASSERT_EQ(session.initialize(mock_crypto, &mock_listener, &session_id)
|
||||
.status_code(),
|
||||
wvcas::CasStatusCode::kNoError);
|
||||
session.set_fingerprinting_control("control");
|
||||
std::vector<uint8_t> expected_message = {0x00, 0x00, 0x07, 'c', 'o',
|
||||
'n', 't', 'r', 'o', 'l'};
|
||||
EXPECT_CALL(mock_listener,
|
||||
OnSessionFingerprintingUpdated(session_id, expected_message))
|
||||
.Times(1);
|
||||
|
||||
session.processEcm(wvcas::CasEcm(184, '0'), 0, kEmptyGroupId);
|
||||
}
|
||||
|
||||
TEST_F(CasSessionTest, RepeatedFingerprintingNoEventSuccess) {
|
||||
TestCasSession session;
|
||||
auto mock_crypto = std::make_shared<MockCryptoSession>();
|
||||
MockEventListener mock_listener;
|
||||
WvCasSessionId session_id;
|
||||
ASSERT_EQ(session.initialize(mock_crypto, &mock_listener, &session_id)
|
||||
.status_code(),
|
||||
wvcas::CasStatusCode::kNoError);
|
||||
session.set_fingerprinting_control("control");
|
||||
EXPECT_CALL(mock_listener, OnSessionFingerprintingUpdated).Times(1);
|
||||
|
||||
session.processEcm(wvcas::CasEcm(184, '0'), 0, kEmptyGroupId);
|
||||
// Same fingerprinting will not trigger event.
|
||||
EXPECT_CALL(mock_listener, OnSessionFingerprintingUpdated).Times(0);
|
||||
session.processEcm(wvcas::CasEcm(184, '1'), 0, kEmptyGroupId);
|
||||
}
|
||||
|
||||
TEST_F(CasSessionTest, DifferentFingerprintingTriggerEventSuccess) {
|
||||
TestCasSession session;
|
||||
auto mock_crypto = std::make_shared<MockCryptoSession>();
|
||||
MockEventListener mock_listener;
|
||||
WvCasSessionId session_id;
|
||||
ASSERT_EQ(session.initialize(mock_crypto, &mock_listener, &session_id)
|
||||
.status_code(),
|
||||
wvcas::CasStatusCode::kNoError);
|
||||
session.set_fingerprinting_control("control");
|
||||
EXPECT_CALL(mock_listener, OnSessionFingerprintingUpdated).Times(1);
|
||||
|
||||
session.processEcm(wvcas::CasEcm(184, '0'), 0, kEmptyGroupId);
|
||||
// Different fingerprinting will trigger event.
|
||||
session.set_fingerprinting_control("control2");
|
||||
EXPECT_CALL(mock_listener, OnSessionFingerprintingUpdated).Times(1);
|
||||
session.processEcm(wvcas::CasEcm(184, '1'), 0, kEmptyGroupId);
|
||||
// Different fingerprinting (including empty) will trigger event.
|
||||
session.set_fingerprinting_control("");
|
||||
EXPECT_CALL(mock_listener, OnSessionFingerprintingUpdated).Times(1);
|
||||
session.processEcm(wvcas::CasEcm(184, '2'), 0, kEmptyGroupId);
|
||||
}
|
||||
|
||||
TEST_F(CasSessionTest, ServiceBlockingSuccess) {
|
||||
TestCasSession session;
|
||||
auto mock_crypto = std::make_shared<MockCryptoSession>();
|
||||
MockEventListener mock_listener;
|
||||
WvCasSessionId session_id;
|
||||
ASSERT_EQ(session.initialize(mock_crypto, &mock_listener, &session_id)
|
||||
.status_code(),
|
||||
wvcas::CasStatusCode::kNoError);
|
||||
session.set_service_blocking_groups({"Group1", "g2"});
|
||||
std::vector<uint8_t> expected_message = {0x00, 0x00, 0x06, 'G', 'r',
|
||||
'o', 'u', 'p', '1', 0x00,
|
||||
0x00, 0x02, 'g', '2'};
|
||||
EXPECT_CALL(mock_listener,
|
||||
OnSessionServiceBlockingUpdated(session_id, expected_message))
|
||||
.Times(1);
|
||||
|
||||
session.processEcm(wvcas::CasEcm(184, '0'), 0, kEmptyGroupId);
|
||||
}
|
||||
|
||||
TEST_F(CasSessionTest, RepeatedServiceBlockingNoEventSuccess) {
|
||||
TestCasSession session;
|
||||
auto mock_crypto = std::make_shared<MockCryptoSession>();
|
||||
MockEventListener mock_listener;
|
||||
WvCasSessionId session_id;
|
||||
ASSERT_EQ(session.initialize(mock_crypto, &mock_listener, &session_id)
|
||||
.status_code(),
|
||||
wvcas::CasStatusCode::kNoError);
|
||||
session.set_service_blocking_groups({"Group1", "g2"});
|
||||
EXPECT_CALL(mock_listener, OnSessionServiceBlockingUpdated).Times(1);
|
||||
|
||||
session.processEcm(wvcas::CasEcm(184, '0'), 0, kEmptyGroupId);
|
||||
EXPECT_CALL(mock_listener, OnSessionServiceBlockingUpdated).Times(0);
|
||||
session.processEcm(wvcas::CasEcm(184, '1'), 0, kEmptyGroupId);
|
||||
}
|
||||
|
||||
TEST_F(CasSessionTest, DifferentServiceBlockingTriggerEventSuccess) {
|
||||
TestCasSession session;
|
||||
auto mock_crypto = std::make_shared<MockCryptoSession>();
|
||||
MockEventListener mock_listener;
|
||||
WvCasSessionId session_id;
|
||||
ASSERT_EQ(session.initialize(mock_crypto, &mock_listener, &session_id)
|
||||
.status_code(),
|
||||
wvcas::CasStatusCode::kNoError);
|
||||
session.set_service_blocking_groups({"Group1", "g2"});
|
||||
EXPECT_CALL(mock_listener, OnSessionServiceBlockingUpdated).Times(1);
|
||||
|
||||
session.processEcm(wvcas::CasEcm(184, '0'), 0, kEmptyGroupId);
|
||||
EXPECT_CALL(mock_listener, OnSessionServiceBlockingUpdated).Times(1);
|
||||
session.set_service_blocking_groups({"Group1"});
|
||||
session.processEcm(wvcas::CasEcm(184, '1'), 0, kEmptyGroupId);
|
||||
EXPECT_CALL(mock_listener, OnSessionServiceBlockingUpdated).Times(1);
|
||||
session.set_service_blocking_groups({});
|
||||
session.processEcm(wvcas::CasEcm(184, '2'), 0, kEmptyGroupId);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace wvcas
|
||||
227
tests/src/widevine_media_cas_plugin_test.cpp
Normal file
227
tests/src/widevine_media_cas_plugin_test.cpp
Normal file
@@ -0,0 +1,227 @@
|
||||
// Copyright 2021 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#include "widevine_media_cas_plugin.h"
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <utils/String8.h>
|
||||
|
||||
#include "cas_events.h"
|
||||
#include "cas_status.h"
|
||||
#include "media/cas/CasAPI.h"
|
||||
#include "media/stagefright/MediaErrors.h"
|
||||
#include "widevine_cas_api.h"
|
||||
|
||||
namespace android {
|
||||
// Minimalist implementation of Android string class to support test.
|
||||
std::map<const String8*, std::unique_ptr<std::string> > string8s;
|
||||
|
||||
String8::String8(const String8& value)
|
||||
: String8(value.c_str(), value.length()) {}
|
||||
|
||||
String8::String8(char const* data, size_t data_length) {
|
||||
auto result =
|
||||
string8s.emplace(this, make_unique<std::string>(data, data_length));
|
||||
mString = result.first->second->data();
|
||||
}
|
||||
|
||||
size_t String8::length() const {
|
||||
auto it = string8s.find(this);
|
||||
return it == string8s.end() ? 0 : it->second->size();
|
||||
}
|
||||
|
||||
String8::~String8() { string8s.erase(this); }
|
||||
|
||||
} // namespace android
|
||||
|
||||
namespace wvcas {
|
||||
namespace {
|
||||
|
||||
using ::testing::_;
|
||||
using ::testing::DoAll;
|
||||
using ::testing::Eq;
|
||||
using ::testing::IsNull;
|
||||
using ::testing::NotNull;
|
||||
using ::testing::Return;
|
||||
using ::testing::SetArgPointee;
|
||||
using ::testing::SetArgReferee;
|
||||
|
||||
class MockWidevineCas : public WidevineCas {
|
||||
public:
|
||||
MockWidevineCas() {}
|
||||
~MockWidevineCas() override {}
|
||||
|
||||
MOCK_METHOD(CasStatus, openSession, (WvCasSessionId * sessionId), (override));
|
||||
MOCK_METHOD(bool, is_provisioned, (), (const, override));
|
||||
MOCK_METHOD(CasStatus, generateEntitlementRequest,
|
||||
(const std::string& init_data, std::string* entitlement_request,
|
||||
std::string& license_id),
|
||||
(override));
|
||||
MOCK_METHOD(CasStatus, RecordLicenseId, (const std::string& license_id),
|
||||
(override));
|
||||
MOCK_METHOD(CasStatus, handleEntitlementResponse,
|
||||
(const std::string& response, std::string& license_id,
|
||||
std::string& multi_content_license_info,
|
||||
std::string& group_license_info),
|
||||
(override));
|
||||
};
|
||||
|
||||
// Override WidevineCasPlugin to set WidevineCas and mock callbacks.
|
||||
class TestWidevineCasPlugin : public WidevineCasPlugin {
|
||||
public:
|
||||
TestWidevineCasPlugin() : WidevineCasPlugin() {}
|
||||
~TestWidevineCasPlugin() override {}
|
||||
|
||||
void SetWidevineCasApi(
|
||||
std::unique_ptr<WidevineCas> widevine_cas_api) override {
|
||||
WidevineCasPlugin::SetWidevineCasApi(std::move(widevine_cas_api));
|
||||
}
|
||||
|
||||
MOCK_METHOD(void, CallBack,
|
||||
(void* appData, int32_t event, int32_t arg, uint8_t* data,
|
||||
size_t size, const CasSessionId* sessionId),
|
||||
(const, override));
|
||||
};
|
||||
|
||||
TEST(WidevineCasPluginTest, openSessionSuccess) {
|
||||
TestWidevineCasPlugin plugin;
|
||||
auto pass_through_cas_api = make_unique<MockWidevineCas>();
|
||||
MockWidevineCas* cas_api = pass_through_cas_api.get();
|
||||
plugin.SetWidevineCasApi(std::move(pass_through_cas_api));
|
||||
const std::vector<uint8_t> expected_android_session_id = {0x78, 0x56, 0x34,
|
||||
0x12};
|
||||
const int32_t created_session_id = 0x12345678;
|
||||
EXPECT_CALL(*cas_api, is_provisioned).WillOnce(Return(true));
|
||||
EXPECT_CALL(*cas_api, openSession(NotNull()))
|
||||
.WillOnce(DoAll(SetArgPointee<0>(expected_android_session_id),
|
||||
Return(CasStatus::OkStatus())));
|
||||
EXPECT_CALL(plugin, CallBack(_, CAS_SESSION_ID, created_session_id, NotNull(),
|
||||
expected_android_session_id.size(), IsNull()));
|
||||
std::vector<uint8_t> session_id;
|
||||
|
||||
EXPECT_EQ(plugin.openSession(&session_id), android::OK);
|
||||
|
||||
EXPECT_EQ(session_id, expected_android_session_id);
|
||||
}
|
||||
|
||||
TEST(WidevineCasPluginTest, openSessionWithoutProvisionFail) {
|
||||
TestWidevineCasPlugin plugin;
|
||||
auto pass_through_cas_api = make_unique<MockWidevineCas>();
|
||||
MockWidevineCas* cas_api = pass_through_cas_api.get();
|
||||
plugin.SetWidevineCasApi(std::move(pass_through_cas_api));
|
||||
EXPECT_CALL(*cas_api, is_provisioned).WillOnce(Return(false));
|
||||
EXPECT_CALL(*cas_api, openSession(NotNull())).Times((0));
|
||||
std::vector<uint8_t> session_id;
|
||||
|
||||
EXPECT_EQ(plugin.openSession(&session_id),
|
||||
android::ERROR_CAS_NOT_PROVISIONED);
|
||||
}
|
||||
|
||||
TEST(WidevineCasPluginTest,
|
||||
provisionWithProvisionStringAlreadyProvisionedSuccess) {
|
||||
TestWidevineCasPlugin plugin;
|
||||
auto pass_through_cas_api = make_unique<MockWidevineCas>();
|
||||
MockWidevineCas* cas_api = pass_through_cas_api.get();
|
||||
plugin.SetWidevineCasApi(std::move(pass_through_cas_api));
|
||||
const std::string provision_string = "init_data";
|
||||
EXPECT_CALL(*cas_api, is_provisioned).WillOnce(Return(true));
|
||||
EXPECT_CALL(plugin, CallBack(_, INDIVIDUALIZATION_COMPLETE, _, _, _, _));
|
||||
// Provision string is init data; it triggers license request.
|
||||
EXPECT_CALL(*cas_api,
|
||||
generateEntitlementRequest(Eq(provision_string), NotNull(), _))
|
||||
.WillOnce(DoAll(SetArgPointee<1>("signed_license_request"),
|
||||
Return(CasStatus::OkStatus())));
|
||||
EXPECT_CALL(plugin, CallBack(_, LICENSE_REQUEST, _, _, _, _));
|
||||
|
||||
EXPECT_EQ(plugin.provision(android::String8(provision_string.c_str(),
|
||||
provision_string.size())),
|
||||
android::OK);
|
||||
}
|
||||
|
||||
TEST(WidevineCasPluginTest, HandleAssignLicenseIDSuccess) {
|
||||
TestWidevineCasPlugin plugin;
|
||||
auto pass_through_cas_api = make_unique<MockWidevineCas>();
|
||||
MockWidevineCas* cas_api = pass_through_cas_api.get();
|
||||
plugin.SetWidevineCasApi(std::move(pass_through_cas_api));
|
||||
const std::string license_id = "license_id";
|
||||
EXPECT_CALL(*cas_api, RecordLicenseId(license_id))
|
||||
.WillOnce(Return(CasStatus::OkStatus()));
|
||||
EXPECT_CALL(plugin, CallBack(_, LICENSE_ID_ASSIGNED, _, _, _, _)).Times(1);
|
||||
|
||||
EXPECT_EQ(plugin.sendEvent(ASSIGN_LICENSE_ID, /*arg=*/0,
|
||||
{license_id.begin(), license_id.end()}),
|
||||
android::OK);
|
||||
}
|
||||
|
||||
TEST(WidevineCasPluginTest, HandleAssignLicenseIDApiError) {
|
||||
TestWidevineCasPlugin plugin;
|
||||
auto pass_through_cas_api = make_unique<MockWidevineCas>();
|
||||
MockWidevineCas* cas_api = pass_through_cas_api.get();
|
||||
plugin.SetWidevineCasApi(std::move(pass_through_cas_api));
|
||||
const std::string license_id = "license_id";
|
||||
EXPECT_CALL(*cas_api, RecordLicenseId)
|
||||
.WillOnce(Return(CasStatus(CasStatusCode::kInvalidParameter, "invalid")));
|
||||
EXPECT_CALL(plugin, CallBack(_, CAS_ERROR, _, _, _, _)).Times(1);
|
||||
|
||||
EXPECT_NE(plugin.sendEvent(ASSIGN_LICENSE_ID, /*arg=*/0,
|
||||
{license_id.begin(), license_id.end()}),
|
||||
android::OK);
|
||||
}
|
||||
|
||||
TEST(WidevineCasPluginTest, HandleEntitlementResponseSuccess) {
|
||||
TestWidevineCasPlugin plugin;
|
||||
auto pass_through_cas_api = make_unique<MockWidevineCas>();
|
||||
MockWidevineCas* cas_api = pass_through_cas_api.get();
|
||||
plugin.SetWidevineCasApi(std::move(pass_through_cas_api));
|
||||
const std::string license = "license";
|
||||
const std::string license_id = "id";
|
||||
const std::string multi_content_license_info = "info";
|
||||
const std::string group_license_info = "info2";
|
||||
EXPECT_CALL(*cas_api, handleEntitlementResponse(_, _, _, _))
|
||||
.WillOnce(DoAll(SetArgReferee<1>(license_id),
|
||||
SetArgReferee<2>(multi_content_license_info),
|
||||
SetArgReferee<3>(group_license_info),
|
||||
Return(CasStatus::OkStatus())));
|
||||
EXPECT_CALL(plugin,
|
||||
CallBack(_, LICENSE_CAS_READY, _, _, license_id.size(), _))
|
||||
.Times(1);
|
||||
EXPECT_CALL(plugin, CallBack(_, MULTI_CONTENT_LICENSE_INFO, _, _,
|
||||
multi_content_license_info.size(), _))
|
||||
.Times(1);
|
||||
EXPECT_CALL(plugin, CallBack(_, GROUP_LICENSE_INFO, _, _,
|
||||
group_license_info.size(), _))
|
||||
.Times(1);
|
||||
|
||||
EXPECT_EQ(plugin.sendEvent(LICENSE_RESPONSE, /*arg=*/0,
|
||||
{license.begin(), license.end()}),
|
||||
android::OK);
|
||||
}
|
||||
|
||||
TEST(WidevineCasPluginTest, HandleEntitlementResponseEmptyResponseFail) {
|
||||
TestWidevineCasPlugin plugin;
|
||||
auto pass_through_cas_api = make_unique<MockWidevineCas>();
|
||||
plugin.SetWidevineCasApi(std::move(pass_through_cas_api));
|
||||
EXPECT_CALL(plugin, CallBack(_, CAS_ERROR, _, _, _, _)).Times(1);
|
||||
|
||||
EXPECT_NE(plugin.sendEvent(LICENSE_RESPONSE, /*arg=*/0, /*eventData=*/{}),
|
||||
android::OK);
|
||||
}
|
||||
|
||||
TEST(WidevineCasPluginTest, HandlePluginVersionQuerySuccess) {
|
||||
TestWidevineCasPlugin plugin;
|
||||
auto pass_through_cas_api = make_unique<MockWidevineCas>();
|
||||
plugin.SetWidevineCasApi(std::move(pass_through_cas_api));
|
||||
std::string expected_version = "uint-test";
|
||||
EXPECT_CALL(plugin, CallBack(_, WV_CAS_PLUGIN_VERSION, _, NotNull(),
|
||||
expected_version.size(), _))
|
||||
.Times(1);
|
||||
|
||||
EXPECT_EQ(plugin.sendEvent(QUERY_WV_CAS_PLUGIN_VERSION, /*arg=*/0,
|
||||
/*eventData=*/{}),
|
||||
android::OK);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace wvcas
|
||||
20
tests/src/wv_cas_test_main.cpp
Normal file
20
tests/src/wv_cas_test_main.cpp
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "OEMCryptoCENC.h"
|
||||
#include "log.h"
|
||||
|
||||
namespace wvutil {
|
||||
extern LogPriority g_cutoff;
|
||||
} // namespace wvutil
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
wvutil::g_cutoff = wvutil::CDM_LOG_INFO;
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
31
wvutil/Android.bp
Normal file
31
wvutil/Android.bp
Normal file
@@ -0,0 +1,31 @@
|
||||
// Builds libcasutil.a
|
||||
cc_library_static {
|
||||
|
||||
name: "libcasutil",
|
||||
|
||||
proprietary: true,
|
||||
|
||||
local_include_dirs: [
|
||||
"include",
|
||||
],
|
||||
|
||||
srcs: [
|
||||
"src/clock.cpp",
|
||||
"src/log.cpp",
|
||||
"src/file_store.cpp",
|
||||
"src/file_utils.cpp",
|
||||
"src/rw_lock.cpp",
|
||||
"src/string_conversions.cpp",
|
||||
"src/android_properties.cpp",
|
||||
"src/timer.cpp",
|
||||
],
|
||||
|
||||
shared_libs: [
|
||||
"liblog",
|
||||
"libutils",
|
||||
"libcrypto",
|
||||
"libhidlbase",
|
||||
],
|
||||
|
||||
export_include_dirs: ["include"],
|
||||
}
|
||||
46
wvutil/include/advance_iv_ctr.h
Normal file
46
wvutil/include/advance_iv_ctr.h
Normal file
@@ -0,0 +1,46 @@
|
||||
// Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine License
|
||||
// Agreement.
|
||||
|
||||
#ifndef WVCDM_UTIL_ADVANCE_IV_CTR_H_
|
||||
#define WVCDM_UTIL_ADVANCE_IV_CTR_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "string_conversions.h"
|
||||
|
||||
namespace wvutil {
|
||||
|
||||
// Advance an IV according to ISO-CENC's CTR modes. The lower half of the IV is
|
||||
// split off and treated as an unsigned 64-bit integer, then incremented by the
|
||||
// number of complete crypto blocks decrypted. The resulting value is then
|
||||
// copied back into the IV over the previous lower half.
|
||||
inline void AdvanceIvCtr(uint8_t (*subsample_iv)[16], size_t bytes) {
|
||||
constexpr size_t kAesBlockSize = 16;
|
||||
constexpr size_t kIvSize = kAesBlockSize;
|
||||
constexpr size_t kCounterIndex = kIvSize / 2;
|
||||
constexpr size_t kCounterSize = kIvSize / 2;
|
||||
|
||||
uint64_t counter;
|
||||
|
||||
static_assert(
|
||||
sizeof(*subsample_iv) == kIvSize,
|
||||
"The subsample_iv field is no longer the length of an AES-128 IV.");
|
||||
static_assert(sizeof(counter) == kCounterSize,
|
||||
"A uint64_t failed to be half the size of an AES-128 IV.");
|
||||
|
||||
// Defensive copy because the elements of the array may not be properly
|
||||
// aligned
|
||||
memcpy(&counter, &(*subsample_iv)[kCounterIndex], kCounterSize);
|
||||
|
||||
const size_t increment =
|
||||
bytes / kAesBlockSize; // The truncation here is intentional
|
||||
counter = htonll64(ntohll64(counter) + increment);
|
||||
|
||||
memcpy(&(*subsample_iv)[kCounterIndex], &counter, kCounterSize);
|
||||
}
|
||||
|
||||
} // namespace wvutil
|
||||
|
||||
#endif // WVCDM_UTIL_ADVANCE_IV_CTR_H_
|
||||
20
wvutil/include/arraysize.h
Normal file
20
wvutil/include/arraysize.h
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine License
|
||||
// Agreement.
|
||||
|
||||
#ifndef WVCDM_UTIL_ARRAYSIZE_H_
|
||||
#define WVCDM_UTIL_ARRAYSIZE_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace wvutil {
|
||||
|
||||
// Returns the size of a fixed-length array.
|
||||
template <typename T, size_t N>
|
||||
constexpr size_t ArraySize(const T (&)[N]) {
|
||||
return N;
|
||||
}
|
||||
|
||||
} // namespace wvutil
|
||||
|
||||
#endif // WVCDM_UTIL_ARRAYSIZE_H_
|
||||
46
wvutil/include/cas_properties.h
Normal file
46
wvutil/include/cas_properties.h
Normal file
@@ -0,0 +1,46 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#ifndef CAS_PROPERTIES_H
|
||||
#define CAS_PROPERTIES_H
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace wvcas {
|
||||
|
||||
// Properties methods must be implemented for a platform. The values returned
|
||||
// describe the capabilities and configuration of a device using Widevine CAS.
|
||||
class Properties {
|
||||
private:
|
||||
Properties(); // Not implemented
|
||||
~Properties(); // NotImplemented
|
||||
public:
|
||||
// Sets the |company_name| field value to be populated in and EMM license
|
||||
// request. Returns false if unable to set the value.
|
||||
static bool GetCompanyName(std::string* company_name);
|
||||
// Sets the |model_name| field value to be populated in and EMM license
|
||||
// request. Returns false if unable to set the value.
|
||||
static bool GetModelName(std::string* model_name);
|
||||
// Sets the |product_name| field value to be populated in and EMM license
|
||||
// request. Returns false if unable to set the value.
|
||||
static bool GetProductName(std::string* product_name);
|
||||
// Sets the |arch_name| field value to be populated in and EMM license
|
||||
// request. Returns false if unable to set the value.
|
||||
static bool GetArchitectureName(std::string* arch_name);
|
||||
// Sets the |device_name| field value to be populated in and EMM license
|
||||
// request. Returns false if unable to set the value.
|
||||
static bool GetDeviceName(std::string* device_name);
|
||||
// Returns a path to CAS oemcrypto library, either default,
|
||||
// or overridden through system property.
|
||||
// Returned path could be either absolute or relative.
|
||||
// Returns false if unable to set the value.
|
||||
static bool GetOEMCryptoPath(std::string* path);
|
||||
// Sets |version| to Widevine CAS plugin version. Returns false if unable to
|
||||
// set the value.
|
||||
static bool GetWvCasPluginVersion(std::string& version);
|
||||
};
|
||||
|
||||
} // namespace wvcas
|
||||
|
||||
#endif // CAS_PROPERTIES_H
|
||||
117
wvutil/include/cdm_random.h
Normal file
117
wvutil/include/cdm_random.h
Normal file
@@ -0,0 +1,117 @@
|
||||
// Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine License
|
||||
// Agreement.
|
||||
#ifndef WVCDM_CORE_CDM_RANDOM_H_
|
||||
#define WVCDM_CORE_CDM_RANDOM_H_
|
||||
|
||||
#include <mutex>
|
||||
#include <random>
|
||||
#include <string>
|
||||
|
||||
namespace wvutil {
|
||||
|
||||
// CdmRandomGenerator is a thread safe, pseudo-random number generator.
|
||||
// It's purpose is to simplified interface for C++11's <random> library.
|
||||
// Some of the methods use a "device specific" random seed, if the
|
||||
// compiler/device does not support device specific randomizers, then the
|
||||
// actual value supplied may not be random. The generator is designed to
|
||||
// meet the C++ named requirement UniformRandomBitGenerator to allow it to
|
||||
// be used with standard library functions / class which are designed to
|
||||
// work with the standard library generators.
|
||||
class CdmRandomGenerator {
|
||||
public:
|
||||
// Result type of operator().
|
||||
using result_type = unsigned int;
|
||||
// Inclusive boundaries of operator().
|
||||
static constexpr unsigned int min() { return 0; }
|
||||
static constexpr unsigned int max() { return RAND_MAX; }
|
||||
|
||||
// The maximum number of bytes that can be generated at once for
|
||||
// `RandomData()`.
|
||||
static constexpr size_t kMaxRandomDataLength = 8192; // 8 kB
|
||||
|
||||
// Initializes the pseudo-random generator with a value from a device
|
||||
// specific random number generator.
|
||||
CdmRandomGenerator();
|
||||
|
||||
// Initializes the pseudo-random generator with the specified seed value.
|
||||
explicit CdmRandomGenerator(unsigned int seed) : generator_(seed) {}
|
||||
|
||||
// All of these methods are thread-safe.
|
||||
|
||||
// Seeds the pseudo-random generator with a value from a device specific
|
||||
// random number generator.
|
||||
void Seed();
|
||||
|
||||
// Seeds the pseudo-random generator with the specified seed value.
|
||||
// This is somewhat similar to `srand()` from the C standard library;
|
||||
// except that the sequence generated from successive calls to `Rand()`
|
||||
// will not necessarily be the same as they would be from the
|
||||
// standard library `rand()`. This is due to the underlying pseudo-random
|
||||
// generator that is used.
|
||||
void Seed(unsigned int seed);
|
||||
|
||||
// Returns a pseudo-random integer.
|
||||
// This is similar to `rand()` from the C standard library. The integer
|
||||
// returned is in the range of [min(), max()].
|
||||
unsigned int Rand();
|
||||
|
||||
// Allows for RNG to be callable.
|
||||
unsigned int operator()() { return Rand(); }
|
||||
|
||||
// Returns a pseudo-random integer within the provided inclusive range.
|
||||
uint64_t RandomInRange(uint64_t lower, uint64_t upper);
|
||||
uint64_t RandomInRange(uint64_t upper) { return RandomInRange(0, upper); }
|
||||
|
||||
// Returns a byte string containing randomized bytes of the specified
|
||||
// length.
|
||||
// If |length| is greater than |CdmRandomGenerator::kMaxRandomDataLength|,
|
||||
// then an error is logged and an empty string is returned.
|
||||
std::string RandomData(size_t length);
|
||||
|
||||
// Random true/false using Bernoulli distribution of equal probability.
|
||||
bool RandomBool();
|
||||
|
||||
private:
|
||||
// Mutex is used to lock the object, and allowing it to be used
|
||||
// concurrently in different threads.
|
||||
std::mutex generator_lock_;
|
||||
|
||||
// The `default_random_engine` depends on the compiler used and
|
||||
// potentially its version. This is important to know if you need to
|
||||
// create reproducible tests between platforms.
|
||||
std::default_random_engine generator_;
|
||||
};
|
||||
|
||||
// Provides a static interface to a process-wide instance of
|
||||
// CdmRandomGenerator.
|
||||
class CdmRandom {
|
||||
public:
|
||||
static unsigned int Rand() { return GetInstance()->Rand(); }
|
||||
static uint64_t RandomInRange(uint64_t lower, uint64_t upper) {
|
||||
return GetInstance()->RandomInRange(lower, upper);
|
||||
}
|
||||
static uint64_t RandomInRange(uint64_t upper) {
|
||||
return GetInstance()->RandomInRange(upper);
|
||||
}
|
||||
|
||||
static std::string RandomData(size_t length) {
|
||||
return GetInstance()->RandomData(length);
|
||||
}
|
||||
|
||||
static bool RandomBool() { return GetInstance()->RandomBool(); }
|
||||
|
||||
private:
|
||||
// These are intended to be used by tests if needed.
|
||||
static void Seed(unsigned int seed) { GetInstance()->Seed(seed); }
|
||||
static void Seed() { GetInstance()->Seed(); }
|
||||
|
||||
// Returns the process-wide instance of CdmRandomGenerator.
|
||||
// It the global instance has not yet been created, then a new instance
|
||||
// is created using a device-specific random seed.
|
||||
static CdmRandomGenerator* GetInstance();
|
||||
};
|
||||
|
||||
} // namespace wvutil
|
||||
|
||||
#endif // WVCDM_CORE_CDM_RANDOM_H_
|
||||
26
wvutil/include/clock.h
Normal file
26
wvutil/include/clock.h
Normal file
@@ -0,0 +1,26 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine License
|
||||
// Agreement.
|
||||
//
|
||||
// Clock - Platform independent interface for a time library
|
||||
//
|
||||
#ifndef WVCDM_UTIL_CLOCK_H_
|
||||
#define WVCDM_UTIL_CLOCK_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace wvutil {
|
||||
|
||||
// Provides time related information. The implementation is platform dependent.
|
||||
class Clock {
|
||||
public:
|
||||
Clock() {}
|
||||
virtual ~Clock() {}
|
||||
|
||||
// Provides the number of seconds since an epoch - 01/01/1970 00:00 UTC
|
||||
virtual int64_t GetCurrentTime();
|
||||
};
|
||||
|
||||
} // namespace wvutil
|
||||
|
||||
#endif // WVCDM_UTIL_CLOCK_H_
|
||||
16
wvutil/include/disallow_copy_and_assign.h
Normal file
16
wvutil/include/disallow_copy_and_assign.h
Normal file
@@ -0,0 +1,16 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine License
|
||||
// Agreement.
|
||||
|
||||
#ifndef WVCDM_UTIL_DISALLOW_COPY_AND_ASSIGN_H_
|
||||
#define WVCDM_UTIL_DISALLOW_COPY_AND_ASSIGN_H_
|
||||
|
||||
namespace wvutil {
|
||||
|
||||
#define CORE_DISALLOW_COPY_AND_ASSIGN(TypeName) \
|
||||
TypeName(const TypeName&); \
|
||||
void operator=(const TypeName&)
|
||||
|
||||
} // namespace wvutil
|
||||
|
||||
#endif // WVCDM_UTIL_DISALLOW_COPY_AND_ASSIGN_H_
|
||||
87
wvutil/include/file_store.h
Normal file
87
wvutil/include/file_store.h
Normal file
@@ -0,0 +1,87 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine License
|
||||
// Agreement.
|
||||
//
|
||||
// File - Platform independent interface for a File class
|
||||
//
|
||||
#ifndef WVCDM_UTIL_FILE_STORE_H_
|
||||
#define WVCDM_UTIL_FILE_STORE_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "disallow_copy_and_assign.h"
|
||||
#include "platform.h"
|
||||
#include "util_common.h"
|
||||
|
||||
namespace wvutil {
|
||||
|
||||
static const std::string kAtscCertificateFileName = "atsccert.bin";
|
||||
static const std::string kCertificateFileName = "cert1.bin";
|
||||
static const std::string kCertificateFileNameExt = ".bin";
|
||||
static const std::string kCertificateFileNamePrefix = "cert1_";
|
||||
static const std::string kLegacyCertificateFileName = "cert.bin";
|
||||
static const std::string kLegacyCertificateFileNamePrefix = "cert";
|
||||
static const std::string kOemCertificateFileName = "oemcert.bin";
|
||||
static const std::string kOemCertificateFileNamePrefix = "oemcert_";
|
||||
|
||||
// File class. The implementation is platform dependent.
|
||||
class File {
|
||||
public:
|
||||
File() {}
|
||||
virtual ~File() {}
|
||||
virtual ssize_t Read(char* buffer, size_t bytes) = 0;
|
||||
virtual ssize_t Write(const char* buffer, size_t bytes) = 0;
|
||||
|
||||
friend class FileSystem;
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(File);
|
||||
};
|
||||
|
||||
class FileSystem {
|
||||
public:
|
||||
FileSystem();
|
||||
FileSystem(const std::string& origin, void* extra_data);
|
||||
virtual ~FileSystem();
|
||||
|
||||
class Impl;
|
||||
|
||||
// defines as bit flag
|
||||
enum OpenFlags {
|
||||
kNoFlags = 0,
|
||||
kCreate = 1,
|
||||
kReadOnly = 2, // defaults to read and write access
|
||||
kTruncate = 4
|
||||
};
|
||||
|
||||
virtual std::unique_ptr<File> Open(const std::string& file_path, int flags);
|
||||
|
||||
virtual bool Exists(const std::string& file_path);
|
||||
virtual bool Exists(const std::string& file_path, int* errno_value);
|
||||
virtual bool Remove(const std::string& file_path);
|
||||
virtual ssize_t FileSize(const std::string& file_path);
|
||||
|
||||
// Return the filenames stored at dir_path.
|
||||
// dir_path will be stripped from the returned names.
|
||||
virtual bool List(const std::string& dir_path,
|
||||
std::vector<std::string>* 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<FileSystem::Impl> impl_;
|
||||
std::string origin_;
|
||||
std::string identifier_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(FileSystem);
|
||||
};
|
||||
|
||||
} // namespace wvutil
|
||||
|
||||
#endif // WVCDM_UTIL_FILE_STORE_H_
|
||||
29
wvutil/include/file_utils.h
Normal file
29
wvutil/include/file_utils.h
Normal file
@@ -0,0 +1,29 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine License
|
||||
// Agreement.
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace wvutil {
|
||||
|
||||
const char kCurrentDirectory[] = ".";
|
||||
const char kParentDirectory[] = "..";
|
||||
const char kDirectoryDelimiter = '/';
|
||||
const char kWildcard[] = "*";
|
||||
bool IsCurrentOrParentDirectory(const char* dir);
|
||||
|
||||
class FileUtils {
|
||||
public:
|
||||
static bool Exists(const std::string& src);
|
||||
static bool Exists(const std::string& src, int* errno_value);
|
||||
// The caller may only specifying a single wildcard
|
||||
static bool Remove(const std::string& src);
|
||||
static bool Copy(const std::string& src, const std::string& dest);
|
||||
static bool List(const std::string& path, std::vector<std::string>* files);
|
||||
static bool IsRegularFile(const std::string& path);
|
||||
static bool IsDirectory(const std::string& path);
|
||||
static bool CreateDirectory(const std::string& path);
|
||||
};
|
||||
|
||||
} // namespace wvutil
|
||||
111
wvutil/include/log.h
Normal file
111
wvutil/include/log.h
Normal file
@@ -0,0 +1,111 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine License
|
||||
// Agreement.
|
||||
//
|
||||
// Log - Platform independent interface for a Logging class
|
||||
//
|
||||
#ifndef WVCDM_UTIL_LOG_H_
|
||||
#define WVCDM_UTIL_LOG_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "util_common.h"
|
||||
|
||||
namespace wvutil {
|
||||
|
||||
// Simple logging class. The implementation is platform dependent.
|
||||
|
||||
typedef enum {
|
||||
// This log level should only be used for |g_cutoff|, in order to silence all
|
||||
// logging. It should never be passed to |Log()| as a log level.
|
||||
CDM_LOG_SILENT = -1,
|
||||
|
||||
CDM_LOG_ERROR = 0,
|
||||
CDM_LOG_WARN = 1,
|
||||
CDM_LOG_INFO = 2,
|
||||
CDM_LOG_DEBUG = 3,
|
||||
CDM_LOG_VERBOSE = 4,
|
||||
} LogPriority;
|
||||
|
||||
extern LogPriority g_cutoff;
|
||||
|
||||
struct LogMessage {
|
||||
uint32_t uid_;
|
||||
int64_t time_ms_;
|
||||
LogPriority priority_;
|
||||
std::string message_;
|
||||
};
|
||||
|
||||
class LogBuffer {
|
||||
public:
|
||||
static const int MAX_CAPACITY = 100;
|
||||
void addLog(const LogMessage& log);
|
||||
std::vector<LogMessage> getLogs();
|
||||
|
||||
private:
|
||||
std::deque<LogMessage> buffer_;
|
||||
std::mutex mutex_;
|
||||
};
|
||||
|
||||
extern LogBuffer g_logbuf;
|
||||
|
||||
static const uint32_t UNKNOWN_UID = std::numeric_limits<uint32_t>::max();
|
||||
|
||||
#ifdef __ANDROID__
|
||||
void SetLoggingUid(const uint32_t);
|
||||
void ClearLoggingUid();
|
||||
uint32_t GetLoggingUid();
|
||||
uint32_t GetIpcCallingUid();
|
||||
#else
|
||||
static inline void SetLoggingUid(const uint32_t) {}
|
||||
static inline void ClearLoggingUid() {}
|
||||
static inline uint32_t GetLoggingUid() { return UNKNOWN_UID; }
|
||||
static inline uint32_t GetIpcCallingUid() { return UNKNOWN_UID; }
|
||||
#endif
|
||||
|
||||
struct LoggingUidSetter {
|
||||
LoggingUidSetter() {}
|
||||
LoggingUidSetter(uint32_t uid) { SetLoggingUid(uid); }
|
||||
virtual ~LoggingUidSetter() { ClearLoggingUid(); }
|
||||
};
|
||||
|
||||
// Enable/disable verbose logging (LOGV).
|
||||
// This function is supplied for cases where the system layer does not
|
||||
// initialize logging. This is also needed to initialize logging in
|
||||
// unit tests.
|
||||
void InitLogging();
|
||||
|
||||
#ifdef __GNUC__
|
||||
[[gnu::format(printf, 5, 6)]]
|
||||
#endif
|
||||
void Log(const char* file, const char* function, int line,
|
||||
LogPriority level, const char* fmt, ...);
|
||||
|
||||
// Log APIs
|
||||
#ifdef CDM_DISABLE_LOGGING
|
||||
# define LOGE(...) (void)0
|
||||
# define LOGW(...) (void)0
|
||||
# define LOGI(...) (void)0
|
||||
# define LOGD(...) (void)0
|
||||
# define LOGV(...) (void)0
|
||||
#else
|
||||
# ifndef LOGE
|
||||
# define LOGE(...) \
|
||||
Log(__FILE__, __func__, __LINE__, wvutil::CDM_LOG_ERROR, __VA_ARGS__)
|
||||
# define LOGW(...) \
|
||||
Log(__FILE__, __func__, __LINE__, wvutil::CDM_LOG_WARN, __VA_ARGS__)
|
||||
# define LOGI(...) \
|
||||
Log(__FILE__, __func__, __LINE__, wvutil::CDM_LOG_INFO, __VA_ARGS__)
|
||||
# define LOGD(...) \
|
||||
Log(__FILE__, __func__, __LINE__, wvutil::CDM_LOG_DEBUG, __VA_ARGS__)
|
||||
# define LOGV(...) \
|
||||
Log(__FILE__, __func__, __LINE__, wvutil::CDM_LOG_VERBOSE, __VA_ARGS__)
|
||||
# endif
|
||||
#endif
|
||||
} // namespace wvutil
|
||||
|
||||
#endif // WVCDM_UTIL_LOG_H_
|
||||
31
wvutil/include/platform.h
Normal file
31
wvutil/include/platform.h
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine License
|
||||
// Agreement.
|
||||
//
|
||||
// Platform - Abstracts some utilities between platforms.
|
||||
//
|
||||
#ifndef WVCDM_UTIL_PLATFORM_H_
|
||||
#define WVCDM_UTIL_PLATFORM_H_
|
||||
|
||||
#include "util_common.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
# include <BaseTsd.h>
|
||||
# include <winsock2.h> // For htonl and ntohl.
|
||||
# include <wtypes.h>
|
||||
# define __PRETTY_FUNCTION__ __FUNCTION__
|
||||
# undef NO_ERROR
|
||||
# undef GetCurrentTime
|
||||
# undef DeleteFile
|
||||
|
||||
using ssize_t = SSIZE_T;
|
||||
|
||||
inline void sleep(int seconds) { Sleep(seconds * 1000); }
|
||||
int setenv(const char* key, const char* value, int overwrite);
|
||||
#else
|
||||
# include <arpa/inet.h>
|
||||
# include <sys/types.h>
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
|
||||
#endif // WVCDM_UTIL_PLATFORM_H_
|
||||
65
wvutil/include/rw_lock.h
Normal file
65
wvutil/include/rw_lock.h
Normal file
@@ -0,0 +1,65 @@
|
||||
// Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine License
|
||||
// Agreement.
|
||||
|
||||
#ifndef WVCDM_UTIL_RW_LOCK_H_
|
||||
#define WVCDM_UTIL_RW_LOCK_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
|
||||
#include "disallow_copy_and_assign.h"
|
||||
#include "util_common.h"
|
||||
|
||||
namespace wvutil {
|
||||
|
||||
// A simple reader-writer mutex implementation that mimics the one from C++17
|
||||
class shared_mutex {
|
||||
public:
|
||||
shared_mutex() : reader_count_(0), has_writer_(false) {}
|
||||
~shared_mutex();
|
||||
|
||||
// These methods take the mutex as a reader. They do not fulfill the
|
||||
// SharedMutex requirement from the C++14 STL, but they fulfill enough of it
|
||||
// to be used with |shared_lock| below.
|
||||
void lock_shared();
|
||||
void unlock_shared();
|
||||
|
||||
// These methods take the mutex as a writer. They fulfill the Mutex
|
||||
// requirement from the C++11 STL so that this mutex can be used with
|
||||
// |std::unique_lock|.
|
||||
void lock() { lock_implementation(false); }
|
||||
bool try_lock() { return lock_implementation(true); }
|
||||
void unlock();
|
||||
|
||||
private:
|
||||
bool lock_implementation(bool abort_if_unavailable);
|
||||
|
||||
uint32_t reader_count_;
|
||||
bool has_writer_;
|
||||
|
||||
std::mutex mutex_;
|
||||
std::condition_variable condition_variable_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(shared_mutex);
|
||||
};
|
||||
|
||||
// A simple reader lock implementation that mimics the one from C++14
|
||||
template <typename Mutex>
|
||||
class shared_lock {
|
||||
public:
|
||||
explicit shared_lock(Mutex& lock) : lock_(&lock) { lock_->lock_shared(); }
|
||||
explicit shared_lock(Mutex* lock) : lock_(lock) { lock_->lock_shared(); }
|
||||
~shared_lock() { lock_->unlock_shared(); }
|
||||
|
||||
private:
|
||||
Mutex* lock_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(shared_lock);
|
||||
};
|
||||
|
||||
} // namespace wvutil
|
||||
|
||||
#endif // WVCDM_UTIL_RW_LOCK_H_
|
||||
61
wvutil/include/string_conversions.h
Normal file
61
wvutil/include/string_conversions.h
Normal file
@@ -0,0 +1,61 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine License
|
||||
// Agreement.
|
||||
#ifndef WVCDM_UTIL_STRING_CONVERSIONS_H_
|
||||
#define WVCDM_UTIL_STRING_CONVERSIONS_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "util_common.h"
|
||||
|
||||
namespace wvutil {
|
||||
|
||||
// ASCII hex to Binary conversion.
|
||||
std::vector<uint8_t> a2b_hex(const std::string& b);
|
||||
std::vector<uint8_t> a2b_hex(const std::string& label,
|
||||
const std::string& b);
|
||||
std::string a2bs_hex(const std::string& b);
|
||||
|
||||
// Binary to ASCII hex conversion. The default versions limit output to 2k to
|
||||
// protect us from log spam. The unlimited version has no length limit.
|
||||
std::string b2a_hex(const std::vector<uint8_t>& b);
|
||||
std::string unlimited_b2a_hex(const std::vector<uint8_t>& b);
|
||||
std::string b2a_hex(const std::string& b);
|
||||
std::string unlimited_b2a_hex(const std::string& b);
|
||||
std::string HexEncode(const uint8_t* bytes, size_t size);
|
||||
std::string UnlimitedHexEncode(const uint8_t* bytes, size_t size);
|
||||
|
||||
// Base64 encoding/decoding.
|
||||
// Converts binary data into the ASCII Base64 character set and vice
|
||||
// versa using the encoding rules defined in RFC4648 section 4.
|
||||
std::string Base64Encode(const std::vector<uint8_t>& bin_input);
|
||||
std::string Base64Encode(const std::string& bin_input);
|
||||
std::vector<uint8_t> Base64Decode(const std::string& bin_input);
|
||||
|
||||
// URL-Safe Base64 encoding/decoding.
|
||||
// Converts binary data into the URL/Filename safe ASCII Base64
|
||||
// character set and vice versa using the encoding rules defined in
|
||||
// RFC4648 section 5.
|
||||
std::string Base64SafeEncode(const std::vector<uint8_t>& bin_input);
|
||||
std::string Base64SafeEncode(const std::string& bin_input);
|
||||
std::vector<uint8_t> Base64SafeDecode(const std::string& bin_input);
|
||||
// URL-Safe Base64 encoding without padding.
|
||||
// Similar to Base64SafeEncode(), without any padding character '='
|
||||
// at the end.
|
||||
std::string Base64SafeEncodeNoPad(const std::vector<uint8_t>& bin_input);
|
||||
std::string Base64SafeEncodeNoPad(const std::string& bin_input);
|
||||
|
||||
// Host to Network/Network to Host conversion.
|
||||
int64_t htonll64(int64_t x);
|
||||
inline int64_t ntohll64(int64_t x) { return htonll64(x); }
|
||||
|
||||
// Encode unsigned integer into a big endian formatted string.
|
||||
std::string EncodeUint32(uint32_t u);
|
||||
|
||||
} // namespace wvutil
|
||||
|
||||
#endif // WVCDM_UTIL_STRING_CONVERSIONS_H_
|
||||
23
wvutil/include/string_format.h
Normal file
23
wvutil/include/string_format.h
Normal file
@@ -0,0 +1,23 @@
|
||||
// Copyright 2022 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine License
|
||||
// Agreement.
|
||||
#ifndef WVCDM_UTIL_STRING_FORMAT_H_
|
||||
#define WVCDM_UTIL_STRING_FORMAT_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace wvutil {
|
||||
|
||||
#ifdef __GNUC__
|
||||
[[gnu::format(printf, 2, 3)]]
|
||||
#endif
|
||||
bool FormatString(std::string* out, const char* fmt, ...);
|
||||
|
||||
#ifdef __GNUC__
|
||||
[[gnu::format(printf, 2, 0)]]
|
||||
#endif
|
||||
bool VFormatString(std::string* out, const char* fmt, va_list vlist);
|
||||
|
||||
} // namespace wvutil
|
||||
|
||||
#endif // WVCDM_UTIL_STRING_FORMAT_H_
|
||||
55
wvutil/include/timer.h
Normal file
55
wvutil/include/timer.h
Normal file
@@ -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 <stdint.h>
|
||||
|
||||
#include "disallow_copy_and_assign.h"
|
||||
|
||||
namespace wvutil {
|
||||
|
||||
// Timer Handler class.
|
||||
//
|
||||
// Derive from this class if you wish to receive events when the timer
|
||||
// expires. Provide the handler when setting up a new Timer.
|
||||
|
||||
class TimerHandler {
|
||||
public:
|
||||
TimerHandler(){};
|
||||
virtual ~TimerHandler(){};
|
||||
|
||||
virtual void OnTimerEvent() = 0;
|
||||
};
|
||||
|
||||
// Timer class. The implementation is platform dependent.
|
||||
//
|
||||
// This class provides a simple recurring timer API. The class receiving
|
||||
// timer expiry events should derive from TimerHandler.
|
||||
// Specify the receiver class and the periodicty of timer events when
|
||||
// the timer is initiated by calling Start.
|
||||
|
||||
class Timer {
|
||||
public:
|
||||
class Impl;
|
||||
|
||||
Timer();
|
||||
~Timer();
|
||||
|
||||
bool Start(TimerHandler *handler, uint32_t time_in_secs);
|
||||
void Stop();
|
||||
bool IsRunning();
|
||||
|
||||
private:
|
||||
Impl *impl_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(Timer);
|
||||
};
|
||||
|
||||
} // namespace wvutil
|
||||
|
||||
#endif // TIMER_H_
|
||||
45
wvutil/include/util_common.h
Normal file
45
wvutil/include/util_common.h
Normal file
@@ -0,0 +1,45 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine License
|
||||
// Agreement.
|
||||
|
||||
#ifndef WVCDM_UTIL_UTIL_COMMON_H_
|
||||
#define WVCDM_UTIL_UTIL_COMMON_H_
|
||||
|
||||
// This section deals with defines that are platform-specific.
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
# define CORE_UTIL_IGNORE_DEPRECATED
|
||||
# define CORE_UTIL_RESTORE_WARNINGS
|
||||
|
||||
#else
|
||||
|
||||
# ifdef __GNUC__
|
||||
# define CORE_UTIL_IGNORE_DEPRECATED \
|
||||
_Pragma("GCC diagnostic push") \
|
||||
_Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"")
|
||||
# define CORE_UTIL_RESTORE_WARNINGS _Pragma("GCC diagnostic pop")
|
||||
# else
|
||||
# define CORE_UTIL_IGNORE_DEPRECATED
|
||||
# define CORE_UTIL_RESTORE_WARNINGS
|
||||
# endif
|
||||
|
||||
#endif
|
||||
|
||||
// This section deals with attribute-detection and is platform-agnostic.
|
||||
|
||||
#if !defined(__has_cpp_attribute)
|
||||
# define __has_cpp_attribute(x) 0
|
||||
#endif
|
||||
|
||||
#if __has_cpp_attribute(fallthrough)
|
||||
# define CORE_UTIL_FALLTHROUGH [[fallthrough]]
|
||||
#elif __has_cpp_attribute(clang::fallthrough)
|
||||
# define CORE_UTIL_FALLTHROUGH [[clang::fallthrough]]
|
||||
#elif __has_cpp_attribute(gnu::fallthrough)
|
||||
# define CORE_UTIL_FALLTHROUGH [[gnu::fallthrough]]
|
||||
#else
|
||||
# define CORE_UTIL_FALLTHROUGH
|
||||
#endif
|
||||
|
||||
#endif // WVCDM_UTIL_UTIL_COMMON_H_
|
||||
16
wvutil/include/wv_attributes.h
Normal file
16
wvutil/include/wv_attributes.h
Normal file
@@ -0,0 +1,16 @@
|
||||
// Copyright 2021 Google LLC. All rights reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine
|
||||
// License Agreement.
|
||||
|
||||
#ifndef WVCDM_UTIL_WV_ATTRIBUTES_H_
|
||||
#define WVCDM_UTIL_WV_ATTRIBUTES_H_
|
||||
|
||||
#ifndef UNUSED
|
||||
# if defined(__GNUC__) || defined(__clang__)
|
||||
# define UNUSED __attribute__((__unused__))
|
||||
# else
|
||||
# define UNUSED
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#endif // WVCDM_UTIL_WV_ATTRIBUTES_H_
|
||||
88
wvutil/src/android_properties.cpp
Normal file
88
wvutil/src/android_properties.cpp
Normal file
@@ -0,0 +1,88 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#include <cutils/properties.h>
|
||||
|
||||
#include "cas_properties.h"
|
||||
#include "log.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// Version format: OEMCrypto_major.OEMCrypto_minor.Plugin_version
|
||||
constexpr char kWvCasPluginVersion[] = "18.4.0";
|
||||
|
||||
bool GetAndroidProperty(const char* key, std::string* value) {
|
||||
char val[PROPERTY_VALUE_MAX];
|
||||
if (!key) {
|
||||
LOGW("GetAndroidProperty: Invalid property key parameter");
|
||||
return false;
|
||||
}
|
||||
if (!value) {
|
||||
LOGW("GetAndroidProperty: Invalid property value parameter");
|
||||
return false;
|
||||
}
|
||||
if (property_get(key, val, "Unknown") <= 0) return false;
|
||||
*value = val;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace wvcas {
|
||||
|
||||
bool Properties::GetCompanyName(std::string* company_name) {
|
||||
if (!company_name) {
|
||||
LOGW("Properties::GetCompanyName: Invalid parameter");
|
||||
return false;
|
||||
}
|
||||
return GetAndroidProperty("ro.product.manufacturer", company_name);
|
||||
}
|
||||
|
||||
bool Properties::GetModelName(std::string* model_name) {
|
||||
if (!model_name) {
|
||||
LOGW("Properties::GetModelName: Invalid parameter");
|
||||
return false;
|
||||
}
|
||||
return GetAndroidProperty("ro.product.model", model_name);
|
||||
}
|
||||
|
||||
bool Properties::GetArchitectureName(std::string* arch_name) {
|
||||
if (!arch_name) {
|
||||
LOGW("Properties::GetArchitectureName: Invalid parameter");
|
||||
return false;
|
||||
}
|
||||
return GetAndroidProperty("ro.product.cpu.abi", arch_name);
|
||||
}
|
||||
|
||||
bool Properties::GetDeviceName(std::string* device_name) {
|
||||
if (!device_name) {
|
||||
LOGW("Properties::GetDeviceName: Invalid parameter");
|
||||
return false;
|
||||
}
|
||||
return GetAndroidProperty("ro.product.device", device_name);
|
||||
}
|
||||
|
||||
bool Properties::GetProductName(std::string* product_name) {
|
||||
if (!product_name) {
|
||||
LOGW("Properties::GetProductName: Invalid parameter");
|
||||
return false;
|
||||
}
|
||||
return GetAndroidProperty("ro.product.name", product_name);
|
||||
}
|
||||
|
||||
bool Properties::GetOEMCryptoPath(std::string* path) {
|
||||
if (path == nullptr) {
|
||||
LOGW("Properties::GetOEMCryptoPath: Invalid parameter");
|
||||
return false;
|
||||
}
|
||||
*path = "liboemcrypto.so";
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Properties::GetWvCasPluginVersion(std::string& version) {
|
||||
version = kWvCasPluginVersion;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace wvcas
|
||||
20
wvutil/src/clock.cpp
Normal file
20
wvutil/src/clock.cpp
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine License
|
||||
// Agreement.
|
||||
//
|
||||
// Clock - implemented using the standard linux time library
|
||||
|
||||
#include "clock.h"
|
||||
|
||||
#include <sys/time.h>
|
||||
|
||||
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
|
||||
537
wvutil/src/file_store.cpp
Normal file
537
wvutil/src/file_store.cpp
Normal file
@@ -0,0 +1,537 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine License
|
||||
// Agreement.
|
||||
//
|
||||
// File class - provides a simple android specific file implementation
|
||||
|
||||
#include "file_store.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <threads.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "file_utils.h"
|
||||
#include "log.h"
|
||||
#include "string_conversions.h"
|
||||
|
||||
#include <openssl/md5.h>
|
||||
|
||||
// Size of the thread-local error string buffer. Used for calls
|
||||
// to strerror_r(3).
|
||||
#define ERRORSTR_BUF_SIZE 1024
|
||||
|
||||
namespace wvutil {
|
||||
namespace {
|
||||
// Maximum number of attempts to read or write on a file.
|
||||
constexpr size_t kMaxIoAttempts = 5;
|
||||
|
||||
// Stand in constant for a closed file descriptor.
|
||||
constexpr int kClosedFd = -1;
|
||||
|
||||
// A reset value of |errno|. Although unlikely, it is possible that a
|
||||
// system call could fail and not set |errno| to a new value. This
|
||||
// would technically be a bug with glibc, but it could cause our error
|
||||
// handling code to enter a bad state.
|
||||
constexpr int kNoError = 0;
|
||||
|
||||
constexpr char kEmptyOrigin[] = "";
|
||||
|
||||
// Reads from file specified by |fd| into the provided |buffer| up to
|
||||
// the number of bytes specified by |count|.
|
||||
// This is an internal function and assumes that all parameters are
|
||||
// valid.
|
||||
//
|
||||
// Returns:
|
||||
// 0 to |count| - Number of bytes successfully read from file.
|
||||
// -1 - Error occurred, check |errno| for read(2).
|
||||
ssize_t SafeRead(int fd, char* buffer, size_t count) {
|
||||
size_t attempts = 0;
|
||||
size_t total_bytes_read = 0;
|
||||
while (total_bytes_read < count && attempts < kMaxIoAttempts) {
|
||||
const size_t to_read = count - total_bytes_read;
|
||||
errno = kNoError;
|
||||
const ssize_t res = read(fd, buffer, to_read);
|
||||
if (res > 0) {
|
||||
attempts = 0; // Clearing |attempts| on success.
|
||||
// It is possible that fewer bytes than |to_read| were read.
|
||||
// In this case, try reading again. Non-critical errors will
|
||||
// likely result in success or |errno| being set to EAGAIN on
|
||||
// the second call. Critical errors will result in a different
|
||||
// error that the caller will need to handle.
|
||||
total_bytes_read += static_cast<size_t>(res);
|
||||
continue;
|
||||
}
|
||||
if (res == 0) return total_bytes_read; // EOF.
|
||||
attempts++;
|
||||
if ((errno != EINTR && errno != EAGAIN) || attempts >= kMaxIoAttempts) {
|
||||
// Caller must handle all other errors, or if max attempts
|
||||
// have been reached.
|
||||
return -1;
|
||||
}
|
||||
// read() was interrupted by signal, safe to try again.
|
||||
}
|
||||
return total_bytes_read;
|
||||
}
|
||||
|
||||
// Writes to the file specified by |fd| from the provided |buffer|.
|
||||
// Function will attempt to write all bytes specified by |count|.
|
||||
// This is an internal function and assumes that all parameters are
|
||||
// valid.
|
||||
//
|
||||
// Returns:
|
||||
// |count| - Successfully wrote all bytes to file.
|
||||
// -1 - Error occurred, check |errno| for write(2).
|
||||
ssize_t SafeWrite(int fd, const char* buffer, size_t count) {
|
||||
size_t attempts = 0;
|
||||
size_t total_bytes_written = 0;
|
||||
while (total_bytes_written < count && attempts < kMaxIoAttempts) {
|
||||
const size_t to_write = count - total_bytes_written;
|
||||
errno = kNoError;
|
||||
const ssize_t res = write(fd, &buffer[total_bytes_written], to_write);
|
||||
if (res > 0) {
|
||||
attempts = 0; // Clearing |attempts| on success.
|
||||
// It is possible that fewer bytes than |to_write| were written.
|
||||
// In this case, try writing again. Non-critical errors will
|
||||
// likely result in success or |errno| being set to EAGAIN on
|
||||
// the second call. Critical errors will result in a different
|
||||
// error that the caller will need to handle.
|
||||
total_bytes_written += static_cast<size_t>(res);
|
||||
continue;
|
||||
}
|
||||
if (res == 0) return total_bytes_written; // Possible EOF.
|
||||
attempts++;
|
||||
if ((errno != EINTR && errno != EAGAIN) || attempts >= kMaxIoAttempts) {
|
||||
// Caller must handle all other errors, or if max attempts
|
||||
// have been reached.
|
||||
return -1;
|
||||
}
|
||||
// write() was interrupted by signal, safe to try again.
|
||||
}
|
||||
return total_bytes_written;
|
||||
}
|
||||
|
||||
// Converts the provided error number to its string representation.
|
||||
// Supports a subset of error numbers expected from the system calls
|
||||
// used in this module.
|
||||
// TODO(b/183653374): Replace this with strerrorname_np().
|
||||
const char* ErrnoToString(int num) {
|
||||
switch (num) {
|
||||
case kNoError:
|
||||
return "ZERO";
|
||||
case EACCES:
|
||||
return "EACCES";
|
||||
case EAGAIN:
|
||||
return "EAGAIN";
|
||||
case EBADF:
|
||||
return "EBADF";
|
||||
case EBUSY:
|
||||
return "EBUSY";
|
||||
case EDESTADDRREQ:
|
||||
return "EDESTADDRREQ";
|
||||
case EDQUOT:
|
||||
return "EDQUOT";
|
||||
case EEXIST:
|
||||
return "EEXIST";
|
||||
case EFAULT:
|
||||
return "EFAULT";
|
||||
case EFBIG:
|
||||
return "EFBIG";
|
||||
case EINTR:
|
||||
return "EINTR";
|
||||
case EINVAL:
|
||||
return "EINVAL";
|
||||
case EIO:
|
||||
return "EIO";
|
||||
case EISDIR:
|
||||
return "EISDIR";
|
||||
case ELOOP:
|
||||
return "ELOOP";
|
||||
case EMFILE:
|
||||
return "EMFILE";
|
||||
case ENAMETOOLONG:
|
||||
return "ENAMETOOLONG";
|
||||
case ENFILE:
|
||||
return "ENFILE";
|
||||
case ENODEV:
|
||||
return "ENODEV";
|
||||
case ENOENT:
|
||||
return "ENOENT";
|
||||
case ENOMEM:
|
||||
return "ENOMEM";
|
||||
case ENOSPC:
|
||||
return "ENOSPC";
|
||||
case ENOTDIR:
|
||||
return "ENOTDIR";
|
||||
case ENXIO:
|
||||
return "ENXIO";
|
||||
case EOPNOTSUPP:
|
||||
return "EOPNOTSUPP";
|
||||
case EOVERFLOW:
|
||||
return "EOVERFLOW";
|
||||
case EPERM:
|
||||
return "EPERM";
|
||||
case EPIPE:
|
||||
return "EPIPE";
|
||||
case EROFS:
|
||||
return "EROFS";
|
||||
case ETXTBSY:
|
||||
return "ETXTBSY";
|
||||
#if EWOULDBLOCK != EAGAIN
|
||||
case EWOULDBLOCK:
|
||||
return "EWOULDBLOCK";
|
||||
#endif
|
||||
}
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
// Safely converts the provided error number to its standard
|
||||
// description string as provided by strerror_r().
|
||||
// This function is guaranteed to return null-terminated string,
|
||||
// and is thread safe.
|
||||
const char* ErrnoToDescription(int num) {
|
||||
static thread_local char error_buf[ERRORSTR_BUF_SIZE];
|
||||
if (num == kNoError) {
|
||||
return "Unspecified error";
|
||||
}
|
||||
// Always ensure there is a null term.
|
||||
error_buf[sizeof(error_buf) - 1] = 0;
|
||||
// See strerror_l(3) manual page for details.
|
||||
#if (_POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600) && !_GNU_SOURCE
|
||||
// Portable version:
|
||||
// int strerror_r(int num, char* buf, size_t buflen)
|
||||
// Returns:
|
||||
// 0 on success
|
||||
// -1 or a positive value on error (depends on glibc version)
|
||||
const int res = strerror_r(num, error_buf, sizeof(error_buf));
|
||||
return res != 0 ? "Unknown" : error_buf;
|
||||
#else
|
||||
// GNU specific version:
|
||||
// char* strerror_r(int num, char* buf, size_t buflen)
|
||||
// Returns:
|
||||
// Pointer to |buf| or internal string on success
|
||||
// Null on error
|
||||
const char* res = strerror_r(num, error_buf, sizeof(error_buf));
|
||||
return res == nullptr ? "Unknown" : res;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Converts the provided file |mode| to a "ls" style file mode.
|
||||
std::string StatModeToString(unsigned int mode) {
|
||||
std::string mode_rep;
|
||||
mode_rep.reserve(11); // 1 file type + 9 permissions + 1 term.
|
||||
// File type.
|
||||
if (S_ISREG(mode)) {
|
||||
mode_rep.append("-");
|
||||
} else if (S_ISDIR(mode)) {
|
||||
mode_rep.append("d");
|
||||
} else if (S_ISLNK(mode)) {
|
||||
mode_rep.append("l");
|
||||
} else if (S_ISCHR(mode)) {
|
||||
mode_rep.append("c");
|
||||
} else if (S_ISBLK(mode)) {
|
||||
mode_rep.append("b");
|
||||
} else if (S_ISSOCK(mode)) {
|
||||
mode_rep.append("s");
|
||||
} else {
|
||||
mode_rep.append("?");
|
||||
}
|
||||
// User owner permission.
|
||||
mode_rep.append((mode & S_IRUSR) ? "r" : "-");
|
||||
mode_rep.append((mode & S_IWUSR) ? "w" : "-");
|
||||
mode_rep.append((mode & S_IXUSR) ? "x" : "-");
|
||||
// Group owner permission.
|
||||
mode_rep.append((mode & S_IRGRP) ? "r" : "-");
|
||||
mode_rep.append((mode & S_IWGRP) ? "w" : "-");
|
||||
mode_rep.append((mode & S_IXGRP) ? "x" : "-");
|
||||
// Others permission.
|
||||
mode_rep.append((mode & S_IROTH) ? "r" : "-");
|
||||
mode_rep.append((mode & S_IWOTH) ? "w" : "-");
|
||||
mode_rep.append((mode & S_IXOTH) ? "x" : "-");
|
||||
return mode_rep;
|
||||
}
|
||||
|
||||
// Coverts the provided system time in seconds to a string representation
|
||||
// in the system's local time.
|
||||
// Time format is a modified ISO8601.
|
||||
// Example: 2021-03-01 12:32:53
|
||||
// If the time cannot be converted into this format, then the decimal
|
||||
// representation of seconds are returned.
|
||||
std::string PosixTimeToString(time_t seconds) {
|
||||
struct tm timestamp;
|
||||
if (localtime_r(&seconds, ×tamp) == nullptr) {
|
||||
// Only possible failure is an overflow.
|
||||
return std::to_string(seconds);
|
||||
}
|
||||
char buffer[32];
|
||||
memset(buffer, 0, sizeof(buffer));
|
||||
const size_t length = strftime(buffer, sizeof(buffer), "%F %T", ×tamp);
|
||||
if (length == 0) {
|
||||
// Unexpected error. Just return seconds.
|
||||
return std::to_string(seconds);
|
||||
}
|
||||
return std::string(buffer, length);
|
||||
}
|
||||
|
||||
std::string GetFileNameSafeHash(const std::string& input) {
|
||||
std::vector<uint8_t> hash(MD5_DIGEST_LENGTH);
|
||||
MD5(reinterpret_cast<const uint8_t*>(input.data()), input.size(),
|
||||
hash.data());
|
||||
return wvutil::Base64SafeEncode(hash);
|
||||
}
|
||||
|
||||
std::string GetFileNameForIdentifier(const std::string path,
|
||||
const std::string identifier) {
|
||||
std::string file_name = path;
|
||||
std::string dir_path;
|
||||
const size_t delimiter_pos = path.rfind(kDirectoryDelimiter);
|
||||
if (delimiter_pos != std::string::npos) {
|
||||
dir_path = file_name.substr(0, delimiter_pos);
|
||||
file_name = path.substr(delimiter_pos + 1);
|
||||
}
|
||||
|
||||
if (file_name == kCertificateFileName && !identifier.empty()) {
|
||||
const std::string hash = GetFileNameSafeHash(identifier);
|
||||
file_name = kCertificateFileNamePrefix + hash + kCertificateFileNameExt;
|
||||
} else if (file_name == kLegacyCertificateFileName && !identifier.empty()) {
|
||||
const std::string hash = GetFileNameSafeHash(identifier);
|
||||
file_name =
|
||||
kLegacyCertificateFileNamePrefix + hash + kCertificateFileNameExt;
|
||||
}
|
||||
|
||||
if (dir_path.empty())
|
||||
return file_name;
|
||||
return dir_path + kDirectoryDelimiter + file_name;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
class AndroidFile : public File {
|
||||
public:
|
||||
// Parameters:
|
||||
// |fd| - Open file descriptor for a regular file.
|
||||
// |flags| - Bit field of flags originally passed to FileSystem::Open()
|
||||
// |file_path| - Path used to open file.
|
||||
AndroidFile(int fd, int flags, const std::string& file_path)
|
||||
: fd_(fd), flags_(flags), file_path_(file_path) {}
|
||||
|
||||
~AndroidFile() { Close(); }
|
||||
|
||||
bool IsOpen() const { return fd_ != kClosedFd; }
|
||||
|
||||
bool CanWrite() const { return !(flags_ & FileSystem::kReadOnly); }
|
||||
|
||||
// Used for logging.
|
||||
const char* file_path() const { return file_path_.c_str(); }
|
||||
|
||||
ssize_t Read(char* buffer, size_t bytes) override {
|
||||
if (!buffer) {
|
||||
LOGE("Output |buffer| is null");
|
||||
return -1;
|
||||
}
|
||||
if (!IsOpen()) {
|
||||
LOGE("File not open: path = %s", file_path());
|
||||
return -1;
|
||||
}
|
||||
const ssize_t res = SafeRead(fd_, buffer, bytes);
|
||||
if (res < 0) {
|
||||
const int saved_errno = errno;
|
||||
LOGE("Read failed: errno = %s (%d), desc = %s",
|
||||
ErrnoToString(saved_errno), saved_errno,
|
||||
ErrnoToDescription(saved_errno));
|
||||
return -1;
|
||||
} else if (res < bytes) {
|
||||
LOGD("Read output truncated: expected = %zu, actual = %zd", bytes, res);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
ssize_t Write(const char* buffer, size_t bytes) override {
|
||||
if (!buffer) {
|
||||
LOGE("Input |buffer| is null");
|
||||
return -1;
|
||||
}
|
||||
if (!IsOpen()) {
|
||||
LOGE("File not open: path = %s", file_path());
|
||||
return -1;
|
||||
}
|
||||
if (!CanWrite()) {
|
||||
LOGE("File is read only: path = %s", file_path());
|
||||
return -1;
|
||||
}
|
||||
const ssize_t res = SafeWrite(fd_, buffer, bytes);
|
||||
if (res < 0) {
|
||||
const int saved_errno = errno;
|
||||
LOGE("Write failed: errno = %s (%d), desc = %s",
|
||||
ErrnoToString(saved_errno), saved_errno,
|
||||
ErrnoToDescription(saved_errno));
|
||||
return -1;
|
||||
}
|
||||
FlushFile();
|
||||
return res;
|
||||
}
|
||||
|
||||
private:
|
||||
void FlushFile() { fsync(fd_); }
|
||||
|
||||
void Close() {
|
||||
if (IsOpen()) {
|
||||
FlushFile();
|
||||
close(fd_);
|
||||
fd_ = kClosedFd;
|
||||
}
|
||||
}
|
||||
|
||||
// Logs the contents of the file's stat info.
|
||||
void LogStat() const {
|
||||
if (!IsOpen()) {
|
||||
LOGD("No stat info available");
|
||||
return;
|
||||
}
|
||||
struct stat st;
|
||||
errno = kNoError;
|
||||
if (fstat(fd_, &st) != 0) {
|
||||
const int saved_errno = errno;
|
||||
if (errno != EBADF) {
|
||||
// No logs if an issue with FD, caller would have indicated the problem.
|
||||
LOGE("Stat failed: errno = %s (%d), desc = %s",
|
||||
ErrnoToString(saved_errno), saved_errno,
|
||||
ErrnoToDescription(saved_errno));
|
||||
}
|
||||
return;
|
||||
}
|
||||
LOGD(
|
||||
"Stat: path = %s, st_dev = %lu, st_ino = %lu, st_mode = 0%o (%s), "
|
||||
"st_uid = %u, st_gid = %u, st_size = %ld, st_atime = %s, "
|
||||
"st_mtime = %s, st_ctime = %s",
|
||||
file_path(), st.st_dev, st.st_ino, st.st_mode,
|
||||
StatModeToString(st.st_mode).c_str(), st.st_uid, st.st_gid, st.st_size,
|
||||
PosixTimeToString(st.st_atime).c_str(),
|
||||
PosixTimeToString(st.st_mtime).c_str(),
|
||||
PosixTimeToString(st.st_ctime).c_str());
|
||||
}
|
||||
|
||||
// File descriptor of the opened file. Set to -1 when closed.
|
||||
int fd_ = kClosedFd;
|
||||
// Bit field of OpenFlags.
|
||||
int flags_ = 0;
|
||||
// Path used to open the file descriptor.
|
||||
std::string file_path_;
|
||||
};
|
||||
|
||||
class FileSystem::Impl {};
|
||||
|
||||
FileSystem::FileSystem() : FileSystem(kEmptyOrigin, nullptr) {}
|
||||
FileSystem::FileSystem(const std::string& origin, void* /* extra_data */)
|
||||
: origin_(origin) {}
|
||||
|
||||
FileSystem::~FileSystem() {}
|
||||
|
||||
std::unique_ptr<File> FileSystem::Open(const std::string& file_name,
|
||||
int flags) {
|
||||
const std::string file_path =
|
||||
GetFileNameForIdentifier(file_name, identifier_);
|
||||
// Verify flags.
|
||||
if ((flags & kReadOnly) && (flags & kTruncate)) {
|
||||
LOGE(
|
||||
"Cannot be both truncated and be read-only: "
|
||||
"file = %s, identifier = %s",
|
||||
file_name.c_str(), identifier_.c_str());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Create the enclosing directory if it does not exist.
|
||||
const size_t delimiter_pos = file_path.rfind(kDirectoryDelimiter);
|
||||
if (delimiter_pos != std::string::npos) {
|
||||
const std::string dir_path = file_path.substr(0, delimiter_pos);
|
||||
if ((flags & FileSystem::kCreate) && !Exists(dir_path))
|
||||
FileUtils::CreateDirectory(dir_path);
|
||||
}
|
||||
|
||||
const bool exists = Exists(file_path);
|
||||
if (!(flags & kCreate) && !exists) {
|
||||
LOGD("File does not exist: file = %s, identifier = %s", file_name.c_str(),
|
||||
identifier_.c_str());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const int open_flags = ((flags & kCreate) ? O_CREAT : 0) |
|
||||
((flags & kReadOnly) ? O_RDONLY : O_RDWR) |
|
||||
((flags & kTruncate) ? O_TRUNC : 0) |
|
||||
O_CLOEXEC; // Never share on calls to exec().
|
||||
constexpr mode_t kDefaultMode = S_IRUSR | S_IWUSR;
|
||||
errno = kNoError;
|
||||
const int fd = open(file_path.c_str(), open_flags, kDefaultMode);
|
||||
if (fd < 0) {
|
||||
const int saved_errno = errno;
|
||||
LOGE("%s failed: errno = %s (%d), desc = %s, path = %s",
|
||||
exists ? "Open" : "Create", ErrnoToString(saved_errno), saved_errno,
|
||||
ErrnoToDescription(saved_errno), file_path.c_str());
|
||||
return nullptr;
|
||||
}
|
||||
// Check that the opened file is a regular file.
|
||||
struct stat st;
|
||||
errno = kNoError;
|
||||
if (fstat(fd, &st) != 0) {
|
||||
const int saved_errno = errno;
|
||||
LOGE("Stat failed: errno = %s (%d), desc = %s", ErrnoToString(saved_errno),
|
||||
saved_errno, ErrnoToDescription(saved_errno));
|
||||
close(fd);
|
||||
return nullptr;
|
||||
}
|
||||
if (!S_ISREG(st.st_mode)) {
|
||||
LOGE("Not a file: path = %s, st_mode = 0%o (%s)", file_path.c_str(),
|
||||
st.st_mode, StatModeToString(st.st_mode).c_str());
|
||||
close(fd);
|
||||
return nullptr;
|
||||
}
|
||||
return std::unique_ptr<File>(new AndroidFile(fd, flags, file_path));
|
||||
}
|
||||
|
||||
bool FileSystem::Exists(const std::string& path) {
|
||||
return FileUtils::Exists(GetFileNameForIdentifier(path, identifier_));
|
||||
}
|
||||
|
||||
bool FileSystem::Exists(const std::string& path, int* errno_value) {
|
||||
return FileUtils::Exists(GetFileNameForIdentifier(path, identifier_),
|
||||
errno_value);
|
||||
}
|
||||
|
||||
bool FileSystem::Remove(const std::string& path) {
|
||||
return FileUtils::Remove(GetFileNameForIdentifier(path, identifier_));
|
||||
}
|
||||
|
||||
ssize_t FileSystem::FileSize(const std::string& file_name) {
|
||||
const std::string file_path =
|
||||
GetFileNameForIdentifier(file_name, identifier_);
|
||||
struct stat st;
|
||||
errno = kNoError;
|
||||
if (stat(file_path.c_str(), &st) == 0) {
|
||||
if (st.st_size == 0) {
|
||||
LOGW("File is empty: name = %s", file_name.c_str());
|
||||
}
|
||||
return st.st_size;
|
||||
}
|
||||
// Else, error occurred.
|
||||
const int saved_errno = errno;
|
||||
LOGE("Stat failed: errno = %s (%d), desc = %s", ErrnoToString(saved_errno),
|
||||
saved_errno, ErrnoToDescription(saved_errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool FileSystem::List(const std::string& path,
|
||||
std::vector<std::string>* 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
|
||||
240
wvutil/src/file_utils.cpp
Normal file
240
wvutil/src/file_utils.cpp
Normal file
@@ -0,0 +1,240 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine License
|
||||
// Agreement.
|
||||
|
||||
#include "file_utils.h"
|
||||
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/sendfile.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "log.h"
|
||||
|
||||
namespace wvutil {
|
||||
|
||||
bool IsCurrentOrParentDirectory(const char* dir) {
|
||||
return strcmp(dir, kCurrentDirectory) == 0 ||
|
||||
strcmp(dir, kParentDirectory) == 0;
|
||||
}
|
||||
|
||||
bool FileUtils::Exists(const std::string& path) {
|
||||
return Exists(path, nullptr);
|
||||
}
|
||||
|
||||
bool FileUtils::Exists(const std::string& path, int* errno_value) {
|
||||
struct stat buf;
|
||||
int error = 0;
|
||||
int res = stat(path.c_str(), &buf) == 0;
|
||||
if (!res) {
|
||||
error = errno;
|
||||
if (error == ENOENT) {
|
||||
LOGI("stat failed: ENOENT");
|
||||
} else {
|
||||
LOGE("stat failed: %d, %s", error, strerror(error));
|
||||
}
|
||||
}
|
||||
if (errno_value != nullptr) *errno_value = error;
|
||||
return res;
|
||||
}
|
||||
|
||||
bool FileUtils::Remove(const std::string& path) {
|
||||
if (FileUtils::IsDirectory(path)) {
|
||||
// Handle directory deletion
|
||||
DIR* dir;
|
||||
if ((dir = opendir(path.c_str())) != nullptr) {
|
||||
// first remove files and dir within it
|
||||
struct dirent* entry;
|
||||
while ((entry = readdir(dir)) != nullptr) {
|
||||
if (!IsCurrentOrParentDirectory(entry->d_name)) {
|
||||
std::string path_to_remove = path + kDirectoryDelimiter;
|
||||
path_to_remove += entry->d_name;
|
||||
if (!Remove(path_to_remove)) {
|
||||
closedir(dir);
|
||||
LOGW("Failed to remove directory entry: dir_path = %s, entry = %s",
|
||||
path.c_str(), entry->d_name);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir(dir);
|
||||
}
|
||||
if (rmdir(path.c_str())) {
|
||||
LOGW("File::Remove: rmdir failed: %d, %s", errno, strerror(errno));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
size_t wildcard_pos = path.find(kWildcard);
|
||||
if (wildcard_pos == std::string::npos) {
|
||||
// Handle file deletion
|
||||
if (unlink(path.c_str()) && (errno != ENOENT)) {
|
||||
LOGW("File::Remove: unlink failed: %d, %s", errno, strerror(errno));
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// Handle wildcard specified file deletion
|
||||
size_t delimiter_pos = path.rfind(kDirectoryDelimiter, wildcard_pos);
|
||||
if (delimiter_pos == std::string::npos) {
|
||||
LOGW("File::Remove: unable to find path delimiter before wildcard");
|
||||
return false;
|
||||
}
|
||||
|
||||
DIR* dir;
|
||||
std::string dir_path = path.substr(0, delimiter_pos);
|
||||
std::string prepend =
|
||||
path.substr(delimiter_pos + 1, wildcard_pos - delimiter_pos - 1);
|
||||
if ((dir = opendir(dir_path.c_str())) == nullptr) {
|
||||
LOGW("File::Remove: directory open failed for wildcard: %d, %s", errno,
|
||||
strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
struct dirent* entry;
|
||||
std::string ext = path.substr(wildcard_pos + 1);
|
||||
|
||||
while ((entry = readdir(dir)) != nullptr) {
|
||||
size_t filename_len = strlen(entry->d_name);
|
||||
if (filename_len > ext.size()) {
|
||||
if (strcmp(entry->d_name + filename_len - ext.size(), ext.c_str()) ==
|
||||
0 &&
|
||||
!IsCurrentOrParentDirectory(entry->d_name) &&
|
||||
strncmp(entry->d_name, prepend.c_str(), prepend.size()) == 0) {
|
||||
std::string file_path_to_remove =
|
||||
dir_path + kDirectoryDelimiter + entry->d_name;
|
||||
if (!Remove(file_path_to_remove)) {
|
||||
closedir(dir);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir(dir);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool FileUtils::Copy(const std::string& src, const std::string& dest) {
|
||||
struct stat stat_buf;
|
||||
if (stat(src.c_str(), &stat_buf)) {
|
||||
LOGV("File::Copy: file %s stat error: %d, %s", src.c_str(), errno,
|
||||
strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
int fd_src = open(src.c_str(), O_RDONLY);
|
||||
if (fd_src < 0) {
|
||||
LOGW("File::Copy: unable to open file %s: %d, %s", src.c_str(), errno,
|
||||
strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
int fd_dest = open(dest.c_str(), O_WRONLY | O_CREAT, stat_buf.st_mode);
|
||||
if (fd_dest < 0) {
|
||||
LOGW("File::Copy: unable to open file %s: %d, %s", dest.c_str(), errno,
|
||||
strerror(errno));
|
||||
close(fd_src);
|
||||
return false;
|
||||
}
|
||||
|
||||
off_t offset = 0;
|
||||
bool status = true;
|
||||
if (sendfile(fd_dest, fd_src, &offset, stat_buf.st_size) < 0) {
|
||||
LOGV("File::Copy: unable to copy %s to %s: %d, %s", src.c_str(),
|
||||
dest.c_str(), errno, strerror(errno));
|
||||
status = false;
|
||||
}
|
||||
|
||||
close(fd_src);
|
||||
close(fd_dest);
|
||||
return status;
|
||||
}
|
||||
|
||||
bool FileUtils::List(const std::string& path, std::vector<std::string>* 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
|
||||
144
wvutil/src/log.cpp
Normal file
144
wvutil/src/log.cpp
Normal file
@@ -0,0 +1,144 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine License
|
||||
// Agreement.
|
||||
//
|
||||
// Log - implemented using the standard Android logging mechanism
|
||||
|
||||
/*
|
||||
* Qutoing from system/core/include/log/log.h:
|
||||
* Normally we strip ALOGV (VERBOSE messages) from release builds.
|
||||
* You can modify this (for example with "#define LOG_NDEBUG 0"
|
||||
* at the top of your source file) to change that behavior.
|
||||
*/
|
||||
#ifndef LOG_NDEBUG
|
||||
# ifdef NDEBUG
|
||||
# define LOG_NDEBUG 1
|
||||
# else
|
||||
# define LOG_NDEBUG 0
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#define LOG_TAG "WVCas"
|
||||
#define LOG_BUF_SIZE 5120
|
||||
|
||||
#include "log.h"
|
||||
#include <hwbinder/IPCThreadState.h>
|
||||
#include <utils/Log.h>
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
|
||||
/*
|
||||
* Uncomment the line below if you want to have the LOGV messages to print
|
||||
* IMPORTANT : this will affect all of CDM
|
||||
*/
|
||||
|
||||
// #define LOG_NDEBUG 0
|
||||
|
||||
namespace wvutil {
|
||||
|
||||
namespace {
|
||||
int64_t GetCurrentTimeMs() {
|
||||
struct timeval tv {};
|
||||
gettimeofday(&tv, NULL);
|
||||
auto msec1 = static_cast<int64_t>(tv.tv_sec) * 1000;
|
||||
auto msec2 = static_cast<int64_t>(tv.tv_usec) / 1000;
|
||||
return msec1 + msec2;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
LogPriority g_cutoff = CDM_LOG_INFO;
|
||||
|
||||
LogBuffer g_logbuf;
|
||||
|
||||
thread_local bool tl_logging_uid_set_ = false;
|
||||
|
||||
thread_local uint32_t tl_logging_uid_ = UNKNOWN_UID;
|
||||
|
||||
void SetLoggingUid(const uint32_t uid) {
|
||||
tl_logging_uid_set_ = true;
|
||||
tl_logging_uid_ = uid;
|
||||
}
|
||||
|
||||
void ClearLoggingUid() {
|
||||
tl_logging_uid_set_ = false;
|
||||
tl_logging_uid_ = UNKNOWN_UID;
|
||||
}
|
||||
|
||||
uint32_t GetLoggingUid() { return tl_logging_uid_; }
|
||||
|
||||
uint32_t GetIpcCallingUid() {
|
||||
const auto self = android::hardware::IPCThreadState::selfOrNull();
|
||||
return self ? self->getCallingUid() : UNKNOWN_UID;
|
||||
}
|
||||
|
||||
void InitLogging() {}
|
||||
|
||||
void Log(const char* file, const char* function, int line, LogPriority level,
|
||||
const char* format, ...) {
|
||||
const char* filename = strrchr(file, '/');
|
||||
filename = filename == nullptr ? file : filename + 1;
|
||||
|
||||
static thread_local char buf[LOG_BUF_SIZE];
|
||||
int len =
|
||||
snprintf(buf, LOG_BUF_SIZE, "[%s(%d):%s] ", filename, line, function);
|
||||
if (len < 0) len = 0;
|
||||
if (static_cast<unsigned int>(len) < sizeof(buf)) {
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
vsnprintf(buf + len, LOG_BUF_SIZE - len, format, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
android_LogPriority prio = ANDROID_LOG_VERBOSE;
|
||||
|
||||
switch (level) {
|
||||
case CDM_LOG_SILENT:
|
||||
return; // It is nonsensical to pass LOG_SILENT.
|
||||
case CDM_LOG_ERROR:
|
||||
prio = ANDROID_LOG_ERROR;
|
||||
break;
|
||||
case CDM_LOG_WARN:
|
||||
prio = ANDROID_LOG_WARN;
|
||||
break;
|
||||
case CDM_LOG_INFO:
|
||||
prio = ANDROID_LOG_INFO;
|
||||
break;
|
||||
case CDM_LOG_DEBUG:
|
||||
prio = ANDROID_LOG_DEBUG;
|
||||
break;
|
||||
#if LOG_NDEBUG
|
||||
case CDM_LOG_VERBOSE:
|
||||
return;
|
||||
#else
|
||||
case CDM_LOG_VERBOSE:
|
||||
prio = ANDROID_LOG_VERBOSE;
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
|
||||
__android_log_write(prio, LOG_TAG, buf);
|
||||
if (level <= CDM_LOG_INFO) {
|
||||
uint32_t uid = tl_logging_uid_set_ ? tl_logging_uid_ : GetIpcCallingUid();
|
||||
g_logbuf.addLog({uid, GetCurrentTimeMs(), level, buf});
|
||||
}
|
||||
}
|
||||
|
||||
void LogBuffer::addLog(const LogMessage& log) {
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
buffer_.push_back(log);
|
||||
while (buffer_.size() > MAX_CAPACITY) {
|
||||
buffer_.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<LogMessage> LogBuffer::getLogs() {
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
return {buffer_.begin(), buffer_.end()};
|
||||
}
|
||||
|
||||
} // namespace wvutil
|
||||
60
wvutil/src/rw_lock.cpp
Normal file
60
wvutil/src/rw_lock.cpp
Normal file
@@ -0,0 +1,60 @@
|
||||
// Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine License
|
||||
// Agreement.
|
||||
|
||||
#include "rw_lock.h"
|
||||
|
||||
#include "log.h"
|
||||
|
||||
namespace wvutil {
|
||||
|
||||
shared_mutex::~shared_mutex() {
|
||||
if (reader_count_ > 0) {
|
||||
LOGE("shared_mutex destroyed with active readers!");
|
||||
}
|
||||
if (has_writer_) {
|
||||
LOGE("shared_mutex destroyed with an active writer!");
|
||||
}
|
||||
}
|
||||
|
||||
void shared_mutex::lock_shared() {
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
|
||||
while (has_writer_) {
|
||||
condition_variable_.wait(lock);
|
||||
}
|
||||
|
||||
++reader_count_;
|
||||
}
|
||||
|
||||
void shared_mutex::unlock_shared() {
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
|
||||
--reader_count_;
|
||||
|
||||
if (reader_count_ == 0) {
|
||||
condition_variable_.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
bool shared_mutex::lock_implementation(bool abort_if_unavailable) {
|
||||
std::unique_lock<std::mutex> 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<std::mutex> lock(mutex_);
|
||||
|
||||
has_writer_ = false;
|
||||
|
||||
condition_variable_.notify_all();
|
||||
}
|
||||
|
||||
} // namespace wvutil
|
||||
343
wvutil/src/string_conversions.cpp
Normal file
343
wvutil/src/string_conversions.cpp
Normal file
@@ -0,0 +1,343 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine License
|
||||
// Agreement.
|
||||
|
||||
#include "string_conversions.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "log.h"
|
||||
#include "platform.h"
|
||||
|
||||
namespace wvutil {
|
||||
namespace {
|
||||
// Base64 character set, indexed for their 6-bit mapping, plus '='.
|
||||
const char kBase64Codes[] =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
|
||||
// URL safe Base64 character set.
|
||||
const char kBase64SafeCodes[] =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_=";
|
||||
|
||||
// Gets the low |n| bits of |in|.
|
||||
#define GET_LOW_BITS(in, n) ((in) & ((1 << (n)) - 1))
|
||||
// Gets the given (zero-indexed) bits [a, b) of |in|.
|
||||
#define GET_BITS(in, a, b) GET_LOW_BITS((in) >> (a), (b) - (a))
|
||||
// Calculates a/b using round-up division (only works for positive numbers).
|
||||
#define CEIL_DIVIDE(a, b) ((((a)-1) / (b)) + 1)
|
||||
|
||||
// Decodes a single Base64 encoded character into its 6-bit value.
|
||||
// The provided |codes| must be a Base64 character map.
|
||||
int DecodeBase64Char(char c, const char* codes) {
|
||||
const char* c_in_codes = strchr(codes, c);
|
||||
if (c_in_codes == nullptr) return -1;
|
||||
const uintptr_t c_in_codes_int = reinterpret_cast<uintptr_t>(c_in_codes);
|
||||
const uintptr_t codes_int = reinterpret_cast<uintptr_t>(codes);
|
||||
return static_cast<int>(c_in_codes_int - codes_int);
|
||||
}
|
||||
|
||||
bool DecodeHexChar(char ch, uint8_t* digit) {
|
||||
if (ch >= '0' && ch <= '9') {
|
||||
*digit = ch - '0';
|
||||
return true;
|
||||
}
|
||||
ch = tolower(ch);
|
||||
if ((ch >= 'a') && (ch <= 'f')) {
|
||||
*digit = ch - 'a' + 10;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Encode for standard base64 encoding (RFC4648).
|
||||
// https://en.wikipedia.org/wiki/Base64
|
||||
// Text | M | a | n |
|
||||
// ASCI | 77 (0x4d) | 97 (0x61) | 110 (0x6e) |
|
||||
// Bits | 0 1 0 0 1 1 0 1 0 1 1 0 0 0 0 1 0 1 1 0 1 1 1 0 |
|
||||
// Index | 19 | 22 | 5 | 46 |
|
||||
// Base64 | T | W | F | u |
|
||||
// | <----------------- 24-bits -----------------> |
|
||||
|
||||
// The provided |codes| must be a Base64 character map.
|
||||
std::string Base64EncodeInternal(const uint8_t* data, size_t length,
|
||||
const char* codes) {
|
||||
// |temp| stores a 24-bit block that is treated as an array where insertions
|
||||
// occur from high to low.
|
||||
uint32_t temp = 0;
|
||||
size_t out_index = 0;
|
||||
const size_t out_size = CEIL_DIVIDE(length, 3) * 4;
|
||||
std::string result(out_size, '\0');
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
// "insert" 8-bits of data
|
||||
temp |= (data[i] << ((2 - (i % 3)) * 8));
|
||||
if (i % 3 == 2) {
|
||||
result[out_index++] = codes[GET_BITS(temp, 18, 24)];
|
||||
result[out_index++] = codes[GET_BITS(temp, 12, 18)];
|
||||
result[out_index++] = codes[GET_BITS(temp, 6, 12)];
|
||||
result[out_index++] = codes[GET_BITS(temp, 0, 6)];
|
||||
temp = 0;
|
||||
}
|
||||
}
|
||||
if (length % 3 == 1) {
|
||||
result[out_index++] = codes[GET_BITS(temp, 18, 24)];
|
||||
result[out_index++] = codes[GET_BITS(temp, 12, 18)];
|
||||
result[out_index++] = '=';
|
||||
result[out_index++] = '=';
|
||||
} else if (length % 3 == 2) {
|
||||
result[out_index++] = codes[GET_BITS(temp, 18, 24)];
|
||||
result[out_index++] = codes[GET_BITS(temp, 12, 18)];
|
||||
result[out_index++] = codes[GET_BITS(temp, 6, 12)];
|
||||
result[out_index++] = '=';
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> Base64DecodeInternal(const char* encoded, size_t length,
|
||||
const char* codes) {
|
||||
const size_t out_size_max = CEIL_DIVIDE(length * 3, 4);
|
||||
std::vector<uint8_t> result(out_size_max, '\0');
|
||||
// |temp| stores 24-bits of data that is treated as an array where insertions
|
||||
// occur from high to low.
|
||||
uint32_t temp = 0;
|
||||
size_t out_index = 0;
|
||||
size_t i;
|
||||
for (i = 0; i < length; i++) {
|
||||
if (encoded[i] == '=') {
|
||||
// Verify an '=' only appears at the end. We want i to remain at the
|
||||
// first '=', so we need an inner loop.
|
||||
for (size_t j = i; j < length; j++) {
|
||||
if (encoded[j] != '=') {
|
||||
LOGE("base64Decode failed");
|
||||
return std::vector<uint8_t>();
|
||||
}
|
||||
}
|
||||
if (length % 4 != 0) {
|
||||
// If padded, then the length must be a multiple of 4.
|
||||
// Unpadded messages are OK.
|
||||
LOGE("base64Decode failed");
|
||||
return std::vector<uint8_t>();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
const int decoded = DecodeBase64Char(encoded[i], codes);
|
||||
if (decoded < 0) {
|
||||
LOGE("base64Decode failed");
|
||||
return std::vector<uint8_t>();
|
||||
}
|
||||
// "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<uint8_t>();
|
||||
case 2:
|
||||
result[out_index++] = GET_BITS(temp, 16, 24);
|
||||
break;
|
||||
case 3:
|
||||
result[out_index++] = GET_BITS(temp, 16, 24);
|
||||
result[out_index++] = GET_BITS(temp, 8, 16);
|
||||
break;
|
||||
}
|
||||
result.resize(out_index);
|
||||
return result;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// converts an ascii hex string(2 bytes per digit) into a decimal byte string
|
||||
std::vector<uint8_t> a2b_hex(const std::string& byte) {
|
||||
std::vector<uint8_t> array;
|
||||
size_t count = byte.size();
|
||||
if (count == 0 || (count % 2) != 0) {
|
||||
LOGE("Invalid input size %zu for string %s", count, byte.c_str());
|
||||
return array;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < count / 2; ++i) {
|
||||
unsigned char msb = 0; // most significant 4 bits
|
||||
unsigned char lsb = 0; // least significant 4 bits
|
||||
if (!DecodeHexChar(byte[i * 2], &msb) ||
|
||||
!DecodeHexChar(byte[i * 2 + 1], &lsb)) {
|
||||
LOGE("Invalid hex value %c%c at index %zu", byte[i * 2], byte[i * 2 + 1],
|
||||
i);
|
||||
return array;
|
||||
}
|
||||
array.push_back((msb << 4) | lsb);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
// converts an ascii hex string(2 bytes per digit) into a decimal byte string
|
||||
// dump the string with the label.
|
||||
std::vector<uint8_t> 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<uint8_t> array = a2b_hex(byte);
|
||||
return std::string(array.begin(), array.end());
|
||||
}
|
||||
|
||||
std::string b2a_hex(const std::vector<uint8_t>& byte) {
|
||||
if (byte.empty()) return "";
|
||||
return HexEncode(byte.data(), byte.size());
|
||||
}
|
||||
|
||||
std::string unlimited_b2a_hex(const std::vector<uint8_t>& byte) {
|
||||
if (byte.empty()) return "";
|
||||
return UnlimitedHexEncode(byte.data(), byte.size());
|
||||
}
|
||||
|
||||
std::string b2a_hex(const std::string& byte) {
|
||||
if (byte.empty()) return "";
|
||||
return HexEncode(reinterpret_cast<const uint8_t*>(byte.data()),
|
||||
byte.length());
|
||||
}
|
||||
|
||||
std::string unlimited_b2a_hex(const std::string& byte) {
|
||||
if (byte.empty()) return "";
|
||||
return UnlimitedHexEncode(reinterpret_cast<const uint8_t*>(byte.data()),
|
||||
byte.length());
|
||||
}
|
||||
|
||||
std::string HexEncode(const uint8_t* in_buffer, size_t size) {
|
||||
constexpr unsigned int kMaxSafeSize = 2048;
|
||||
if (size > kMaxSafeSize) size = kMaxSafeSize;
|
||||
return UnlimitedHexEncode(in_buffer, size);
|
||||
}
|
||||
|
||||
std::string UnlimitedHexEncode(const uint8_t* in_buffer, size_t size) {
|
||||
static const char kHexChars[] = "0123456789ABCDEF";
|
||||
if (size == 0) return "";
|
||||
// Each input byte creates two output hex characters.
|
||||
std::string out_buffer(size * 2, '\0');
|
||||
for (unsigned int i = 0; i < size; ++i) {
|
||||
char byte = in_buffer[i];
|
||||
out_buffer[(i << 1)] = kHexChars[(byte >> 4) & 0xf];
|
||||
out_buffer[(i << 1) + 1] = kHexChars[byte & 0xf];
|
||||
}
|
||||
return out_buffer;
|
||||
}
|
||||
|
||||
// Standard Base64 encoding and decoding.
|
||||
|
||||
std::string Base64Encode(const std::vector<uint8_t>& bin_input) {
|
||||
if (bin_input.empty()) {
|
||||
return std::string();
|
||||
}
|
||||
return Base64EncodeInternal(bin_input.data(), bin_input.size(), kBase64Codes);
|
||||
}
|
||||
|
||||
std::string Base64Encode(const std::string& bin_input) {
|
||||
if (bin_input.empty()) {
|
||||
return std::string();
|
||||
}
|
||||
return Base64EncodeInternal(
|
||||
reinterpret_cast<const uint8_t*>(bin_input.data()), bin_input.size(),
|
||||
kBase64Codes);
|
||||
}
|
||||
|
||||
// Decode for standard base64 encoding (RFC4648).
|
||||
std::vector<uint8_t> Base64Decode(const std::string& b64_input) {
|
||||
if (b64_input.empty()) {
|
||||
return std::vector<uint8_t>();
|
||||
}
|
||||
return Base64DecodeInternal(b64_input.data(), b64_input.size(), kBase64Codes);
|
||||
}
|
||||
|
||||
// URL/Filename Safe Base64 encoding and decoding.
|
||||
|
||||
// This is the encoding required to interface with the provisioning server, as
|
||||
// well as for certain license server transactions. It is also used for logging
|
||||
// certain strings. The difference between web safe encoding vs regular encoding
|
||||
// is that the web safe version replaces '+' with '-' and '/' with '_'.
|
||||
std::string Base64SafeEncode(const std::vector<uint8_t>& bin_input) {
|
||||
if (bin_input.empty()) {
|
||||
return std::string();
|
||||
}
|
||||
return Base64EncodeInternal(bin_input.data(), bin_input.size(),
|
||||
kBase64SafeCodes);
|
||||
}
|
||||
|
||||
std::string Base64SafeEncode(const std::string& bin_input) {
|
||||
if (bin_input.empty()) {
|
||||
return std::string();
|
||||
}
|
||||
return Base64EncodeInternal(
|
||||
reinterpret_cast<const uint8_t*>(bin_input.data()), bin_input.size(),
|
||||
kBase64SafeCodes);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> Base64SafeDecode(const std::string& b64_input) {
|
||||
if (b64_input.empty()) {
|
||||
return std::vector<uint8_t>();
|
||||
}
|
||||
return Base64DecodeInternal(b64_input.data(), b64_input.size(),
|
||||
kBase64SafeCodes);
|
||||
}
|
||||
|
||||
// URL/Filename Safe Base64 encoding without padding.
|
||||
|
||||
std::string Base64SafeEncodeNoPad(const std::vector<uint8_t>& bin_input) {
|
||||
std::string b64_output = Base64SafeEncode(bin_input);
|
||||
// Output size: ceiling [ bin_input.size() * 4 / 3 ].
|
||||
b64_output.resize((bin_input.size() * 4 + 2) / 3);
|
||||
return b64_output;
|
||||
}
|
||||
|
||||
std::string Base64SafeEncodeNoPad(const std::string& bin_input) {
|
||||
std::string b64_output = Base64SafeEncode(bin_input);
|
||||
// Output size: ceiling [ bin_input.size() * 4 / 3 ].
|
||||
b64_output.resize((bin_input.size() * 4 + 2) / 3);
|
||||
return b64_output;
|
||||
}
|
||||
|
||||
// Host to Network/Network to Host conversion.
|
||||
|
||||
// Convert to big endian (network-byte-order)
|
||||
int64_t htonll64(int64_t x) {
|
||||
union {
|
||||
uint32_t array[2];
|
||||
int64_t number;
|
||||
} mixed;
|
||||
mixed.number = 1;
|
||||
if (mixed.array[0] == 1) { // Little Endian.
|
||||
mixed.number = x;
|
||||
uint32_t temp = mixed.array[0];
|
||||
mixed.array[0] = htonl(mixed.array[1]);
|
||||
mixed.array[1] = htonl(temp);
|
||||
return mixed.number;
|
||||
} else { // Big Endian.
|
||||
return x;
|
||||
}
|
||||
}
|
||||
|
||||
// Encode unsigned integer into a big endian formatted string
|
||||
std::string EncodeUint32(unsigned int u) {
|
||||
std::string s;
|
||||
s.push_back((u >> 24) & 0xFF);
|
||||
s.push_back((u >> 16) & 0xFF);
|
||||
s.push_back((u >> 8) & 0xFF);
|
||||
s.push_back(u & 0xFF);
|
||||
return s;
|
||||
}
|
||||
|
||||
} // namespace wvutil
|
||||
104
wvutil/src/timer.cpp
Normal file
104
wvutil/src/timer.cpp
Normal file
@@ -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 <unistd.h>
|
||||
|
||||
#include <utils/Mutex.h>
|
||||
#include <utils/RefBase.h>
|
||||
#include <utils/StrongPointer.h>
|
||||
#include <utils/Thread.h>
|
||||
|
||||
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<ImplThread> 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
|
||||
Reference in New Issue
Block a user