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:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user