Regular update
Plugin: 1. Process ECM v3 and send fingerprinting/service_blocking events 2. Rmove unused function Ctr128Add 3. Add support for ECM v3 OEMCrypto: 1. Update API description of OEMCrypto_LoadCasECMKeys 2. Fix android build files for ODK 3. Load content keys to shared memory 4. Move KCB check to LoadCasKeys call 5. Support even/odd content keys to share entitlement key
This commit is contained in:
@@ -268,10 +268,10 @@ typedef struct {
|
||||
* padding.
|
||||
* entitlement_key_id - entitlement key id to be matched to key table.
|
||||
* content_key_id - content key id to be loaded into key table.
|
||||
* content_key_data_iv - the IV for performing AES-256-CBC decryption of the key data.
|
||||
* content_key_data - encrypted content key data.
|
||||
* content_iv - the 16 byte iv used to decrypt content.
|
||||
* cipher_mode - the cipher mode to be used to decrypt the content.
|
||||
* content_key_data_iv - the IV for performing AES-256-CBC decryption of the key
|
||||
* data. content_key_data - encrypted content key data. content_iv - the 16 byte
|
||||
* iv used to decrypt content. cipher_mode - the cipher mode to be used to
|
||||
* decrypt the content.
|
||||
*/
|
||||
typedef struct {
|
||||
OEMCrypto_Substring entitlement_key_id;
|
||||
@@ -1737,42 +1737,44 @@ OEMCryptoResult OEMCrypto_LoadEntitledContentKeys(
|
||||
* OEMCrypto_LoadCasECMKeys
|
||||
*
|
||||
* Description:
|
||||
* Load content keys into a session which already has entitlement
|
||||
* keys loaded. This function will only be called for a session after a call
|
||||
* to OEMCrypto_LoadKeys with the parameter type license_type equal to
|
||||
* OEMCrypto_EntitlementLicense. This function may be called multiple times
|
||||
* for the same session.
|
||||
* The OEMCrypto_LoadCasECMKeys method is added to load content keys into an
|
||||
* entitled key session, which already has entitlement keys loaded.
|
||||
*
|
||||
* If the session does not have license_type equal to
|
||||
* OEMCrypto_EntitlementLicense, return OEMCrypto_ERROR_INVALID_CONTEXT and
|
||||
* perform no work.
|
||||
* This function will only be called for a session after a call to
|
||||
* OEMCrypto_LoadKeys with the license_type equal to
|
||||
* OEMCrypto_EntitlementLicense, and a call to
|
||||
* OEMCrypto_CreateEntitledKeySession initializing the entitled key session.
|
||||
* This function may be called multiple times for the same session.
|
||||
*
|
||||
* For each key object in key_array, OEMCrypto shall look up the entry in the
|
||||
* key table with the corresponding entitlement_key_id.
|
||||
* For each key object, odd and even, OEMCrypto shall look up the entry in the
|
||||
* key table with the corresponding entitlement_key_id. Before the
|
||||
* entitlement_key is used:
|
||||
* 1) If no entry is found, return OEMCrypto_KEY_NOT_ENTITLED.
|
||||
* 2) If the entry already has a content_key_id and content_key_data, that id
|
||||
* and data are erased.
|
||||
* 3) The content_key_id from the key_array is copied to the entry's
|
||||
* content_key_id.
|
||||
* 2) Check the entitlement key’s key control block use. If failed, return
|
||||
* corresponding error code such as OEMCrypto_ERROR_ANALOG_OUTPUT,
|
||||
* OEMCrypto_ERROR_INSUFFICIENT_HDCP.
|
||||
* 3) If the entitlement key’s control block has a nonzero Duration field,
|
||||
* then the API shall verify that the duration is greater than the
|
||||
* session’s elapsed time clock before the key is used. OEMCrypto will
|
||||
* return OEMCrypto_ERROR_KEY_EXPIRED.
|
||||
* 4) The content_key_data decrypted using the entitlement_key_data as a key
|
||||
* for AES-256-CBC with an IV of content_key_data_iv, and using PKCS#7
|
||||
* padding. Notice that the entitlement key will be an AES 256 bit key.
|
||||
* The clear content key data will be stored in the entry's
|
||||
* content_key_data.
|
||||
* 5) The decrypted content key data may be set in a hardware descrambler
|
||||
* if present.
|
||||
*
|
||||
* Entries in the key table that do not correspond to anything in the
|
||||
* key_array are not modified or removed.
|
||||
*
|
||||
* For devices that use a hardware key ladder, it may be more appropriate to
|
||||
* store the encrypted content key data in the key table, and defer decrypting
|
||||
* it until the function SelectKey is called.
|
||||
* for AES-256-CBC with an IV of content_key_data_iv. Wrapped content is
|
||||
* padded using PKCS#7 padding. Notice that the entitlement key will be an
|
||||
* AES 256 bit key. The clear content key data will be stored in the
|
||||
* entry’s content_key_data.
|
||||
* 5) The decrypted content key data may be set in a hardware KeySlot,
|
||||
* together with content iv and cipher mode information, which can be used
|
||||
* by the Descrambler in TunerHal. The entitled key session ID may be used
|
||||
* as the key token to uniquely identify the content key in KeySlot.
|
||||
*
|
||||
* Parameters:
|
||||
* session (in) - handle for the session to be used.
|
||||
* even_key (in) - key update for the even ecm key.
|
||||
* odd_key (in) - key update for the odd ecm key.
|
||||
* [in] session: handle for the entitled key session to be used.
|
||||
* [in] message: pointer to memory containing message to be verified.
|
||||
* [in] message_length: length of the message, in bytes.
|
||||
* [in] even_key: key update for the even ecm key. May be null if the key
|
||||
* does not change.
|
||||
* [in] odd_key: key update for the odd ecm key. May be null if the key does
|
||||
* not change.
|
||||
*
|
||||
* Returns
|
||||
* OEMCrypto_SUCCESS success
|
||||
@@ -1781,6 +1783,11 @@ OEMCryptoResult OEMCrypto_LoadEntitledContentKeys(
|
||||
* OEMCrypto_ERROR_INSUFFICIENT_RESOURCES
|
||||
* OEMCrypto_ERROR_UNKNOWN_FAILURE
|
||||
* OEMCrypto_KEY_NOT_ENTITLED
|
||||
* OEMCrypto_ERROR_INVALID_ENTITLED_KEY_SESSION
|
||||
* OEMCrypto_ERROR_KEY_EXPIRED
|
||||
* OEMCrypto_ERROR_ANALOG_OUTPUT
|
||||
* OEMCrypto_ERROR_INSUFFICIENT_HDCP
|
||||
*
|
||||
* Threading
|
||||
*
|
||||
* This function may be called simultaneously with functions on other
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// License Agreement.
|
||||
|
||||
cc_defaults {
|
||||
name: "odk_fuzz_library_defaults",
|
||||
name: "cas_odk_fuzz_library_defaults",
|
||||
srcs: [
|
||||
"odk_fuzz_helper.cpp",
|
||||
],
|
||||
@@ -15,7 +15,7 @@ cc_defaults {
|
||||
}
|
||||
|
||||
cc_fuzz {
|
||||
name: "odk_license_request_fuzz",
|
||||
name: "cas_odk_license_request_fuzz",
|
||||
srcs: [
|
||||
"odk_license_request_fuzz.cpp",
|
||||
],
|
||||
@@ -24,15 +24,15 @@ cc_fuzz {
|
||||
},
|
||||
corpus: ["corpus/little_endian_64bit/license_request_corpus/*"],
|
||||
static_libs: [
|
||||
"libwv_kdo",
|
||||
"libwv_odk",
|
||||
"libwvcas_kdo",
|
||||
"libwvcas_odk",
|
||||
],
|
||||
defaults: ["odk_fuzz_library_defaults"],
|
||||
defaults: ["cas_odk_fuzz_library_defaults"],
|
||||
proprietary: true,
|
||||
}
|
||||
|
||||
cc_fuzz {
|
||||
name: "odk_renewal_request_fuzz",
|
||||
name: "cas_odk_renewal_request_fuzz",
|
||||
srcs: [
|
||||
"odk_renewal_request_fuzz.cpp",
|
||||
],
|
||||
@@ -41,15 +41,15 @@ cc_fuzz {
|
||||
},
|
||||
corpus: ["corpus/little_endian_64bit/renewal_request_corpus/*"],
|
||||
static_libs: [
|
||||
"libwv_kdo",
|
||||
"libwv_odk",
|
||||
"libwvcas_kdo",
|
||||
"libwvcas_odk",
|
||||
],
|
||||
defaults: ["odk_fuzz_library_defaults"],
|
||||
defaults: ["cas_odk_fuzz_library_defaults"],
|
||||
proprietary: true,
|
||||
}
|
||||
|
||||
cc_fuzz {
|
||||
name: "odk_provisioning_request_fuzz",
|
||||
name: "cas_odk_provisioning_request_fuzz",
|
||||
srcs: [
|
||||
"odk_provisioning_request_fuzz.cpp",
|
||||
],
|
||||
@@ -58,15 +58,15 @@ cc_fuzz {
|
||||
},
|
||||
corpus: ["corpus/little_endian_64bit/provisioning_request_corpus/*"],
|
||||
static_libs: [
|
||||
"libwv_kdo",
|
||||
"libwv_odk",
|
||||
"libwvcas_kdo",
|
||||
"libwvcas_odk",
|
||||
],
|
||||
defaults: ["odk_fuzz_library_defaults"],
|
||||
defaults: ["cas_odk_fuzz_library_defaults"],
|
||||
proprietary: true,
|
||||
}
|
||||
|
||||
cc_fuzz {
|
||||
name: "odk_license_response_fuzz",
|
||||
name: "cas_odk_license_response_fuzz",
|
||||
srcs: [
|
||||
"odk_license_response_fuzz.cpp",
|
||||
],
|
||||
@@ -75,15 +75,15 @@ cc_fuzz {
|
||||
},
|
||||
corpus: ["corpus/little_endian_64bit/license_response_corpus/*"],
|
||||
static_libs: [
|
||||
"libwv_kdo",
|
||||
"libwv_odk",
|
||||
"libwvcas_kdo",
|
||||
"libwvcas_odk",
|
||||
],
|
||||
defaults: ["odk_fuzz_library_defaults"],
|
||||
defaults: ["cas_odk_fuzz_library_defaults"],
|
||||
proprietary: true,
|
||||
}
|
||||
|
||||
cc_fuzz {
|
||||
name: "odk_renewal_response_fuzz",
|
||||
name: "cas_odk_renewal_response_fuzz",
|
||||
srcs: [
|
||||
"odk_renewal_response_fuzz.cpp",
|
||||
],
|
||||
@@ -92,15 +92,15 @@ cc_fuzz {
|
||||
},
|
||||
corpus: ["corpus/little_endian_64bit/renewal_response_corpus/*"],
|
||||
static_libs: [
|
||||
"libwv_kdo",
|
||||
"libwv_odk",
|
||||
"libwvcas_kdo",
|
||||
"libwvcas_odk",
|
||||
],
|
||||
defaults: ["odk_fuzz_library_defaults"],
|
||||
defaults: ["cas_odk_fuzz_library_defaults"],
|
||||
proprietary: true,
|
||||
}
|
||||
|
||||
cc_fuzz {
|
||||
name: "odk_provisioning_response_fuzz",
|
||||
name: "cas_odk_provisioning_response_fuzz",
|
||||
srcs: [
|
||||
"odk_provisioning_response_fuzz.cpp",
|
||||
],
|
||||
@@ -109,15 +109,15 @@ cc_fuzz {
|
||||
},
|
||||
corpus: ["corpus/little_endian_64bit/provisioning_response_corpus/*"],
|
||||
static_libs: [
|
||||
"libwv_kdo",
|
||||
"libwv_odk",
|
||||
"libwvcas_kdo",
|
||||
"libwvcas_odk",
|
||||
],
|
||||
defaults: ["odk_fuzz_library_defaults"],
|
||||
defaults: ["cas_odk_fuzz_library_defaults"],
|
||||
proprietary: true,
|
||||
}
|
||||
|
||||
cc_fuzz {
|
||||
name: "odk_license_response_fuzz_with_mutator",
|
||||
name: "cas_odk_license_response_fuzz_with_mutator",
|
||||
srcs: [
|
||||
"odk_license_response_fuzz_with_mutator.cpp",
|
||||
],
|
||||
@@ -126,15 +126,15 @@ cc_fuzz {
|
||||
},
|
||||
corpus: ["corpus/little_endian_64bit/license_response_corpus/*"],
|
||||
static_libs: [
|
||||
"libwv_kdo",
|
||||
"libwv_odk",
|
||||
"libwvcas_kdo",
|
||||
"libwvcas_odk",
|
||||
],
|
||||
defaults: ["odk_fuzz_library_defaults"],
|
||||
defaults: ["cas_odk_fuzz_library_defaults"],
|
||||
proprietary: true,
|
||||
}
|
||||
|
||||
cc_fuzz {
|
||||
name: "odk_renewal_response_fuzz_with_mutator",
|
||||
name: "cas_odk_renewal_response_fuzz_with_mutator",
|
||||
srcs: [
|
||||
"odk_renewal_response_fuzz_with_mutator.cpp",
|
||||
],
|
||||
@@ -143,15 +143,15 @@ cc_fuzz {
|
||||
},
|
||||
corpus: ["corpus/little_endian_64bit/renewal_response_corpus/*"],
|
||||
static_libs: [
|
||||
"libwv_kdo",
|
||||
"libwv_odk",
|
||||
"libwvcas_kdo",
|
||||
"libwvcas_odk",
|
||||
],
|
||||
defaults: ["odk_fuzz_library_defaults"],
|
||||
defaults: ["cas_odk_fuzz_library_defaults"],
|
||||
proprietary: true,
|
||||
}
|
||||
|
||||
cc_fuzz {
|
||||
name: "odk_provisioning_response_fuzz_with_mutator",
|
||||
name: "cas_odk_provisioning_response_fuzz_with_mutator",
|
||||
srcs: [
|
||||
"odk_provisioning_response_fuzz_with_mutator.cpp",
|
||||
],
|
||||
@@ -160,9 +160,9 @@ cc_fuzz {
|
||||
},
|
||||
corpus: ["corpus/little_endian_64bit/provisioning_response_corpus/*"],
|
||||
static_libs: [
|
||||
"libwv_kdo",
|
||||
"libwv_odk",
|
||||
"libwvcas_kdo",
|
||||
"libwvcas_odk",
|
||||
],
|
||||
defaults: ["odk_fuzz_library_defaults"],
|
||||
defaults: ["cas_odk_fuzz_library_defaults"],
|
||||
proprietary: true,
|
||||
}
|
||||
@@ -10,7 +10,7 @@
|
||||
// Builds libwv_odk.so, The ODK shared Library (libwv_odk) is used
|
||||
// by the OEMCrypto unit tests to generate corpus for ODK fuzz scrips.
|
||||
cc_library_shared {
|
||||
name: "libwv_odk_corpus_generator",
|
||||
name: "libwvcas_odk_corpus_generator",
|
||||
include_dirs: [
|
||||
"vendor/widevine/libwvmediacas/oemcrypto/include",
|
||||
"vendor/widevine/libwvmediacas/oemcrypto/odk/include",
|
||||
|
||||
@@ -4,22 +4,87 @@
|
||||
|
||||
#include "oemcrypto_entitled_key_session.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
#include "log.h"
|
||||
|
||||
namespace wvoec_ref {
|
||||
|
||||
namespace {
|
||||
constexpr size_t kContentKeySize = 16;
|
||||
constexpr size_t kContentIvSize = 16;
|
||||
|
||||
// Returns the open file descriptor.
|
||||
int OpenHardwareKeySlot() {
|
||||
// Must be hardware location in production, instead of a file that is publicly
|
||||
// visible.
|
||||
// Not able to create (permission denied). The file must already exist.
|
||||
int fd = open("/data/local/tmp/wv", O_RDWR);
|
||||
if (fd < 0) {
|
||||
LOGE("File for shared memory open failed: %d", errno);
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
// Returns true if the lock is success.
|
||||
bool LockHardwareKeySlot(int fd) {
|
||||
flock lock;
|
||||
lock.l_type = F_WRLCK; /* read/write (exclusive versus shared) lock */
|
||||
lock.l_whence = SEEK_SET; /* base for seek offsets */
|
||||
lock.l_start = 0; /* 1st byte in file */
|
||||
lock.l_len = 0; /* 0 here means 'until EOF' */
|
||||
lock.l_pid = getpid(); /* process id */
|
||||
|
||||
// Wait until we get the lock.
|
||||
if (fcntl(fd, F_SETLKW, &lock) < 0) {
|
||||
LOGE("Failed to lock file: %d", errno);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void UnlockAndCloseHardwareKeySlot(int fd) {
|
||||
if (fd < 0) {
|
||||
return;
|
||||
}
|
||||
flock lock;
|
||||
lock.l_type = F_UNLCK;
|
||||
lock.l_whence = SEEK_SET; /* base for seek offsets */
|
||||
lock.l_start = 0; /* 1st byte in file */
|
||||
lock.l_len = 0; /* 0 here means 'until EOF' */
|
||||
lock.l_pid = getpid(); /* process id */
|
||||
if (fcntl(fd, F_SETLK, &lock) < 0) {
|
||||
LOGE("explicit unlocking failed.");
|
||||
}
|
||||
close(fd); /* close the file: would unlock if needed */
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
EntitledKeySession::~EntitledKeySession() { ClearKeySlot(); }
|
||||
|
||||
const Key* EntitledKeySession::GetEntitlementKey(
|
||||
const KeyId& content_key_id) const {
|
||||
if (entitlement_key_map_.find(content_key_id) == entitlement_key_map_.end()) {
|
||||
if (content_key_info_map_.find(content_key_id) ==
|
||||
content_key_info_map_.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
return entitlement_key_map_.at(content_key_id);
|
||||
return content_key_info_map_.at(content_key_id)->entitlement_key;
|
||||
}
|
||||
|
||||
EntitledKey* EntitledKeySession::GetContentKey(
|
||||
const KeyId& content_key_id) const {
|
||||
if (content_key_map_.find(content_key_id) == content_key_map_.end()) {
|
||||
if (content_key_info_map_.find(content_key_id) ==
|
||||
content_key_info_map_.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
return content_key_map_.at(content_key_id).get();
|
||||
return content_key_info_map_.at(content_key_id)->content_key.get();
|
||||
}
|
||||
|
||||
bool EntitledKeySession::AddOrUpdateContentKey(
|
||||
@@ -29,32 +94,151 @@ bool EntitledKeySession::AddOrUpdateContentKey(
|
||||
content_key == nullptr) {
|
||||
return false;
|
||||
}
|
||||
// Remove the entry if |entitlement_key| already exists. Each entitlement key
|
||||
// can only be referred by one content key within an entitled key session.
|
||||
for (auto const& content_id_entitlement : entitlement_key_map_) {
|
||||
if (content_id_entitlement.second == entitlement_key) {
|
||||
RemoveContentKey(content_id_entitlement.first);
|
||||
// Remove the entry if |entitlement_key| already referenced by a content key
|
||||
// with the same parity. Each entitlement key can only be referred by one
|
||||
// even and one odd content key within an entitled key session.
|
||||
for (auto const& content_key_entry : content_key_info_map_) {
|
||||
const ContentKeyInfo* const key_info = content_key_entry.second.get();
|
||||
if (key_info->entitlement_key == entitlement_key &&
|
||||
key_info->content_key->is_even_key() == content_key->is_even_key()) {
|
||||
RemoveContentKey(content_key_entry.first);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// |content_key_id| must be unique.
|
||||
if (content_key_map_.find(content_key_id) != content_key_map_.end()) {
|
||||
if (content_key_info_map_.find(content_key_id) !=
|
||||
content_key_info_map_.end()) {
|
||||
return false;
|
||||
}
|
||||
content_key_map_[content_key_id] = std::move(content_key);
|
||||
entitlement_key_map_[content_key_id] = entitlement_key;
|
||||
|
||||
content_key_info_map_[content_key_id] =
|
||||
std::unique_ptr<ContentKeyInfo>(new ContentKeyInfo());
|
||||
content_key_info_map_[content_key_id]->content_key = std::move(content_key);
|
||||
content_key_info_map_[content_key_id]->entitlement_key = entitlement_key;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EntitledKeySession::RemoveContentKey(const KeyId& content_key_id) {
|
||||
if (content_key_map_.find(content_key_id) == content_key_map_.end()) {
|
||||
if (content_key_info_map_.find(content_key_id) ==
|
||||
content_key_info_map_.end()) {
|
||||
return false;
|
||||
}
|
||||
content_key_map_.erase(content_key_id);
|
||||
entitlement_key_map_.erase(content_key_id);
|
||||
content_key_info_map_.erase(content_key_id);
|
||||
return true;
|
||||
}
|
||||
|
||||
EntitledKeySession::ContentKeySlot* EntitledKeySession::GetKeySlotToUpdate(
|
||||
uint8_t* buffer) const {
|
||||
if (buffer == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Read the current content to know where to write.
|
||||
auto content_key_slots = reinterpret_cast<ContentKeySlots*>(buffer);
|
||||
int use_index = -1;
|
||||
for (size_t i = 0; i < sizeof(ContentKeySlots) / sizeof(ContentKeySlot);
|
||||
++i) {
|
||||
const ContentKeySlot& current_key_slot = content_key_slots->key_slots[i];
|
||||
// Use the existing slot, or the first available slot if there is no
|
||||
// existing one.
|
||||
if (current_key_slot.entitled_key_session_id == key_sid_) {
|
||||
use_index = i;
|
||||
break;
|
||||
}
|
||||
if (current_key_slot.entitled_key_session_id == 0 && use_index < 0) {
|
||||
use_index = i;
|
||||
}
|
||||
}
|
||||
if (use_index < 0) {
|
||||
LOGE("No key slot available to write keys.");
|
||||
return nullptr;
|
||||
}
|
||||
return &content_key_slots->key_slots[use_index];
|
||||
}
|
||||
|
||||
bool EntitledKeySession::WriteContentKeyToKeySlot(
|
||||
const KeyId& content_key_id, ContentKeySlot* key_slot) const {
|
||||
if (key_slot == nullptr) {
|
||||
LOGE("key_slot is null");
|
||||
return false;
|
||||
}
|
||||
EntitledKey* entitled_key = GetContentKey(content_key_id);
|
||||
if (entitled_key == nullptr) {
|
||||
LOGE("Content key is not found.");
|
||||
return false;
|
||||
}
|
||||
|
||||
key_slot->entitled_key_session_id = key_sid_;
|
||||
key_slot->cipher_mode = static_cast<uint8_t>(entitled_key->cipher_mode());
|
||||
memcpy(entitled_key->is_even_key() ? &key_slot->even_key[0]
|
||||
: &key_slot->odd_key[0],
|
||||
entitled_key->value().data(),
|
||||
std::min(entitled_key->value().size(), kContentKeySize));
|
||||
memcpy(entitled_key->is_even_key() ? &key_slot->even_iv[0]
|
||||
: &key_slot->odd_iv[0],
|
||||
entitled_key->content_iv().data(),
|
||||
std::min(entitled_key->content_iv().size(), kContentIvSize));
|
||||
msync(reinterpret_cast<void*>(key_slot), sizeof(ContentKeySlot), MS_SYNC);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EntitledKeySession::WriteToKeySlot(const KeyId& content_key_id) const {
|
||||
bool result = false;
|
||||
const int fd = OpenHardwareKeySlot();
|
||||
if (fd < 0) {
|
||||
return result;
|
||||
}
|
||||
if (!LockHardwareKeySlot(fd)) {
|
||||
close(fd);
|
||||
return result;
|
||||
}
|
||||
ftruncate(fd, sizeof(ContentKeySlots));
|
||||
uint8_t* buffer = static_cast<uint8_t*>(mmap(nullptr, sizeof(ContentKeySlots),
|
||||
PROT_READ | PROT_WRITE,
|
||||
MAP_SHARED, fd, /*offset=*/0));
|
||||
if (buffer == MAP_FAILED) {
|
||||
LOGE("Fail to get mmap buffer %d", errno);
|
||||
close(fd);
|
||||
return result;
|
||||
}
|
||||
|
||||
ContentKeySlot* key_slot = GetKeySlotToUpdate(buffer);
|
||||
if (key_slot != nullptr) {
|
||||
result = WriteContentKeyToKeySlot(content_key_id, key_slot);
|
||||
}
|
||||
munmap(reinterpret_cast<void*>(buffer), sizeof(ContentKeySlots));
|
||||
UnlockAndCloseHardwareKeySlot(fd);
|
||||
return result;
|
||||
}
|
||||
|
||||
void EntitledKeySession::ClearKeySlot() const {
|
||||
const int fd = OpenHardwareKeySlot();
|
||||
if (fd < 0) {
|
||||
return;
|
||||
}
|
||||
if (!LockHardwareKeySlot(fd)) {
|
||||
close(fd);
|
||||
return;
|
||||
}
|
||||
ftruncate(fd, sizeof(ContentKeySlots));
|
||||
uint8_t* buffer = static_cast<uint8_t*>(mmap(nullptr, sizeof(ContentKeySlots),
|
||||
PROT_READ | PROT_WRITE,
|
||||
MAP_SHARED, fd, /*offset=*/0));
|
||||
if (buffer == MAP_FAILED) {
|
||||
LOGE("Fail to get mmap buffer %d", errno);
|
||||
close(fd);
|
||||
return;
|
||||
}
|
||||
|
||||
ContentKeySlot* key_slot = GetKeySlotToUpdate(buffer);
|
||||
if (key_slot != nullptr) {
|
||||
memset(key_slot, 0, sizeof(ContentKeySlot));
|
||||
msync(reinterpret_cast<void*>(key_slot), sizeof(ContentKeySlot), MS_SYNC);
|
||||
}
|
||||
munmap(reinterpret_cast<void*>(buffer), sizeof(ContentKeySlots));
|
||||
UnlockAndCloseHardwareKeySlot(fd);
|
||||
}
|
||||
|
||||
/***************************************/
|
||||
|
||||
EntitledKeySession* EntitledKeySessionTable::CreateEntitledKeySession(
|
||||
|
||||
@@ -28,7 +28,7 @@ class EntitledKeySession {
|
||||
current_entitlement_key_(nullptr){};
|
||||
EntitledKeySession(const EntitledKeySession&) = delete;
|
||||
EntitledKeySession(EntitledKeySession&&) = delete;
|
||||
~EntitledKeySession() = default;
|
||||
~EntitledKeySession();
|
||||
|
||||
// Get id of this entitled key session .
|
||||
SessionId GetSessionId() const { return key_sid_; }
|
||||
@@ -55,17 +55,47 @@ class EntitledKeySession {
|
||||
// Remove a content key |content_key_id|.
|
||||
bool RemoveContentKey(const KeyId& content_key_id);
|
||||
|
||||
// Write the content key info to hardware key slot, which can be retrieved
|
||||
// from Android TV Tuner Hal implementation.
|
||||
bool WriteToKeySlot(const KeyId& content_key_id) const;
|
||||
// Clear the content keys written to hardware key slots by this entitled key
|
||||
// session. The function is called by destructor.
|
||||
void ClearKeySlot() const;
|
||||
|
||||
// Total number of content keys this entitled key session holds.
|
||||
size_t size() const { return content_key_map_.size(); }
|
||||
size_t size() const { return content_key_info_map_.size(); }
|
||||
|
||||
private:
|
||||
struct ContentKeyInfo {
|
||||
std::unique_ptr<EntitledKey> content_key;
|
||||
Key* entitlement_key;
|
||||
};
|
||||
|
||||
// Must have the same exact definition on the tuner hal side.
|
||||
typedef struct {
|
||||
uint32_t entitled_key_session_id;
|
||||
uint8_t cipher_mode;
|
||||
uint8_t even_key[16];
|
||||
uint8_t even_iv[16];
|
||||
uint8_t odd_key[16];
|
||||
uint8_t odd_iv[16];
|
||||
} ContentKeySlot;
|
||||
|
||||
// Must have the same exact definition on the tuner hal side.
|
||||
typedef struct {
|
||||
// Assume max number of slots is 16.
|
||||
ContentKeySlot key_slots[16];
|
||||
} ContentKeySlots;
|
||||
|
||||
ContentKeySlot* GetKeySlotToUpdate(uint8_t* buffer) const;
|
||||
bool WriteContentKeyToKeySlot(const KeyId& content_key_id,
|
||||
ContentKeySlot* key_slot) const;
|
||||
|
||||
const SessionId key_sid_;
|
||||
EntitledKey* current_content_key_;
|
||||
const Key* current_entitlement_key_;
|
||||
// Map from content key id to content key.
|
||||
std::map<KeyId, std::unique_ptr<EntitledKey>> content_key_map_;
|
||||
// Map from content key id to referenced entitlement key.
|
||||
std::map<KeyId, Key*> entitlement_key_map_;
|
||||
// Map from content key id to content key info.
|
||||
std::map<KeyId, std::unique_ptr<ContentKeyInfo>> content_key_info_map_;
|
||||
};
|
||||
|
||||
class EntitledKeySessionTable {
|
||||
|
||||
@@ -68,7 +68,9 @@ class Key {
|
||||
class EntitledKey {
|
||||
public:
|
||||
explicit EntitledKey(std::vector<uint8_t> key_string)
|
||||
: value_(std::move(key_string)), cipher_mode_(OEMCrypto_CipherMode_CTR){};
|
||||
: value_(std::move(key_string)),
|
||||
cipher_mode_(OEMCrypto_CipherMode_CTR),
|
||||
is_even_key_(true){};
|
||||
EntitledKey(const EntitledKey&) = default;
|
||||
EntitledKey(EntitledKey&&) = default;
|
||||
~EntitledKey() = default;
|
||||
@@ -78,11 +80,14 @@ class EntitledKey {
|
||||
void set_cipher_mode(OEMCryptoCipherMode mode) { cipher_mode_ = mode; }
|
||||
void set_content_iv(const std::vector<uint8_t>& iv) { content_iv_ = iv; }
|
||||
const std::vector<uint8_t>& content_iv() const { return content_iv_; }
|
||||
bool is_even_key() const { return is_even_key_; }
|
||||
void set_is_even_key(bool is_even_key) { is_even_key_ = is_even_key; }
|
||||
|
||||
private:
|
||||
std::vector<uint8_t> value_;
|
||||
OEMCryptoCipherMode cipher_mode_;
|
||||
std::vector<uint8_t> content_iv_;
|
||||
bool is_even_key_;
|
||||
};
|
||||
|
||||
} // namespace wvoec_ref
|
||||
|
||||
@@ -470,16 +470,16 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_LoadCasECMKeys(
|
||||
LOGE("[OEMCrypto_LoadCasECMKeys(): ERROR_INVALID_SESSION]");
|
||||
return OEMCrypto_ERROR_INVALID_SESSION;
|
||||
}
|
||||
std::vector<OEMCrypto_EntitledCasKeyObject> key_array;
|
||||
std::vector<const OEMCrypto_EntitledCasKeyObject*> key_array;
|
||||
key_array.reserve(2);
|
||||
if (even_key) key_array.push_back(*even_key);
|
||||
if (odd_key) key_array.push_back(*odd_key);
|
||||
if (even_key) key_array.push_back(even_key);
|
||||
if (odd_key) key_array.push_back(odd_key);
|
||||
for (unsigned int i = 0; i < key_array.size(); i++) {
|
||||
if (!RangeCheck(message_length, key_array[i].entitlement_key_id, false) ||
|
||||
!RangeCheck(message_length, key_array[i].content_key_id, false) ||
|
||||
!RangeCheck(message_length, key_array[i].content_key_data_iv, false) ||
|
||||
!RangeCheck(message_length, key_array[i].content_key_data, false) ||
|
||||
!RangeCheck(message_length, key_array[i].content_iv, false)) {
|
||||
if (!RangeCheck(message_length, key_array[i]->entitlement_key_id, false) ||
|
||||
!RangeCheck(message_length, key_array[i]->content_key_id, false) ||
|
||||
!RangeCheck(message_length, key_array[i]->content_key_data_iv, false) ||
|
||||
!RangeCheck(message_length, key_array[i]->content_key_data, false) ||
|
||||
!RangeCheck(message_length, key_array[i]->content_iv, true)) {
|
||||
LOGE(
|
||||
"[OEMCrypto_LoadCasECMKeys(): "
|
||||
"OEMCrypto_ERROR_INVALID_CONTEXT -range "
|
||||
@@ -489,8 +489,7 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_LoadCasECMKeys(
|
||||
}
|
||||
}
|
||||
return session_ctx->LoadEntitledCasKeys(entitled_key_session, message,
|
||||
message_length, key_array.size(),
|
||||
&key_array[0]);
|
||||
message_length, even_key, odd_key);
|
||||
}
|
||||
|
||||
OEMCRYPTO_API OEMCryptoResult OEMCrypto_LoadRenewal(OEMCrypto_SESSION session,
|
||||
|
||||
@@ -840,9 +840,9 @@ OEMCryptoResult SessionContext::LoadEntitledContentKeys(
|
||||
|
||||
OEMCryptoResult SessionContext::LoadEntitledCasKeys(
|
||||
EntitledKeySession* key_session, const uint8_t* message,
|
||||
size_t message_length, size_t key_array_length,
|
||||
const OEMCrypto_EntitledCasKeyObject* key_array) {
|
||||
if (!key_array) {
|
||||
size_t message_length, const OEMCrypto_EntitledCasKeyObject* even_key,
|
||||
const OEMCrypto_EntitledCasKeyObject* odd_key) {
|
||||
if (even_key == nullptr && odd_key == nullptr) {
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
if (session_keys_ == nullptr ||
|
||||
@@ -853,8 +853,13 @@ OEMCryptoResult SessionContext::LoadEntitledCasKeys(
|
||||
return OEMCrypto_ERROR_INVALID_ENTITLED_KEY_SESSION;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < key_array_length; ++i) {
|
||||
const OEMCrypto_EntitledCasKeyObject* key_data = &key_array[i];
|
||||
const std::vector<const OEMCrypto_EntitledCasKeyObject*> key_array = {
|
||||
even_key, odd_key};
|
||||
for (size_t i = 0; i < key_array.size(); ++i) {
|
||||
if (key_array[i] == nullptr) {
|
||||
continue;
|
||||
}
|
||||
const OEMCrypto_EntitledCasKeyObject* key_data = key_array[i];
|
||||
std::vector<uint8_t> entitlement_key_id;
|
||||
entitlement_key_id.assign(message + key_data->entitlement_key_id.offset,
|
||||
message + key_data->entitlement_key_id.offset +
|
||||
@@ -864,6 +869,15 @@ OEMCryptoResult SessionContext::LoadEntitledCasKeys(
|
||||
if (entitlement_key == nullptr) {
|
||||
return OEMCrypto_KEY_NOT_ENTITLED;
|
||||
}
|
||||
|
||||
// Assume in Tuner Hal, buffer type is always secure.
|
||||
OEMCryptoResult result = CheckKeyControlBlockUse(
|
||||
entitlement_key->control(), "LoadEntitledCasKeys", /*use_type=*/0,
|
||||
OEMCrypto_BufferType_Secure);
|
||||
if (result != OEMCrypto_SUCCESS) {
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> content_key;
|
||||
std::vector<uint8_t> iv;
|
||||
std::vector<uint8_t> encrypted_content_key;
|
||||
@@ -893,11 +907,19 @@ OEMCryptoResult SessionContext::LoadEntitledCasKeys(
|
||||
content_key_obj->set_content_iv(content_iv);
|
||||
}
|
||||
content_key_obj->set_cipher_mode(key_data->cipher_mode);
|
||||
content_key_obj->set_is_even_key(i % 2 == 0);
|
||||
|
||||
if (!key_session->AddOrUpdateContentKey(entitlement_key, content_key_id,
|
||||
std::move(content_key_obj))) {
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
|
||||
if (!key_session->WriteToKeySlot(content_key_id)) {
|
||||
// Currently assume this is acceptable.
|
||||
LOGE("Failed to update content keys to hardware KeySlots.");
|
||||
} else {
|
||||
LOGI("Updated content keys to hardware KeySlots.");
|
||||
}
|
||||
}
|
||||
return OEMCrypto_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -118,8 +118,8 @@ class SessionContext {
|
||||
const OEMCrypto_EntitledContentKeyObject* key_array);
|
||||
virtual OEMCryptoResult LoadEntitledCasKeys(
|
||||
EntitledKeySession* key_session, const uint8_t* message,
|
||||
size_t message_length, size_t key_array_length,
|
||||
const OEMCrypto_EntitledCasKeyObject* key_array);
|
||||
size_t message_length, const OEMCrypto_EntitledCasKeyObject* even_key,
|
||||
const OEMCrypto_EntitledCasKeyObject* odd_key);
|
||||
virtual OEMCryptoResult InstallKey(
|
||||
const KeyId& key_id, const std::vector<uint8_t>& key_data,
|
||||
const std::vector<uint8_t>& key_data_iv,
|
||||
|
||||
@@ -794,13 +794,79 @@ void EntitledMessage::LoadKeys(OEMCryptoResult expected_sts) {
|
||||
VerifyEntitlementTestKeys();
|
||||
}
|
||||
|
||||
void EntitledMessage::LoadCasKeys(OEMCryptoResult expected_sts, bool load_even,
|
||||
bool load_odd) {
|
||||
for (size_t i = 0; i < num_keys_; ++i) {
|
||||
EntitledContentKeyData* key_data = &entitled_key_data_[i];
|
||||
const size_t entitlement_key_index = key_data->key_index;
|
||||
MessageKeyData* entitlement_key =
|
||||
&license_messages_->response_data().keys[entitlement_key_index];
|
||||
|
||||
// Load the entitlement key from |key_array_|.
|
||||
AES_KEY aes_key;
|
||||
AES_set_encrypt_key(entitlement_key->key_data, 256, &aes_key);
|
||||
|
||||
// Encrypt the content key with the entitlement key.
|
||||
uint8_t iv[16];
|
||||
memcpy(&iv[0], key_data->content_key_data_iv, KEY_IV_SIZE);
|
||||
AES_cbc_encrypt(key_data->content_key_data,
|
||||
key_data->encrypted_content_key_data, KEY_SIZE, &aes_key,
|
||||
iv, AES_ENCRYPT);
|
||||
}
|
||||
|
||||
// Convert the OEMCrypto_EntitledContentKeyObject to
|
||||
// OEMCrypto_EntitledCasKeyObject. Only the first two key object is used.
|
||||
OEMCrypto_EntitledCasKeyObject even_key;
|
||||
OEMCrypto_EntitledCasKeyObject odd_key;
|
||||
bool has_even = load_even && num_keys_ >= 1;
|
||||
bool has_odd = load_odd && num_keys_ >= 2;
|
||||
if (has_even) {
|
||||
even_key.entitlement_key_id = entitled_key_array_[0].entitlement_key_id;
|
||||
even_key.content_key_id = entitled_key_array_[0].content_key_id;
|
||||
even_key.content_key_data_iv = entitled_key_array_[0].content_key_data_iv;
|
||||
even_key.content_key_data = entitled_key_array_[0].content_key_data;
|
||||
even_key.content_iv.length = 0;
|
||||
}
|
||||
if (has_odd) {
|
||||
odd_key.entitlement_key_id = entitled_key_array_[1].entitlement_key_id;
|
||||
odd_key.content_key_id = entitled_key_array_[1].content_key_id;
|
||||
odd_key.content_key_data_iv = entitled_key_array_[1].content_key_data_iv;
|
||||
odd_key.content_key_data = entitled_key_array_[1].content_key_data;
|
||||
odd_key.content_iv.length = 0;
|
||||
}
|
||||
|
||||
ASSERT_EQ(
|
||||
expected_sts,
|
||||
OEMCrypto_LoadCasECMKeys(
|
||||
key_session_, reinterpret_cast<const uint8_t*>(entitled_key_data_),
|
||||
sizeof(entitled_key_data_), has_even ? &even_key : nullptr,
|
||||
has_odd ? &odd_key : nullptr));
|
||||
if (expected_sts != OEMCrypto_SUCCESS) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (has_even) {
|
||||
VerifyEntitlementTestKey(0);
|
||||
}
|
||||
if (has_odd) {
|
||||
VerifyEntitlementTestKey(1);
|
||||
}
|
||||
}
|
||||
|
||||
// This function verifies that the key control block reported by OEMCrypto agree
|
||||
// with the truth key control block. Failures in this function probably
|
||||
// indicate the OEMCrypto_LoadEntitledKeys did not correctly process the key
|
||||
// control block.
|
||||
void EntitledMessage::VerifyEntitlementTestKeys() {
|
||||
for (unsigned int i = 0; i < num_keys_; i++) {
|
||||
EntitledContentKeyData* key_data = &entitled_key_data_[i];
|
||||
VerifyEntitlementTestKey(i);
|
||||
}
|
||||
}
|
||||
|
||||
void EntitledMessage::VerifyEntitlementTestKey(size_t index) {
|
||||
ASSERT_GE(num_keys_, index);
|
||||
|
||||
EntitledContentKeyData* key_data = &entitled_key_data_[index];
|
||||
const size_t entitlement_key_index = key_data->key_index;
|
||||
MessageKeyData* entitlement_key =
|
||||
&license_messages_->response_data().keys[entitlement_key_index];
|
||||
@@ -816,11 +882,10 @@ void EntitledMessage::VerifyEntitlementTestKeys() {
|
||||
// we change to host byte order.
|
||||
ASSERT_EQ((htonl_fnc(entitlement_key->control.duration)),
|
||||
(htonl_fnc(block.duration)))
|
||||
<< "For key " << i;
|
||||
<< "For key " << index;
|
||||
ASSERT_EQ(htonl_fnc(entitlement_key->control.control_bits),
|
||||
htonl_fnc(block.control_bits))
|
||||
<< "For key " << i;
|
||||
}
|
||||
<< "For key " << index;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -419,12 +419,14 @@ class EntitledMessage {
|
||||
key_session_ = key_session;
|
||||
}
|
||||
void LoadKeys(OEMCryptoResult expected_sts);
|
||||
void LoadCasKeys(OEMCryptoResult expected_sts, bool load_even, bool load_odd);
|
||||
void set_num_keys(uint32_t num_keys) { num_keys_ = num_keys; }
|
||||
uint32_t num_keys() const { return num_keys_; }
|
||||
void SetEntitlementKeyId(unsigned int index, const std::string& key_id);
|
||||
void SetContentKeyId(unsigned int index, const std::string& key_id);
|
||||
// Verify that key control blocks of the loaded keys.
|
||||
void VerifyEntitlementTestKeys();
|
||||
void VerifyEntitlementTestKey(size_t index);
|
||||
|
||||
private:
|
||||
// Find the offset of the give pointer, relative to |entitled_key_data_|.
|
||||
|
||||
@@ -896,7 +896,7 @@ TEST_P(OEMCryptoLicenseTest, LoadKeyWithNonceTwiceAPI16) {
|
||||
}
|
||||
|
||||
// This verifies that entitlement keys and entitled content keys can be loaded.
|
||||
TEST_P(OEMCryptoLicenseTest, LoadEntitlementKeysAPI17) {
|
||||
TEST_P(OEMCryptoLicenseTest, LoadEntitlementKeysAPI16) {
|
||||
ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce());
|
||||
license_messages_.set_license_type(OEMCrypto_EntitlementLicense);
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest());
|
||||
@@ -906,25 +906,24 @@ TEST_P(OEMCryptoLicenseTest, LoadEntitlementKeysAPI17) {
|
||||
uint32_t key_session_id = 0;
|
||||
OEMCryptoResult sts = OEMCrypto_CreateEntitledKeySession(
|
||||
session_.session_id(), &key_session_id);
|
||||
if (sts == OEMCrypto_ERROR_NOT_IMPLEMENTED) {
|
||||
return;
|
||||
}
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
|
||||
ASSERT_NE(key_session_id, 0u);
|
||||
|
||||
EntitledMessage entitled_message_1(&license_messages_);
|
||||
entitled_message_1.FillKeyArray();
|
||||
entitled_message_1.SetEntitledKeySession(key_session_id);
|
||||
ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadKeys(OEMCrypto_SUCCESS));
|
||||
ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadCasKeys(
|
||||
OEMCrypto_SUCCESS, /*load_even=*/true, /*load_odd=*/true));
|
||||
EntitledMessage entitled_message_2(&license_messages_);
|
||||
entitled_message_2.FillKeyArray();
|
||||
entitled_message_2.SetEntitledKeySession(key_session_id);
|
||||
ASSERT_NO_FATAL_FAILURE(entitled_message_2.LoadKeys(OEMCrypto_SUCCESS));
|
||||
ASSERT_NO_FATAL_FAILURE(entitled_message_2.LoadCasKeys(
|
||||
OEMCrypto_SUCCESS, /*load_even=*/true, /*load_odd=*/true));
|
||||
}
|
||||
|
||||
// This verifies that entitled content keys cannot be loaded if we have not yet
|
||||
// loaded the entitlement keys.
|
||||
TEST_P(OEMCryptoLicenseTest, LoadEntitlementKeysNoEntitlementKeysAPI17) {
|
||||
TEST_P(OEMCryptoLicenseTest, LoadEntitlementKeysNoEntitlementKeysAPI16) {
|
||||
ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce());
|
||||
license_messages_.set_license_type(OEMCrypto_EntitlementLicense);
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest());
|
||||
@@ -933,22 +932,19 @@ TEST_P(OEMCryptoLicenseTest, LoadEntitlementKeysNoEntitlementKeysAPI17) {
|
||||
uint32_t key_session_id = 0;
|
||||
OEMCryptoResult sts = OEMCrypto_CreateEntitledKeySession(
|
||||
session_.session_id(), &key_session_id);
|
||||
if (sts == OEMCrypto_ERROR_NOT_IMPLEMENTED) {
|
||||
return;
|
||||
}
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
|
||||
ASSERT_NE(key_session_id, 0u);
|
||||
|
||||
EntitledMessage entitled_message_1(&license_messages_);
|
||||
entitled_message_1.FillKeyArray();
|
||||
entitled_message_1.SetEntitledKeySession(key_session_id);
|
||||
ASSERT_NO_FATAL_FAILURE(
|
||||
entitled_message_1.LoadKeys(OEMCrypto_ERROR_INVALID_CONTEXT));
|
||||
ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadCasKeys(
|
||||
OEMCrypto_ERROR_INVALID_CONTEXT, /*load_even=*/true, /*load_odd=*/true));
|
||||
}
|
||||
|
||||
// This verifies that entitled content keys cannot be loaded if we have loaded
|
||||
// the wrong entitlement keys.
|
||||
TEST_P(OEMCryptoLicenseTest, LoadEntitlementKeysWrongEntitlementKeysAPI17) {
|
||||
TEST_P(OEMCryptoLicenseTest, LoadEntitlementKeysWrongEntitlementKeysAPI16) {
|
||||
ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce());
|
||||
license_messages_.set_license_type(OEMCrypto_EntitlementLicense);
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest());
|
||||
@@ -958,9 +954,6 @@ TEST_P(OEMCryptoLicenseTest, LoadEntitlementKeysWrongEntitlementKeysAPI17) {
|
||||
uint32_t key_session_id = 0;
|
||||
OEMCryptoResult sts = OEMCrypto_CreateEntitledKeySession(
|
||||
session_.session_id(), &key_session_id);
|
||||
if (sts == OEMCrypto_ERROR_NOT_IMPLEMENTED) {
|
||||
return;
|
||||
}
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
|
||||
ASSERT_NE(key_session_id, 0u);
|
||||
|
||||
@@ -969,13 +962,13 @@ TEST_P(OEMCryptoLicenseTest, LoadEntitlementKeysWrongEntitlementKeysAPI17) {
|
||||
const std::string key_id = "no_key";
|
||||
entitled_message_1.SetEntitlementKeyId(0, key_id);
|
||||
entitled_message_1.SetEntitledKeySession(key_session_id);
|
||||
ASSERT_NO_FATAL_FAILURE(
|
||||
entitled_message_1.LoadKeys(OEMCrypto_KEY_NOT_ENTITLED));
|
||||
ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadCasKeys(
|
||||
OEMCrypto_KEY_NOT_ENTITLED, /*load_even=*/true, /*load_odd=*/true));
|
||||
}
|
||||
|
||||
// This verifies that entitled content keys cannot be loaded if we specify an
|
||||
// entitled key session that has not been created.
|
||||
TEST_P(OEMCryptoLicenseTest, LoadEntitlementKeysWrongEntitledKeySessionAPI17) {
|
||||
TEST_P(OEMCryptoLicenseTest, LoadEntitlementKeysWrongEntitledKeySessionAPI16) {
|
||||
ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce());
|
||||
license_messages_.set_license_type(OEMCrypto_EntitlementLicense);
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest());
|
||||
@@ -985,22 +978,20 @@ TEST_P(OEMCryptoLicenseTest, LoadEntitlementKeysWrongEntitledKeySessionAPI17) {
|
||||
uint32_t key_session_id = 0;
|
||||
OEMCryptoResult sts = OEMCrypto_CreateEntitledKeySession(
|
||||
session_.session_id(), &key_session_id);
|
||||
if (sts == OEMCrypto_ERROR_NOT_IMPLEMENTED) {
|
||||
return;
|
||||
}
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
|
||||
ASSERT_NE(key_session_id, 0u);
|
||||
|
||||
EntitledMessage entitled_message_1(&license_messages_);
|
||||
entitled_message_1.FillKeyArray();
|
||||
entitled_message_1.SetEntitledKeySession(0);
|
||||
ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadKeys(
|
||||
OEMCrypto_ERROR_INVALID_ENTITLED_KEY_SESSION));
|
||||
ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadCasKeys(
|
||||
OEMCrypto_ERROR_INVALID_ENTITLED_KEY_SESSION, /*load_even=*/true,
|
||||
/*load_odd=*/true));
|
||||
}
|
||||
|
||||
// This verifies that entitled content keys cannot be loaded if we specify an
|
||||
// entitled key session that is actually an oemcrypto session.
|
||||
TEST_P(OEMCryptoLicenseTest, LoadEntitlementKeysOemcryptoSessionAPI17) {
|
||||
TEST_P(OEMCryptoLicenseTest, LoadEntitlementKeysOemcryptoSessionAPI16) {
|
||||
ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce());
|
||||
license_messages_.set_license_type(OEMCrypto_EntitlementLicense);
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest());
|
||||
@@ -1010,17 +1001,15 @@ TEST_P(OEMCryptoLicenseTest, LoadEntitlementKeysOemcryptoSessionAPI17) {
|
||||
uint32_t key_session_id = 0;
|
||||
OEMCryptoResult sts = OEMCrypto_CreateEntitledKeySession(
|
||||
session_.session_id(), &key_session_id);
|
||||
if (sts == OEMCrypto_ERROR_NOT_IMPLEMENTED) {
|
||||
return;
|
||||
}
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
|
||||
ASSERT_NE(key_session_id, 0u);
|
||||
|
||||
EntitledMessage entitled_message_1(&license_messages_);
|
||||
entitled_message_1.FillKeyArray();
|
||||
entitled_message_1.SetEntitledKeySession(session_.session_id());
|
||||
ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadKeys(
|
||||
OEMCrypto_ERROR_INVALID_ENTITLED_KEY_SESSION));
|
||||
ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadCasKeys(
|
||||
OEMCrypto_ERROR_INVALID_ENTITLED_KEY_SESSION, /*load_even=*/true,
|
||||
/*load_odd=*/true));
|
||||
}
|
||||
|
||||
// This tests load license with an 8k license response.
|
||||
@@ -1479,7 +1468,7 @@ TEST_P(OEMCryptoLicenseTest, SelectKeyNotThereAPI16) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(OEMCryptoLicenseTest, SelectKeyEntitledKeyAPI17) {
|
||||
TEST_P(OEMCryptoLicenseTest, SelectKeyEntitledKeyAPI16) {
|
||||
ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce());
|
||||
license_messages_.set_license_type(OEMCrypto_EntitlementLicense);
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest());
|
||||
@@ -1490,16 +1479,14 @@ TEST_P(OEMCryptoLicenseTest, SelectKeyEntitledKeyAPI17) {
|
||||
uint32_t key_session_id;
|
||||
OEMCryptoResult sts = OEMCrypto_CreateEntitledKeySession(
|
||||
session_.session_id(), &key_session_id);
|
||||
if (sts == OEMCrypto_ERROR_NOT_IMPLEMENTED) {
|
||||
return;
|
||||
}
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
|
||||
EntitledMessage entitled_message_1(&license_messages_);
|
||||
entitled_message_1.FillKeyArray();
|
||||
entitled_message_1.SetEntitledKeySession(key_session_id);
|
||||
const char* content_key_id = "content_key_id";
|
||||
entitled_message_1.SetContentKeyId(0, content_key_id);
|
||||
ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadKeys(OEMCrypto_SUCCESS));
|
||||
ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadCasKeys(
|
||||
OEMCrypto_SUCCESS, /*load_even=*/true, /*load_odd=*/false));
|
||||
|
||||
ASSERT_EQ(
|
||||
OEMCrypto_SUCCESS,
|
||||
@@ -1510,7 +1497,7 @@ TEST_P(OEMCryptoLicenseTest, SelectKeyEntitledKeyAPI17) {
|
||||
|
||||
// SelectEntitledKey should fail if we attempt to select a key that has not been
|
||||
// loaded. Also, the error should be NO_CONTENT_KEY.
|
||||
TEST_P(OEMCryptoLicenseTest, SelectKeyEntitledKeyNotThereAPI17) {
|
||||
TEST_P(OEMCryptoLicenseTest, SelectKeyEntitledKeyNotThereAPI16) {
|
||||
ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce());
|
||||
license_messages_.set_license_type(OEMCrypto_EntitlementLicense);
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest());
|
||||
@@ -1521,14 +1508,12 @@ TEST_P(OEMCryptoLicenseTest, SelectKeyEntitledKeyNotThereAPI17) {
|
||||
uint32_t key_session_id;
|
||||
OEMCryptoResult sts = OEMCrypto_CreateEntitledKeySession(
|
||||
session_.session_id(), &key_session_id);
|
||||
if (sts == OEMCrypto_ERROR_NOT_IMPLEMENTED) {
|
||||
return;
|
||||
}
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
|
||||
EntitledMessage entitled_message_1(&license_messages_);
|
||||
entitled_message_1.FillKeyArray();
|
||||
entitled_message_1.SetEntitledKeySession(key_session_id);
|
||||
ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadKeys(OEMCrypto_SUCCESS));
|
||||
ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadCasKeys(
|
||||
OEMCrypto_SUCCESS, /*load_even=*/true, /*load_odd=*/true));
|
||||
|
||||
const char* content_key_id = "no_key";
|
||||
ASSERT_EQ(
|
||||
@@ -1540,7 +1525,7 @@ TEST_P(OEMCryptoLicenseTest, SelectKeyEntitledKeyNotThereAPI17) {
|
||||
|
||||
// Select key with entitlement license fails if the key id is entitilement key
|
||||
// id.
|
||||
TEST_P(OEMCryptoLicenseTest, SelectKeyEntitlementKeyAPI17) {
|
||||
TEST_P(OEMCryptoLicenseTest, SelectKeyEntitlementKeyAPI16) {
|
||||
ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce());
|
||||
license_messages_.set_license_type(OEMCrypto_EntitlementLicense);
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest());
|
||||
@@ -1551,14 +1536,12 @@ TEST_P(OEMCryptoLicenseTest, SelectKeyEntitlementKeyAPI17) {
|
||||
uint32_t key_session_id;
|
||||
OEMCryptoResult sts = OEMCrypto_CreateEntitledKeySession(
|
||||
session_.session_id(), &key_session_id);
|
||||
if (sts == OEMCrypto_ERROR_NOT_IMPLEMENTED) {
|
||||
return;
|
||||
}
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
|
||||
EntitledMessage entitled_message_1(&license_messages_);
|
||||
entitled_message_1.FillKeyArray();
|
||||
entitled_message_1.SetEntitledKeySession(key_session_id);
|
||||
ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadKeys(OEMCrypto_SUCCESS));
|
||||
ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadCasKeys(
|
||||
OEMCrypto_SUCCESS, /*load_even=*/true, /*load_odd=*/true));
|
||||
|
||||
ASSERT_EQ(OEMCrypto_ERROR_INVALID_CONTEXT,
|
||||
OEMCrypto_SelectKey(session_.session_id(),
|
||||
@@ -1568,7 +1551,7 @@ TEST_P(OEMCryptoLicenseTest, SelectKeyEntitlementKeyAPI17) {
|
||||
}
|
||||
|
||||
// This verifies that entitled key sessions can be created and removed.
|
||||
TEST_P(OEMCryptoLicenseTest, EntitledKeySessionsAPI17) {
|
||||
TEST_P(OEMCryptoLicenseTest, EntitledKeySessionsAPI16) {
|
||||
ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce());
|
||||
license_messages_.set_license_type(OEMCrypto_EntitlementLicense);
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest());
|
||||
@@ -1579,9 +1562,6 @@ TEST_P(OEMCryptoLicenseTest, EntitledKeySessionsAPI17) {
|
||||
uint32_t key_session_id_1;
|
||||
OEMCryptoResult sts = OEMCrypto_CreateEntitledKeySession(
|
||||
session_.session_id(), &key_session_id_1);
|
||||
if (sts == OEMCrypto_ERROR_NOT_IMPLEMENTED) {
|
||||
return;
|
||||
}
|
||||
ASSERT_NE(key_session_id_1, 0u); // 0 is a reserved id number.
|
||||
|
||||
uint32_t key_session_id_2;
|
||||
@@ -1599,7 +1579,7 @@ TEST_P(OEMCryptoLicenseTest, EntitledKeySessionsAPI17) {
|
||||
|
||||
// This verifies that multiple entitled key sessions can be created. They can
|
||||
// load and select keys independently.
|
||||
TEST_P(OEMCryptoLicenseTest, EntitledKeySessionMultipleKeySessionsAPI17) {
|
||||
TEST_P(OEMCryptoLicenseTest, EntitledKeySessionMultipleKeySessionsAPI16) {
|
||||
ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce());
|
||||
license_messages_.set_license_type(OEMCrypto_EntitlementLicense);
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest());
|
||||
@@ -1610,16 +1590,14 @@ TEST_P(OEMCryptoLicenseTest, EntitledKeySessionMultipleKeySessionsAPI17) {
|
||||
uint32_t key_session_id_1;
|
||||
OEMCryptoResult sts = OEMCrypto_CreateEntitledKeySession(
|
||||
session_.session_id(), &key_session_id_1);
|
||||
if (sts == OEMCrypto_ERROR_NOT_IMPLEMENTED) {
|
||||
return;
|
||||
}
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
|
||||
EntitledMessage entitled_message_1(&license_messages_);
|
||||
entitled_message_1.FillKeyArray();
|
||||
entitled_message_1.SetEntitledKeySession(key_session_id_1);
|
||||
const char* content_key_id_1 = "content_key_id_1";
|
||||
entitled_message_1.SetContentKeyId(0, content_key_id_1);
|
||||
ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadKeys(OEMCrypto_SUCCESS));
|
||||
ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadCasKeys(
|
||||
OEMCrypto_SUCCESS, /*load_even=*/true, /*load_odd=*/true));
|
||||
// We can select content key 1 in entitled key session 1.
|
||||
ASSERT_EQ(
|
||||
OEMCrypto_SUCCESS,
|
||||
@@ -1638,7 +1616,8 @@ TEST_P(OEMCryptoLicenseTest, EntitledKeySessionMultipleKeySessionsAPI17) {
|
||||
entitled_message_2.SetEntitledKeySession(key_session_id_2);
|
||||
const char* content_key_id_2 = "content_key_id_2";
|
||||
entitled_message_2.SetContentKeyId(0, content_key_id_2);
|
||||
ASSERT_NO_FATAL_FAILURE(entitled_message_2.LoadKeys(OEMCrypto_SUCCESS));
|
||||
ASSERT_NO_FATAL_FAILURE(entitled_message_2.LoadCasKeys(
|
||||
OEMCrypto_SUCCESS, /*load_even=*/true, /*load_odd=*/true));
|
||||
// We can select content key 2 in entitled key session 2.
|
||||
ASSERT_EQ(
|
||||
OEMCrypto_SUCCESS,
|
||||
@@ -1660,9 +1639,9 @@ TEST_P(OEMCryptoLicenseTest, EntitledKeySessionMultipleKeySessionsAPI17) {
|
||||
}
|
||||
|
||||
// This verifies that within an entitled key session, each entitlement key can
|
||||
// corresponds to only one content key at most.
|
||||
// corresponds to only one even content key at most.
|
||||
TEST_P(OEMCryptoLicenseTest,
|
||||
EntitledKeySessionOneContentKeyPerEntitlementAPI17) {
|
||||
EntitledKeySessionOneContentKeyPerEntitlementAPI16) {
|
||||
ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce());
|
||||
license_messages_.set_license_type(OEMCrypto_EntitlementLicense);
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest());
|
||||
@@ -1673,9 +1652,6 @@ TEST_P(OEMCryptoLicenseTest,
|
||||
uint32_t key_session_id;
|
||||
OEMCryptoResult sts = OEMCrypto_CreateEntitledKeySession(
|
||||
session_.session_id(), &key_session_id);
|
||||
if (sts == OEMCrypto_ERROR_NOT_IMPLEMENTED) {
|
||||
return;
|
||||
}
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
|
||||
// Construct and load content keys to entitled key session.
|
||||
EntitledMessage entitled_message_1(&license_messages_);
|
||||
@@ -1683,7 +1659,8 @@ TEST_P(OEMCryptoLicenseTest,
|
||||
entitled_message_1.SetEntitledKeySession(key_session_id);
|
||||
const char* content_key_id_1 = "content_key_id_1";
|
||||
entitled_message_1.SetContentKeyId(0, content_key_id_1);
|
||||
ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadKeys(OEMCrypto_SUCCESS));
|
||||
ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadCasKeys(
|
||||
OEMCrypto_SUCCESS, /*load_even=*/true, /*load_odd=*/true));
|
||||
// We can select content key 1 in entitled key session.
|
||||
ASSERT_EQ(
|
||||
OEMCrypto_SUCCESS,
|
||||
@@ -1693,7 +1670,8 @@ TEST_P(OEMCryptoLicenseTest,
|
||||
// Load content key with new content id.
|
||||
const char* content_key_id_2 = "content_key_id_2";
|
||||
entitled_message_1.SetContentKeyId(0, content_key_id_2);
|
||||
ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadKeys(OEMCrypto_SUCCESS));
|
||||
ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadCasKeys(
|
||||
OEMCrypto_SUCCESS, /*load_even=*/true, /*load_odd=*/true));
|
||||
// We can select content key 2 in entitled key session.
|
||||
ASSERT_EQ(
|
||||
OEMCrypto_SUCCESS,
|
||||
@@ -1709,6 +1687,55 @@ TEST_P(OEMCryptoLicenseTest,
|
||||
strlen(content_key_id_1), OEMCrypto_CipherMode_CTR));
|
||||
}
|
||||
|
||||
// This verifies that within an entitled key session, each entitlement key can
|
||||
// be shared by both even and odd content keys.
|
||||
TEST_P(OEMCryptoLicenseTest,
|
||||
EntitledKeySessionEvenOddContentKeysPerEntitlementAPI16) {
|
||||
ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce());
|
||||
license_messages_.set_license_type(OEMCrypto_EntitlementLicense);
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest());
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse());
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse());
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse());
|
||||
|
||||
uint32_t key_session_id;
|
||||
OEMCryptoResult sts = OEMCrypto_CreateEntitledKeySession(
|
||||
session_.session_id(), &key_session_id);
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
|
||||
// Construct and load even content keys to entitled key session.
|
||||
EntitledMessage entitled_message_1(&license_messages_);
|
||||
entitled_message_1.FillKeyArray();
|
||||
entitled_message_1.SetEntitledKeySession(key_session_id);
|
||||
const char* content_key_id_1 = "content_key_id_1";
|
||||
entitled_message_1.SetContentKeyId(0, content_key_id_1);
|
||||
ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadCasKeys(
|
||||
OEMCrypto_SUCCESS, /*load_even=*/true, /*load_odd=*/false));
|
||||
// We can select content key 1 (even key) in entitled key session.
|
||||
ASSERT_EQ(
|
||||
OEMCrypto_SUCCESS,
|
||||
OEMCrypto_SelectKey(key_session_id,
|
||||
reinterpret_cast<const uint8_t*>(content_key_id_1),
|
||||
strlen(content_key_id_1), OEMCrypto_CipherMode_CTR));
|
||||
// Load odd content key with new content id.
|
||||
const char* content_key_id_2 = "content_key_id_2";
|
||||
entitled_message_1.SetContentKeyId(1, content_key_id_2);
|
||||
ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadCasKeys(
|
||||
OEMCrypto_SUCCESS, /*load_even=*/false, /*load_odd=*/true));
|
||||
// We can select content key 2 (odd key) in entitled key session.
|
||||
ASSERT_EQ(
|
||||
OEMCrypto_SUCCESS,
|
||||
OEMCrypto_SelectKey(key_session_id,
|
||||
reinterpret_cast<const uint8_t*>(content_key_id_2),
|
||||
strlen(content_key_id_2), OEMCrypto_CipherMode_CTR));
|
||||
// Content key one is still in the entitled key session as they are even and
|
||||
// odd keys that can use the same entitlement key.
|
||||
ASSERT_EQ(
|
||||
OEMCrypto_SUCCESS,
|
||||
OEMCrypto_SelectKey(key_session_id,
|
||||
reinterpret_cast<const uint8_t*>(content_key_id_1),
|
||||
strlen(content_key_id_1), OEMCrypto_CipherMode_CTR));
|
||||
}
|
||||
|
||||
// 'cens' mode is no longer supported in v16
|
||||
TEST_P(OEMCryptoLicenseTest, RejectCensAPI16) {
|
||||
ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce());
|
||||
@@ -1819,9 +1846,6 @@ TEST_P(OEMCryptoLicenseTest, QueryKeyControl) {
|
||||
session_.session_id(), license_messages_.response_data().keys[0].key_id,
|
||||
license_messages_.response_data().keys[0].key_id_length,
|
||||
reinterpret_cast<uint8_t*>(&block), &size);
|
||||
if (sts == OEMCrypto_ERROR_NOT_IMPLEMENTED) {
|
||||
return;
|
||||
}
|
||||
ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts);
|
||||
const char* key_id = "no_key";
|
||||
size = sizeof(block);
|
||||
@@ -2029,6 +2053,60 @@ TEST_P(OEMCryptoSessionTestDecryptWithHDCP, DecryptAPI09) {
|
||||
INSTANTIATE_TEST_CASE_P(TestHDCP, OEMCryptoSessionTestDecryptWithHDCP,
|
||||
Range(1, 6));
|
||||
|
||||
// Used to test the different HDCP versions. This test is parameterized by the
|
||||
// required HDCP version in the key control block.
|
||||
class OEMCryptoSessionTestLoadCasKeysWithHDCP : public OEMCryptoSessionTests,
|
||||
public WithParamInterface<int> {
|
||||
protected:
|
||||
void LoadCasKeysWithHDCP(OEMCrypto_HDCP_Capability version) {
|
||||
OEMCryptoResult sts;
|
||||
OEMCrypto_HDCP_Capability current, maximum;
|
||||
sts = OEMCrypto_GetHDCPCapability(¤t, &maximum);
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
|
||||
Session s;
|
||||
ASSERT_NO_FATAL_FAILURE(s.open());
|
||||
ASSERT_NO_FATAL_FAILURE(InstallTestRSAKey(&s));
|
||||
LicenseRoundTrip license_messages(&s);
|
||||
license_messages.set_control((version << wvoec::kControlHDCPVersionShift) |
|
||||
wvoec::kControlObserveHDCP |
|
||||
wvoec::kControlHDCPRequired);
|
||||
license_messages.set_license_type(OEMCrypto_EntitlementLicense);
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages.SignAndVerifyRequest());
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages.CreateDefaultResponse());
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages.EncryptAndSignResponse());
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, license_messages.LoadResponse());
|
||||
|
||||
uint32_t key_session_id;
|
||||
sts = OEMCrypto_CreateEntitledKeySession(s.session_id(), &key_session_id);
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
|
||||
EntitledMessage entitled_message_1(&license_messages);
|
||||
entitled_message_1.FillKeyArray();
|
||||
entitled_message_1.SetEntitledKeySession(key_session_id);
|
||||
|
||||
if (version > current) {
|
||||
ASSERT_NO_FATAL_FAILURE(
|
||||
entitled_message_1.LoadCasKeys(OEMCrypto_ERROR_INSUFFICIENT_HDCP,
|
||||
/*load_even=*/true, /*load_odd=*/true))
|
||||
<< "Failed when current HDCP = " << HDCPCapabilityAsString(current)
|
||||
<< ", maximum HDCP = " << HDCPCapabilityAsString(maximum)
|
||||
<< ", license HDCP = " << HDCPCapabilityAsString(version);
|
||||
} else {
|
||||
ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadCasKeys(
|
||||
OEMCrypto_SUCCESS, /*load_even=*/true, /*load_odd=*/true))
|
||||
<< "Failed when current HDCP = " << HDCPCapabilityAsString(current)
|
||||
<< ", maximum HDCP = " << HDCPCapabilityAsString(maximum)
|
||||
<< ", license HDCP = " << HDCPCapabilityAsString(version);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TEST_P(OEMCryptoSessionTestLoadCasKeysWithHDCP, LoadCasECMKeysAPI16) {
|
||||
// Test parameterized by HDCP version.
|
||||
LoadCasKeysWithHDCP(static_cast<OEMCrypto_HDCP_Capability>(GetParam()));
|
||||
}
|
||||
INSTANTIATE_TEST_CASE_P(TestHDCP, OEMCryptoSessionTestLoadCasKeysWithHDCP,
|
||||
Range(1, 6));
|
||||
|
||||
//
|
||||
// Load, Refresh Keys Test
|
||||
//
|
||||
@@ -2924,6 +3002,54 @@ TEST_P(OEMCryptoLicenseTest, KeyDuration) {
|
||||
ASSERT_NO_FATAL_FAILURE(session_.TestSelectExpired(0));
|
||||
}
|
||||
|
||||
TEST_P(OEMCryptoLicenseTest, LoadCasKeysNoAnalogAPI16) {
|
||||
ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce());
|
||||
license_messages_.set_license_type(OEMCrypto_EntitlementLicense);
|
||||
license_messages_.set_control(wvoec::kControlDisableAnalogOutput);
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest());
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse());
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse());
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse());
|
||||
uint32_t key_session_id;
|
||||
OEMCryptoResult sts = OEMCrypto_CreateEntitledKeySession(
|
||||
session_.session_id(), &key_session_id);
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
|
||||
|
||||
EntitledMessage entitled_message(&license_messages_);
|
||||
entitled_message.FillKeyArray();
|
||||
entitled_message.SetEntitledKeySession(key_session_id);
|
||||
ASSERT_NO_FATAL_FAILURE(entitled_message.LoadCasKeys(
|
||||
OEMCrypto_ERROR_ANALOG_OUTPUT, /*load_even=*/true, /*load_odd=*/true));
|
||||
}
|
||||
|
||||
// Test that key duration is honored.
|
||||
TEST_P(OEMCryptoLicenseTest, LoadCasKeysKeyDurationAPI16) {
|
||||
ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce());
|
||||
license_messages_.set_license_type(OEMCrypto_EntitlementLicense);
|
||||
license_messages_.core_response()
|
||||
.timer_limits.total_playback_duration_seconds = kDuration;
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest());
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse());
|
||||
ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse());
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse());
|
||||
uint32_t key_session_id;
|
||||
OEMCryptoResult sts = OEMCrypto_CreateEntitledKeySession(
|
||||
session_.session_id(), &key_session_id);
|
||||
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
|
||||
EntitledMessage entitled_message(&license_messages_);
|
||||
entitled_message.FillKeyArray();
|
||||
entitled_message.SetEntitledKeySession(key_session_id);
|
||||
|
||||
ASSERT_NO_FATAL_FAILURE(entitled_message.LoadCasKeys(
|
||||
OEMCrypto_SUCCESS, /*load_even=*/true, /*load_odd=*/true));
|
||||
wvutil::TestSleep::Sleep(kShortSleep); // Should still be valid key.
|
||||
ASSERT_NO_FATAL_FAILURE(entitled_message.LoadCasKeys(
|
||||
OEMCrypto_SUCCESS, /*load_even=*/true, /*load_odd=*/true));
|
||||
wvutil::TestSleep::Sleep(kLongSleep); // Should be expired key.
|
||||
ASSERT_NO_FATAL_FAILURE(entitled_message.LoadCasKeys(
|
||||
OEMCrypto_ERROR_KEY_EXPIRED, /*load_even=*/true, /*load_odd=*/true));
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(TestAll, OEMCryptoLicenseTest,
|
||||
Range<uint32_t>(kCurrentAPI - 1, kCurrentAPI + 1));
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ cc_library_static {
|
||||
"src/widevine_media_cas.cpp",
|
||||
"src/cas_license.cpp",
|
||||
"src/ecm_parser.cpp",
|
||||
"src/ecm_parser_v2.cpp",
|
||||
"src/ecm_parser_v3.cpp",
|
||||
"src/license_key_status.cpp",
|
||||
"src/oemcrypto_interface.cpp",
|
||||
"src/policy_engine.cpp",
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
#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 {
|
||||
@@ -48,9 +50,41 @@ typedef enum {
|
||||
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 SESSION_FINGERPRINTING_CONTROL event.
|
||||
typedef enum {
|
||||
FINGERPRINTING_CONTROL = 0,
|
||||
} SessionFingerprintingFieldType;
|
||||
|
||||
// Types used inside an SESSION_SERVICE_BLOCKING_GROUPS event.
|
||||
typedef enum {
|
||||
SERVICE_BLOCKING_DEVICE_GROUP = 0,
|
||||
} SessionServiceBlockingFieldType;
|
||||
|
||||
#endif // CAS_EVENTS_H
|
||||
|
||||
@@ -110,6 +110,13 @@ class CasLicense : public wvutil::TimerHandler, public wvcas::CasEventListener {
|
||||
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;
|
||||
|
||||
// Query to see if the license is expired.
|
||||
virtual bool IsExpired() const;
|
||||
|
||||
|
||||
@@ -89,6 +89,14 @@ class CasEventListener {
|
||||
virtual void OnAgeRestrictionUpdated(const WvCasSessionId& sessionId,
|
||||
uint8_t ecm_age_restriction) = 0;
|
||||
|
||||
// Notifies listeners of new fingerprinting info.
|
||||
virtual void OnSessionFingerprintingUpdated(
|
||||
const WvCasSessionId& sessionId, const CasData& fingerprinting) = 0;
|
||||
|
||||
// Notifies listeners of new service blocking info.
|
||||
virtual void OnSessionServiceBlockingUpdated(
|
||||
const WvCasSessionId& sessionId, const CasData& service_blocking) = 0;
|
||||
|
||||
CasEventListener(const CasEventListener&) = delete;
|
||||
CasEventListener& operator=(const CasEventListener&) = delete;
|
||||
};
|
||||
|
||||
@@ -9,56 +9,42 @@
|
||||
#include <vector>
|
||||
|
||||
#include "cas_types.h"
|
||||
#include "media_cas.pb.h"
|
||||
|
||||
namespace wvcas {
|
||||
|
||||
enum class KeySlotId { kEvenKeySlot, kOddKeySlot };
|
||||
|
||||
struct EcmKeyData;
|
||||
|
||||
// EcmParser allows random access to the fields of an ECM.
|
||||
// The only validation performed is to ensure that the ecm
|
||||
// passed in is large enough to hold a single key entry.
|
||||
class EcmParser {
|
||||
protected:
|
||||
EcmParser() {}
|
||||
|
||||
public:
|
||||
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 bool create(const CasEcm& ecm,
|
||||
std::unique_ptr<const EcmParser>* parser);
|
||||
static std::unique_ptr<const EcmParser> Create(const CasEcm& ecm);
|
||||
|
||||
// Accessor methods.
|
||||
virtual uint8_t version() const;
|
||||
virtual uint8_t sequence_count() const;
|
||||
virtual CryptoMode crypto_mode() const;
|
||||
virtual bool rotation_enabled() const;
|
||||
virtual size_t content_iv_size() const;
|
||||
virtual uint8_t age_restriction() const;
|
||||
virtual const std::vector<uint8_t> entitlement_key_id(KeySlotId id) const;
|
||||
virtual const std::vector<uint8_t> content_key_id(KeySlotId id) const;
|
||||
virtual const std::vector<uint8_t> wrapped_key_data(KeySlotId id) const;
|
||||
virtual const std::vector<uint8_t> wrapped_key_iv(KeySlotId id) const;
|
||||
virtual const std::vector<uint8_t> content_iv(KeySlotId id) const;
|
||||
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;
|
||||
|
||||
EcmParser(const EcmParser&) = delete;
|
||||
EcmParser& operator=(const EcmParser&) = delete;
|
||||
|
||||
private:
|
||||
// Constructs an EcmParser using |ecm|.
|
||||
explicit EcmParser(const CasEcm& ecm);
|
||||
|
||||
size_t key_data_size() const;
|
||||
// Returns false if the ecm used to construct the object is not a valid size.
|
||||
// TODO(jfore): Add validation using the version field.
|
||||
bool is_valid_size() const;
|
||||
const EcmKeyData* key_slot_data(KeySlotId id) const;
|
||||
|
||||
CasEcm ecm_;
|
||||
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;
|
||||
};
|
||||
|
||||
} // namespace wvcas
|
||||
|
||||
76
plugin/include/ecm_parser_v2.h
Normal file
76
plugin/include/ecm_parser_v2.h
Normal file
@@ -0,0 +1,76 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#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<const 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;
|
||||
|
||||
// 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 ""; }
|
||||
|
||||
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
|
||||
60
plugin/include/ecm_parser_v3.h
Normal file
60
plugin/include/ecm_parser_v3.h
Normal file
@@ -0,0 +1,60 @@
|
||||
// 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<const 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 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;
|
||||
|
||||
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_;
|
||||
};
|
||||
|
||||
} // namespace wvcas
|
||||
|
||||
#endif // ECM_PARSER_V3_H
|
||||
@@ -7,11 +7,13 @@
|
||||
|
||||
#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 {
|
||||
|
||||
@@ -45,7 +47,7 @@ class WidevineCasSession {
|
||||
virtual ~WidevineCasSession();
|
||||
|
||||
CasStatus initialize(std::shared_ptr<CryptoSession> crypto_session,
|
||||
uint32_t* session_id);
|
||||
CasEventListener* event_listener, uint32_t* session_id);
|
||||
|
||||
// Get the current key information. This method will be used by a descrambler
|
||||
// plugin to obtain the current key information.
|
||||
@@ -80,6 +82,13 @@ class WidevineCasSession {
|
||||
std::shared_ptr<CryptoSession> crypto_session_;
|
||||
// Id of the entitled key session in OEMCrypto associated with this session.
|
||||
uint32_t 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_;
|
||||
};
|
||||
|
||||
} // namespace wvcas
|
||||
|
||||
@@ -112,6 +112,11 @@ class WidevineCasPlugin : public CasPlugin, public CasEventListener {
|
||||
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;
|
||||
|
||||
// Choose to use |callback_| or |callback_ext_| to send back information.
|
||||
// |sessionId| is ignored if |callback_ext_| is null,
|
||||
|
||||
@@ -1018,4 +1018,20 @@ void CasLicense::OnAgeRestrictionUpdated(const WvCasSessionId& sessionId,
|
||||
event_listener_->OnAgeRestrictionUpdated(sessionId, ecm_age_restriction);
|
||||
}
|
||||
}
|
||||
|
||||
void CasLicense::OnSessionFingerprintingUpdated(const WvCasSessionId& sessionId,
|
||||
const CasData& fingerprinting) {
|
||||
if (event_listener_ != nullptr) {
|
||||
event_listener_->OnSessionFingerprintingUpdated(sessionId, fingerprinting);
|
||||
}
|
||||
}
|
||||
|
||||
void CasLicense::OnSessionServiceBlockingUpdated(
|
||||
const WvCasSessionId& sessionId, const CasData& service_blocking) {
|
||||
if (event_listener_ != nullptr) {
|
||||
event_listener_->OnSessionServiceBlockingUpdated(sessionId,
|
||||
service_blocking);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace wvcas
|
||||
|
||||
@@ -85,39 +85,6 @@ void FillEntitledContentKeyObjectFromKeyData(
|
||||
dest->cipher_mode = CipherModeFromKeyData(src.cipher_mode);
|
||||
}
|
||||
|
||||
// Increment the IV based on the number of encrypted block. IV size is assumed
|
||||
// to be 16 bytes.
|
||||
static constexpr uint32_t kIvSizeBytesBytes = 16;
|
||||
static constexpr uint32_t kCencIvSize = 8;
|
||||
static const uint32_t kAesBlockSizeBytes = 16;
|
||||
bool Ctr128Add(size_t block_count, uint8_t* counter) {
|
||||
if (counter == nullptr) return false;
|
||||
if (0 == block_count) return true;
|
||||
|
||||
uint8_t carry = 0;
|
||||
uint8_t n = kIvSizeBytesBytes - 1;
|
||||
// Update the counter one byte at a time.
|
||||
while (n >= kCencIvSize) {
|
||||
// Grab a single byte of the block_count.
|
||||
uint32_t temp = block_count & 0xff;
|
||||
// Add the corresponding byte from the counter value.
|
||||
temp += counter[n];
|
||||
// Add in the carry.
|
||||
temp += carry;
|
||||
// Write back the updated counter byte and set the carry value as needed.
|
||||
counter[n] = temp & 0xff;
|
||||
carry = (temp & 0x100) ? 1 : 0;
|
||||
// Update block_count and set the counter index for the next byte.
|
||||
block_count = block_count >> 8;
|
||||
n--;
|
||||
// Early exit if nothing to do.
|
||||
if (!block_count && !carry) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
shared_mutex CryptoLock::static_field_mutex_;
|
||||
|
||||
@@ -4,29 +4,17 @@
|
||||
|
||||
#include "ecm_parser.h"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "ecm_parser_v2.h"
|
||||
#include "ecm_parser_v3.h"
|
||||
#include "log.h"
|
||||
|
||||
namespace wvcas {
|
||||
|
||||
namespace {
|
||||
// ECM constants
|
||||
constexpr uint8_t kSequenceCountMask = 0xF8; // Mode bits 3..7
|
||||
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;
|
||||
// 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;
|
||||
@@ -43,8 +31,6 @@ constexpr size_t kSectionHeaderSize = 3;
|
||||
constexpr size_t kSectionHeaderWithPointerSize = 4;
|
||||
constexpr uint8_t kPointerFieldZero = 0x00;
|
||||
|
||||
constexpr size_t kMaxTsPayloadSizeBytes = 184;
|
||||
|
||||
// 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) {
|
||||
@@ -65,183 +51,34 @@ int find_ecm_start_index(const CasEcm& cas_ecm) {
|
||||
|
||||
} // namespace
|
||||
|
||||
#pragma pack(push, 1) // No padding in ecm struct definition.
|
||||
struct EcmKeyData {
|
||||
const uint8_t entitlement_key_id[kEntitlementKeyIDSizeBytes];
|
||||
const uint8_t content_key_id[kContentKeyIDSizeBytes];
|
||||
const uint8_t control_word[kContentKeyDataSizeBytes];
|
||||
const uint8_t control_word_iv[kWrappedKeyIVSizeBytes];
|
||||
// Actual size can be either 8 or 16 bytes.
|
||||
const uint8_t content_iv[kContentKeyMaxIVSizeBytes];
|
||||
};
|
||||
|
||||
struct EcmDescriptor {
|
||||
const uint16_t ca_id;
|
||||
const uint8_t version;
|
||||
const uint8_t flags_cipher_rotation;
|
||||
const uint8_t flags_iv_age;
|
||||
};
|
||||
|
||||
static_assert((sizeof(EcmDescriptor) + 2 * sizeof(EcmKeyData)) <=
|
||||
kMaxTsPayloadSizeBytes,
|
||||
"Maximum possible ecm size is larger than a ts payload");
|
||||
#pragma pack(pop) // Revert padding value to previous.
|
||||
|
||||
EcmParser::EcmParser(const CasEcm& ecm) : ecm_(ecm) {}
|
||||
|
||||
size_t EcmParser::key_data_size() const {
|
||||
return sizeof(EcmKeyData) + content_iv_size() - kContentKeyMaxIVSizeBytes;
|
||||
}
|
||||
|
||||
bool EcmParser::is_valid_size() const {
|
||||
size_t expected_size =
|
||||
sizeof(EcmDescriptor) + key_data_size() * (rotation_enabled() ? 2 : 1);
|
||||
// Parser always receives entire ts payload of 184 bytes.
|
||||
return ecm_.size() >= expected_size;
|
||||
}
|
||||
|
||||
const EcmKeyData* EcmParser::key_slot_data(KeySlotId id) const {
|
||||
// ECM descriptor is followed by either one or two ECM key data.
|
||||
size_t key_data_offset = sizeof(EcmDescriptor);
|
||||
if (rotation_enabled()) {
|
||||
if (id == KeySlotId::kOddKeySlot) {
|
||||
key_data_offset += key_data_size();
|
||||
} else if (id != KeySlotId::kEvenKeySlot) {
|
||||
return nullptr;
|
||||
}
|
||||
} else {
|
||||
// No rotation enabled.
|
||||
if (id != KeySlotId::kEvenKeySlot) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
return reinterpret_cast<const EcmKeyData*>(&ecm_[key_data_offset]);
|
||||
}
|
||||
|
||||
bool EcmParser::create(const CasEcm& cas_ecm,
|
||||
std::unique_ptr<const EcmParser>* parser) {
|
||||
if (parser == nullptr) {
|
||||
return false;
|
||||
}
|
||||
std::unique_ptr<const EcmParser> EcmParser::Create(const CasEcm& cas_ecm) {
|
||||
// Detect and strip optional section header.
|
||||
int offset = find_ecm_start_index(cas_ecm);
|
||||
if (offset < 0 || (static_cast<int>(cas_ecm.size()) - offset <
|
||||
static_cast<int>(sizeof(EcmDescriptor)))) {
|
||||
return false;
|
||||
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());
|
||||
// Reconfirm ecm data should start with valid Widevine CAS ID.
|
||||
uint16_t cas_id_val = ntohs(*reinterpret_cast<const uint16_t*>(ecm.data()));
|
||||
// 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 false;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Using 'new' to access a non-public constructor.
|
||||
std::unique_ptr<const EcmParser> new_parser =
|
||||
std::unique_ptr<const EcmParser>(new EcmParser(ecm));
|
||||
if (!new_parser->is_valid_size()) {
|
||||
return false;
|
||||
if (ecm[kVersionIndex] <= 2) {
|
||||
std::unique_ptr<const EcmParserV2> parser;
|
||||
if (!EcmParserV2::create(ecm, &parser)) {
|
||||
return nullptr;
|
||||
}
|
||||
*parser = std::move(new_parser);
|
||||
return true;
|
||||
return parser;
|
||||
} else {
|
||||
return EcmParserV3::Create(ecm);
|
||||
}
|
||||
|
||||
uint8_t EcmParser::version() const {
|
||||
const EcmDescriptor* ecm = reinterpret_cast<const EcmDescriptor*>(&ecm_[0]);
|
||||
return ecm->version;
|
||||
}
|
||||
|
||||
uint8_t EcmParser::sequence_count() const {
|
||||
if (version() == 1) {
|
||||
const EcmDescriptor* ecm = reinterpret_cast<const EcmDescriptor*>(&ecm_[0]);
|
||||
return (ecm->flags_cipher_rotation & kSequenceCountMask) >> 3;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
CryptoMode EcmParser::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 EcmParser::rotation_enabled() const {
|
||||
const EcmDescriptor* ecm = reinterpret_cast<const EcmDescriptor*>(&ecm_[0]);
|
||||
return (ecm->flags_cipher_rotation & kRotationFlag);
|
||||
}
|
||||
|
||||
size_t EcmParser::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 EcmParser::age_restriction() const {
|
||||
if (version() == 1) {
|
||||
return 0;
|
||||
}
|
||||
const EcmDescriptor* ecm = reinterpret_cast<const EcmDescriptor*>(&ecm_[0]);
|
||||
return (ecm->flags_iv_age & kAgeRestrictionMask) >> 1;
|
||||
}
|
||||
|
||||
const std::vector<uint8_t> EcmParser::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;
|
||||
}
|
||||
|
||||
const std::vector<uint8_t> EcmParser::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;
|
||||
}
|
||||
|
||||
const std::vector<uint8_t> EcmParser::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;
|
||||
}
|
||||
|
||||
const std::vector<uint8_t> EcmParser::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;
|
||||
}
|
||||
|
||||
const std::vector<uint8_t> EcmParser::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
|
||||
|
||||
182
plugin/src/ecm_parser_v2.cpp
Normal file
182
plugin/src/ecm_parser_v2.cpp
Normal file
@@ -0,0 +1,182 @@
|
||||
// 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<const EcmParserV2>* parser) {
|
||||
if (parser == nullptr) {
|
||||
return false;
|
||||
}
|
||||
// Using 'new' to access a non-public constructor.
|
||||
auto new_parser =
|
||||
std::unique_ptr<const 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
|
||||
174
plugin/src/ecm_parser_v3.cpp
Normal file
174
plugin/src/ecm_parser_v3.cpp
Normal file
@@ -0,0 +1,174 @@
|
||||
// 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::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)) {}
|
||||
|
||||
std::unique_ptr<const EcmParserV3> EcmParserV3::Create(const CasEcm& cas_ecm) {
|
||||
if (cas_ecm.size() <= kEcmHeaderSize) {
|
||||
LOGE("ECM is too short. Size: %u", 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<const 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 &&
|
||||
!ecm_payload_.odd_key_data().entitlement_key_id().empty()
|
||||
? ecm_payload_.odd_key_data()
|
||||
: ecm_payload_.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
|
||||
? ecm_payload_.odd_key_data()
|
||||
: ecm_payload_.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 &&
|
||||
!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 &&
|
||||
!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();
|
||||
}
|
||||
|
||||
} // namespace wvcas
|
||||
@@ -190,7 +190,7 @@ CasStatus WidevineCas::openSession(WvCasSessionId* sessionId) {
|
||||
|
||||
CasSessionPtr session = newCasSession();
|
||||
CasStatus status = session->initialize(
|
||||
crypto_session_, reinterpret_cast<uint32_t*>(sessionId));
|
||||
crypto_session_, event_listener_, reinterpret_cast<uint32_t*>(sessionId));
|
||||
if (CasStatusCode::kNoError != status.status_code()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -3,10 +3,13 @@
|
||||
// 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 {
|
||||
|
||||
@@ -25,7 +28,8 @@ WidevineCasSession::~WidevineCasSession() {
|
||||
}
|
||||
|
||||
CasStatus WidevineCasSession::initialize(
|
||||
std::shared_ptr<CryptoSession> crypto_session, uint32_t* session_id) {
|
||||
std::shared_ptr<CryptoSession> crypto_session,
|
||||
CasEventListener* event_listener, uint32_t* session_id) {
|
||||
if (crypto_session == nullptr || session_id == nullptr) {
|
||||
LOGE("WidevineCasSession::initialize: missing input parameters");
|
||||
return CasStatus(CasStatusCode::kInvalidParameter,
|
||||
@@ -34,6 +38,7 @@ CasStatus WidevineCasSession::initialize(
|
||||
crypto_session_ = std::move(crypto_session);
|
||||
crypto_session_->CreateEntitledKeySession(&key_session_id_);
|
||||
*session_id = key_session_id_;
|
||||
event_listener_ = event_listener;
|
||||
return CasStatusCode::kNoError;
|
||||
}
|
||||
|
||||
@@ -49,7 +54,7 @@ CasStatus WidevineCasSession::processEcm(const CasEcm& ecm,
|
||||
if (ecm != current_ecm_) {
|
||||
LOGD("WidevineCasSession::processEcm: received new ecm");
|
||||
std::unique_ptr<const EcmParser> ecm_parser = getEcmParser(ecm);
|
||||
if (!ecm_parser) {
|
||||
if (ecm_parser == nullptr) {
|
||||
return CasStatus(CasStatusCode::kInvalidParameter, "invalid ecm");
|
||||
}
|
||||
|
||||
@@ -61,6 +66,46 @@ CasStatus WidevineCasSession::processEcm(const CasEcm& ecm,
|
||||
return CasStatus(CasStatusCode::kAccessDeniedByParentalControl, message);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> message;
|
||||
if (!ecm_parser->fingerprinting().control().empty()) {
|
||||
message.push_back(static_cast<uint8_t>(
|
||||
SessionFingerprintingFieldType::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(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::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(key_session_id_,
|
||||
message);
|
||||
}
|
||||
}
|
||||
|
||||
bool load_even = false;
|
||||
bool load_odd = false;
|
||||
|
||||
@@ -118,11 +163,7 @@ CasStatus WidevineCasSession::processEcm(const CasEcm& ecm,
|
||||
|
||||
std::unique_ptr<const EcmParser> WidevineCasSession::getEcmParser(
|
||||
const CasEcm& ecm) const {
|
||||
std::unique_ptr<const EcmParser> new_ecm_parser;
|
||||
if (!EcmParser::create(ecm, &new_ecm_parser)) {
|
||||
return std::unique_ptr<const EcmParser>();
|
||||
}
|
||||
return new_ecm_parser;
|
||||
return EcmParser::Create(ecm);
|
||||
}
|
||||
|
||||
const char* WidevineCasSession::securityLevel() {
|
||||
|
||||
@@ -461,6 +461,33 @@ void WidevineCasPlugin::OnAgeRestrictionUpdated(const WvCasSessionId& sessionId,
|
||||
sessionId, &ecm_age_restriction, 1, &android_session_id);
|
||||
}
|
||||
|
||||
void WidevineCasPlugin::OnSessionFingerprintingUpdated(
|
||||
const WvCasSessionId& sessionId, const CasData& fingerprinting) {
|
||||
LOGI("OnSessionFingerprintingUpdated");
|
||||
const CasSessionId& android_session_id =
|
||||
widevineSessionIdToAndroid(sessionId);
|
||||
CallBack(reinterpret_cast<void*>(app_data_),
|
||||
SESSION_FINGERPRINTING_INFO, /*arg=*/
|
||||
0,
|
||||
fingerprinting.empty() ? nullptr
|
||||
: const_cast<uint8_t*>(&fingerprinting[0]),
|
||||
fingerprinting.size(), &android_session_id);
|
||||
}
|
||||
|
||||
void WidevineCasPlugin::OnSessionServiceBlockingUpdated(
|
||||
const WvCasSessionId& sessionId, const CasData& service_blocking) {
|
||||
LOGI("OnSessionServiceBlockingUpdated");
|
||||
const CasSessionId& android_session_id =
|
||||
widevineSessionIdToAndroid(sessionId);
|
||||
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(), &android_session_id);
|
||||
}
|
||||
|
||||
void WidevineCasPlugin::CallBack(void* appData, int32_t event, int32_t arg,
|
||||
uint8_t* data, size_t size,
|
||||
const CasSessionId* sessionId) const {
|
||||
|
||||
@@ -16,3 +16,83 @@ message CaDescriptorPrivateData {
|
||||
// Content ID.
|
||||
optional bytes content_id = 2;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
message EcmMetaData {
|
||||
enum CipherMode {
|
||||
UNSPECIFIED = 0;
|
||||
AES_CBC = 1;
|
||||
AES_CTR = 2;
|
||||
DVB_CSA2 = 3;
|
||||
DVB_CSA3 = 4;
|
||||
AES_OFB = 5;
|
||||
AES_SCTE52 = 6;
|
||||
}
|
||||
// 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];
|
||||
}
|
||||
|
||||
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 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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
@@ -6,6 +6,8 @@ cc_binary {
|
||||
"src/cas_license_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/test_properties.cpp",
|
||||
"src/widevine_cas_session_test.cpp",
|
||||
"src/cas_session_map_test.cpp",
|
||||
|
||||
@@ -4,49 +4,23 @@
|
||||
|
||||
#include "ecm_parser.h"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <bitset>
|
||||
#include <tuple>
|
||||
|
||||
#include "media_cas.pb.h"
|
||||
|
||||
namespace wvcas {
|
||||
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 kEcmHeaderSizeBytes = kCasIdSizeBytes + kVersionSizeBytes;
|
||||
constexpr int kEcmVersion2 = 2;
|
||||
constexpr int kEcmVersion3 = 3;
|
||||
|
||||
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 size_t kValidEcmV2SizeBytes = 165;
|
||||
|
||||
constexpr uint16_t kSectionHeader1 = 0x80;
|
||||
constexpr uint16_t kSectionHeader2 = 0x81;
|
||||
@@ -56,242 +30,56 @@ constexpr uint16_t kWidevineCasId = 0x4AD4;
|
||||
// New Widevine CAS IDs 0x56C0 to 0x56C9 (all inclusive).
|
||||
constexpr uint16_t kWidevineNewCasIdLowerBound = 0x56C0;
|
||||
constexpr uint16_t kWidevineNewCasIdUpperBound = 0x56C9;
|
||||
} // namespace
|
||||
|
||||
class EcmParserTest : public testing::Test {
|
||||
protected:
|
||||
void SetUp() {
|
||||
BuildEcm(kWidevineCasId, /*with_rotation=*/true, /*content_iv_flag=*/false);
|
||||
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());
|
||||
}
|
||||
size_t ContentKeyIVSize(bool content_iv_flag);
|
||||
size_t CalculateEcmSize(bool with_rotation, bool content_iv_flag = false);
|
||||
void BuildEcm(uint16_t cas_id, bool with_rotation, bool content_iv_flag);
|
||||
|
||||
std::vector<uint8_t> ecm_data_;
|
||||
std::unique_ptr<const wvcas::EcmParser> parser_;
|
||||
};
|
||||
|
||||
size_t EcmParserTest::ContentKeyIVSize(bool content_iv_flag) {
|
||||
// Content key iv is 8 bytes if Content_IV flag is zero, and 16 bytes
|
||||
// othersize.
|
||||
return content_iv_flag ? 16 : 8;
|
||||
return ecm_data;
|
||||
}
|
||||
|
||||
size_t EcmParserTest::CalculateEcmSize(bool with_rotation,
|
||||
bool content_iv_flag) {
|
||||
size_t ecm_key_data_size =
|
||||
kContentKeyIDSizeBytes + kContentKeyDataSize + kWrappedKeyIVSizeBytes +
|
||||
kEntitlementKeyIDSizeBytes + ContentKeyIVSize(content_iv_flag);
|
||||
return kEcmDescriptorSizeBytes + ecm_key_data_size * (with_rotation ? 2 : 1);
|
||||
// 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);
|
||||
}
|
||||
|
||||
void EcmParserTest::BuildEcm(uint16_t cas_id, 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] = cas_id >> 8;
|
||||
ecm_data_[1] = cas_id & 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(EcmParserTest, FieldsWithoutKeyRotation) {
|
||||
bool content_key_iv_16b = false;
|
||||
ecm_data_.resize(CalculateEcmSize(false, content_key_iv_16b));
|
||||
ASSERT_TRUE(wvcas::EcmParser::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(EcmParserTest, FieldsWithKeyRotation) {
|
||||
ecm_data_[3] |= kRotationFlag;
|
||||
ASSERT_TRUE(wvcas::EcmParser::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(EcmParserTest, create) {
|
||||
EXPECT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser_));
|
||||
|
||||
ecm_data_.resize(4);
|
||||
EXPECT_FALSE(wvcas::EcmParser::create(ecm_data_, &parser_));
|
||||
|
||||
ecm_data_.resize(4 + CalculateEcmSize(false));
|
||||
EXPECT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser_));
|
||||
|
||||
ecm_data_.resize(kMaxEcmSizeBytes);
|
||||
EXPECT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser_));
|
||||
|
||||
ecm_data_.resize(CalculateEcmSize(true));
|
||||
EXPECT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser_));
|
||||
EXPECT_FALSE(wvcas::EcmParser::create(ecm_data_, nullptr));
|
||||
|
||||
ecm_data_.resize(CalculateEcmSize(true));
|
||||
EXPECT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser_));
|
||||
EXPECT_FALSE(wvcas::EcmParser::create(ecm_data_, nullptr));
|
||||
}
|
||||
|
||||
TEST_F(EcmParserTest, crypto_mode) {
|
||||
ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser_));
|
||||
EXPECT_EQ(parser_->crypto_mode(), wvcas::CryptoMode::kAesCBC);
|
||||
|
||||
ecm_data_[3] = kAESCTRCryptoModeFlagsVal;
|
||||
ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser_));
|
||||
EXPECT_EQ(parser_->crypto_mode(), wvcas::CryptoMode::kAesCTR);
|
||||
|
||||
ecm_data_[3] = kDvbCsa2CryptoModeFlagsVal;
|
||||
ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser_));
|
||||
EXPECT_EQ(parser_->crypto_mode(), wvcas::CryptoMode::kDvbCsa2);
|
||||
|
||||
ecm_data_[3] = kDvbCsa3CryptoModeFlagsVal;
|
||||
ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser_));
|
||||
EXPECT_EQ(parser_->crypto_mode(), wvcas::CryptoMode::kDvbCsa3);
|
||||
|
||||
ecm_data_[3] = kDvbOFBCryptoModeFlagsVal;
|
||||
ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser_));
|
||||
EXPECT_EQ(parser_->crypto_mode(), wvcas::CryptoMode::kAesOFB);
|
||||
|
||||
ecm_data_[3] = kDvbSCTECryptoModeFlagsVal;
|
||||
ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser_));
|
||||
EXPECT_EQ(parser_->crypto_mode(), wvcas::CryptoMode::kAesSCTE);
|
||||
}
|
||||
|
||||
TEST_F(EcmParserTest, ContentKeyIVSizes) {
|
||||
bool with_rotation = true;
|
||||
bool iv_flag = false;
|
||||
ecm_data_.resize(CalculateEcmSize(with_rotation, iv_flag));
|
||||
ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser_));
|
||||
EXPECT_EQ(parser_->content_iv_size(), ContentKeyIVSize(iv_flag));
|
||||
|
||||
iv_flag = true;
|
||||
ecm_data_[4] = kContentIVSizeFlag;
|
||||
ecm_data_.resize(CalculateEcmSize(with_rotation, iv_flag));
|
||||
ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser_));
|
||||
EXPECT_EQ(parser_->content_iv_size(), ContentKeyIVSize(iv_flag));
|
||||
}
|
||||
|
||||
TEST_F(EcmParserTest, AgeRestriction) {
|
||||
ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser_));
|
||||
EXPECT_EQ(0, parser_->age_restriction());
|
||||
|
||||
uint8_t age_restriction = 16;
|
||||
ecm_data_[4] |= age_restriction << 1;
|
||||
ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser_));
|
||||
EXPECT_EQ(age_restriction, parser_->age_restriction());
|
||||
}
|
||||
INSTANTIATE_TEST_SUITE_P(EcmParserVersionTest, EcmParserVersionTest,
|
||||
::testing::Values(kEcmVersion2, kEcmVersion3));
|
||||
|
||||
// Verifies CAS ID returned by the parser must be expected ones.
|
||||
class EcmParserCasIdTest
|
||||
: public EcmParserTest,
|
||||
public ::testing::WithParamInterface<::testing::tuple<uint16_t, bool>> {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
const uint16_t cas_id = ::testing::get<0>(GetParam());
|
||||
BuildEcm(cas_id, /*with_rotation=*/true, /*content_iv_flag=*/false);
|
||||
}
|
||||
};
|
||||
: public testing::Test,
|
||||
public ::testing::WithParamInterface<::testing::tuple<uint16_t, bool>> {};
|
||||
|
||||
TEST_P(EcmParserCasIdTest, ValidateCasIds) {
|
||||
bool expected_result = ::testing::get<1>(GetParam());
|
||||
ASSERT_EQ(wvcas::EcmParser::create(ecm_data_, &parser_), expected_result);
|
||||
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,
|
||||
@@ -315,23 +103,28 @@ INSTANTIATE_TEST_SUITE_P(
|
||||
|
||||
// Verifies Section header and pointer field may be prepended to ECM.
|
||||
class EcmParserSectionHeaderTest
|
||||
: public EcmParserTest,
|
||||
: 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(),
|
||||
ecm_data.insert(ecm_data.begin(), section_header.begin(),
|
||||
section_header.end());
|
||||
ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser_));
|
||||
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(),
|
||||
ecm_data.insert(ecm_data.begin(), section_header.begin(),
|
||||
section_header.end());
|
||||
ASSERT_TRUE(wvcas::EcmParser::create(ecm_data_, &parser_));
|
||||
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<const 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);
|
||||
}
|
||||
291
tests/src/ecm_parser_v3_test.cpp
Normal file
291
tests/src/ecm_parser_v3_test.cpp
Normal file
@@ -0,0 +1,291 @@
|
||||
// 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::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<const 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<const 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<const 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<const 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<const 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<const 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)));
|
||||
|
||||
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<const 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<const 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<const EcmParserV3> parser = EcmParserV3::Create(ecm);
|
||||
|
||||
ASSERT_TRUE(parser != nullptr);
|
||||
EXPECT_EQ(parser->signature(), expected_signature);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace wvcas
|
||||
@@ -115,3 +115,6 @@ TEST(IntegrationTests, TestSessionEventPassing) {
|
||||
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestSessionEventPassing"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestProcessEcmV3) {
|
||||
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestProcessEcmV3"));
|
||||
}
|
||||
|
||||
@@ -91,6 +91,14 @@ class MockEventListener : public wvcas::CasEventListener {
|
||||
MOCK_METHOD2(OnAgeRestrictionUpdated,
|
||||
void(const wvcas::WvCasSessionId& sessionId,
|
||||
uint8_t ecm_age_restriction));
|
||||
MOCK_METHOD(void, OnSessionFingerprintingUpdated,
|
||||
(const int32_t& sessionId,
|
||||
const std::vector<uint8_t>& fingerprinting),
|
||||
(override));
|
||||
MOCK_METHOD(void, OnSessionServiceBlockingUpdated,
|
||||
(const int32_t& sessionId,
|
||||
const std::vector<uint8_t>& service_blocking),
|
||||
(override));
|
||||
};
|
||||
|
||||
class TestablePolicyEngine : public wvcas::PolicyEngine {
|
||||
|
||||
@@ -85,6 +85,14 @@ class MockEventListener : public wvcas::CasEventListener {
|
||||
MOCK_METHOD2(OnAgeRestrictionUpdated,
|
||||
void(const wvcas::WvCasSessionId& sessionId,
|
||||
uint8_t ecm_age_restriction));
|
||||
MOCK_METHOD(void, OnSessionFingerprintingUpdated,
|
||||
(const int32_t& sessionId,
|
||||
const std::vector<uint8_t>& fingerprinting),
|
||||
(override));
|
||||
MOCK_METHOD(void, OnSessionServiceBlockingUpdated,
|
||||
(const int32_t& sessionId,
|
||||
const std::vector<uint8_t>& service_blocking),
|
||||
(override));
|
||||
};
|
||||
typedef StrictMock<MockEventListener> StrictMockEventListener;
|
||||
|
||||
@@ -125,19 +133,9 @@ class MockFileSystem : public wvutil::FileSystem {
|
||||
typedef NiceMock<MockFileSystem> NiceMockFileSystem;
|
||||
|
||||
class MockWidevineSession : public wvcas::WidevineCasSession {
|
||||
class EcmParser : public wvcas::EcmParser {
|
||||
public:
|
||||
EcmParser() {}
|
||||
~EcmParser() override {}
|
||||
};
|
||||
|
||||
public:
|
||||
MockWidevineSession() {}
|
||||
~MockWidevineSession() override {}
|
||||
std::unique_ptr<const wvcas::EcmParser> getEcmParser(
|
||||
const wvcas::CasEcm& ecm) const override {
|
||||
return make_unique<EcmParser>();
|
||||
}
|
||||
MOCK_METHOD2(processEcm, wvcas::CasStatus(const wvcas::CasEcm& ecm,
|
||||
uint8_t parental_control_age));
|
||||
MOCK_METHOD2(HandleProcessEcm,
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "widevine_cas_session.h"
|
||||
|
||||
#include <cas_events.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
@@ -11,10 +12,13 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "cas_types.h"
|
||||
#include "cas_util.h"
|
||||
#include "media_cas.pb.h"
|
||||
#include "mock_crypto_session.h"
|
||||
#include "string_conversions.h"
|
||||
|
||||
namespace {
|
||||
using ::testing::_;
|
||||
using ::testing::DoAll;
|
||||
using ::testing::Eq;
|
||||
@@ -109,20 +113,24 @@ MATCHER(IsValidKeyOddSlotData, "") {
|
||||
|
||||
class MockEcmParser : public wvcas::EcmParser {
|
||||
public:
|
||||
MOCK_CONST_METHOD0(sequence_count, uint8_t());
|
||||
MOCK_CONST_METHOD0(version, uint8_t());
|
||||
MOCK_CONST_METHOD0(age_restriction, uint8_t());
|
||||
MOCK_CONST_METHOD0(crypto_mode, wvcas::CryptoMode());
|
||||
MOCK_CONST_METHOD0(rotation_enabled, bool());
|
||||
MOCK_CONST_METHOD0(content_iv_size, size_t());
|
||||
MOCK_CONST_METHOD1(entitlement_key_id,
|
||||
const std::vector<uint8_t>(wvcas::KeySlotId id));
|
||||
MOCK_CONST_METHOD1(content_key_id,
|
||||
const std::vector<uint8_t>(wvcas::KeySlotId id));
|
||||
std::vector<uint8_t>(wvcas::KeySlotId id));
|
||||
MOCK_CONST_METHOD1(content_key_id, std::vector<uint8_t>(wvcas::KeySlotId id));
|
||||
MOCK_CONST_METHOD1(wrapped_key_data,
|
||||
const std::vector<uint8_t>(wvcas::KeySlotId id));
|
||||
MOCK_CONST_METHOD1(wrapped_key_iv,
|
||||
const std::vector<uint8_t>(wvcas::KeySlotId id));
|
||||
MOCK_CONST_METHOD1(content_iv,
|
||||
const std::vector<uint8_t>(wvcas::KeySlotId id));
|
||||
std::vector<uint8_t>(wvcas::KeySlotId id));
|
||||
MOCK_CONST_METHOD1(wrapped_key_iv, std::vector<uint8_t>(wvcas::KeySlotId id));
|
||||
MOCK_CONST_METHOD1(content_iv, std::vector<uint8_t>(wvcas::KeySlotId id));
|
||||
MOCK_CONST_METHOD0(has_fingerprinting, bool());
|
||||
MOCK_CONST_METHOD0(fingerprinting, video_widevine::Fingerprinting());
|
||||
MOCK_CONST_METHOD0(has_service_blocking, bool());
|
||||
MOCK_CONST_METHOD0(service_blocking, video_widevine::ServiceBlocking());
|
||||
MOCK_CONST_METHOD0(ecm_serialized_payload, std::string());
|
||||
MOCK_CONST_METHOD0(signature, std::string());
|
||||
};
|
||||
|
||||
class CasSessionTest : public ::testing::Test {
|
||||
@@ -138,7 +146,7 @@ class TestCasSession : public wvcas::WidevineCasSession {
|
||||
virtual ~TestCasSession() {}
|
||||
|
||||
std::unique_ptr<const wvcas::EcmParser> getEcmParser(
|
||||
const wvcas::CasEcm& ecm) const;
|
||||
const wvcas::CasEcm& ecm) const override;
|
||||
|
||||
std::vector<uint8_t> entitlement_key_id(wvcas::KeySlotId id) const {
|
||||
std::string key_id;
|
||||
@@ -194,15 +202,30 @@ class TestCasSession : public wvcas::WidevineCasSession {
|
||||
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<const 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, sequence_count()).WillByDefault(Return(0));
|
||||
ON_CALL(*mock_ecm_parser, age_restriction())
|
||||
.WillByDefault(Return(age_restriction_));
|
||||
ON_CALL(*mock_ecm_parser, crypto_mode())
|
||||
@@ -218,6 +241,14 @@ std::unique_ptr<const wvcas::EcmParser> TestCasSession::getEcmParser(
|
||||
.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, 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<const wvcas::EcmParser>(mock_ecm_parser.release());
|
||||
}
|
||||
|
||||
@@ -231,7 +262,8 @@ TEST_F(CasSessionTest, processEcm) {
|
||||
.WillOnce(DoAll(SetArgPointee<0>(kEntitledKeySessionId),
|
||||
Return(wvcas::CasStatusCode::kNoError)));
|
||||
ASSERT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
session.initialize(mock, &session_id).status_code());
|
||||
session.initialize(mock, /*event_listener=*/nullptr, &session_id)
|
||||
.status_code());
|
||||
EXPECT_EQ(session_id, kEntitledKeySessionId);
|
||||
|
||||
wvcas::CasEcm ecm(184);
|
||||
@@ -250,7 +282,8 @@ TEST_F(CasSessionTest, parentalControl) {
|
||||
.WillOnce(DoAll(SetArgPointee<0>(kEntitledKeySessionId),
|
||||
Return(wvcas::CasStatusCode::kNoError)));
|
||||
ASSERT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
session.initialize(mock, &session_id).status_code());
|
||||
session.initialize(mock, /*event_listener=*/nullptr, &session_id)
|
||||
.status_code());
|
||||
EXPECT_EQ(session_id, kEntitledKeySessionId);
|
||||
|
||||
EXPECT_CALL(*mock, LoadCasECMKeys(session_id, IsValidKeyEvenSlotData(),
|
||||
@@ -281,3 +314,141 @@ TEST_F(CasSessionTest, parentalControl) {
|
||||
session.processEcm(ecm, 3).status_code());
|
||||
EXPECT_CALL(*mock, RemoveEntitledKeySession(session_id));
|
||||
}
|
||||
|
||||
class MockEventListener : public wvcas::CasEventListener {
|
||||
public:
|
||||
MockEventListener() {}
|
||||
~MockEventListener() override {}
|
||||
MOCK_METHOD0(OnSessionRenewalNeeded, void());
|
||||
MOCK_METHOD2(OnSessionKeysChange, void(const wvcas::KeyStatusMap& keys_status,
|
||||
bool has_new_usable_key));
|
||||
MOCK_METHOD1(OnExpirationUpdate, void(int64_t new_expiry_time_seconds));
|
||||
MOCK_METHOD1(OnNewRenewalServerUrl,
|
||||
void(const std::string& renewal_server_url));
|
||||
MOCK_METHOD0(OnLicenseExpiration, void());
|
||||
MOCK_METHOD2(OnAgeRestrictionUpdated,
|
||||
void(const wvcas::WvCasSessionId& sessionId,
|
||||
uint8_t ecm_age_restriction));
|
||||
MOCK_METHOD(void, OnSessionFingerprintingUpdated,
|
||||
(const int32_t& sessionId,
|
||||
const std::vector<uint8_t>& fingerprinting),
|
||||
(override));
|
||||
MOCK_METHOD(void, OnSessionServiceBlockingUpdated,
|
||||
(const int32_t& sessionId,
|
||||
const std::vector<uint8_t>& service_blocking),
|
||||
(override));
|
||||
};
|
||||
|
||||
TEST_F(CasSessionTest, FingerprintingSuccess) {
|
||||
TestCasSession session;
|
||||
auto mock_crypto = std::make_shared<MockCryptoSession>();
|
||||
MockEventListener mock_listener;
|
||||
uint32_t 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);
|
||||
}
|
||||
|
||||
TEST_F(CasSessionTest, RepeatedFingerprintingNoEventSuccess) {
|
||||
TestCasSession session;
|
||||
auto mock_crypto = std::make_shared<MockCryptoSession>();
|
||||
MockEventListener mock_listener;
|
||||
uint32_t 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);
|
||||
// Same fingerprinting will not trigger event.
|
||||
EXPECT_CALL(mock_listener, OnSessionFingerprintingUpdated).Times(0);
|
||||
session.processEcm(wvcas::CasEcm(184, '1'), 0);
|
||||
}
|
||||
|
||||
TEST_F(CasSessionTest, DifferentFingerprintingTriggerEventSuccess) {
|
||||
TestCasSession session;
|
||||
auto mock_crypto = std::make_shared<MockCryptoSession>();
|
||||
MockEventListener mock_listener;
|
||||
uint32_t 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);
|
||||
// Different fingerprinting will trigger event.
|
||||
session.set_fingerprinting_control("control2");
|
||||
EXPECT_CALL(mock_listener, OnSessionFingerprintingUpdated).Times(1);
|
||||
session.processEcm(wvcas::CasEcm(184, '1'), 0);
|
||||
// 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);
|
||||
}
|
||||
|
||||
TEST_F(CasSessionTest, ServiceBlockingSuccess) {
|
||||
TestCasSession session;
|
||||
auto mock_crypto = std::make_shared<MockCryptoSession>();
|
||||
MockEventListener mock_listener;
|
||||
uint32_t 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);
|
||||
}
|
||||
|
||||
TEST_F(CasSessionTest, RepeatedServiceBlockingNoEventSuccess) {
|
||||
TestCasSession session;
|
||||
auto mock_crypto = std::make_shared<MockCryptoSession>();
|
||||
MockEventListener mock_listener;
|
||||
uint32_t 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);
|
||||
EXPECT_CALL(mock_listener, OnSessionServiceBlockingUpdated).Times(0);
|
||||
session.processEcm(wvcas::CasEcm(184, '1'), 0);
|
||||
}
|
||||
|
||||
TEST_F(CasSessionTest, DifferentServiceBlockingTriggerEventSuccess) {
|
||||
TestCasSession session;
|
||||
auto mock_crypto = std::make_shared<MockCryptoSession>();
|
||||
MockEventListener mock_listener;
|
||||
uint32_t 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);
|
||||
EXPECT_CALL(mock_listener, OnSessionServiceBlockingUpdated).Times(1);
|
||||
session.set_service_blocking_groups({"Group1"});
|
||||
session.processEcm(wvcas::CasEcm(184, '1'), 0);
|
||||
EXPECT_CALL(mock_listener, OnSessionServiceBlockingUpdated).Times(1);
|
||||
session.set_service_blocking_groups({});
|
||||
session.processEcm(wvcas::CasEcm(184, '2'), 0);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Reference in New Issue
Block a user