Modifiable OEMCrypto
Merge from Widevine repo of http://go/wvgerrit/24729 This CL adds a new variant of the OEMCrypto mock code that adjusts its behaviour based on a configuration file. This is intended for testing. For example, a tester can set current_hdcp to 2 in the options.txt file, push it to the device, and verify that a license is granted for HDCP 2.0. Then the tester can edit the value of current_hdcp to 1 and push the file to the device. Playback should stop because the license is no longer valid. This variant uses a real level 1 liboemcrypto.so to push data to a secure buffer. That means we can test playback for a license that requires secure buffers on an Android device with real secure buffers. b/35141278 b/37353534 Change-Id: Id12f2800c6395d9da7cffebd6797aac17414b38d
This commit is contained in:
@@ -0,0 +1,414 @@
|
||||
// Copyright 2017 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Mock implementation of OEMCrypto APIs
|
||||
//
|
||||
// This file contains oemcrypto engine properties that reads data from a file
|
||||
// to decide what it's current status is. It is used for testing cdm code.
|
||||
// The following properties are read from the file:
|
||||
// log_level: logging level to use.
|
||||
// 0 = LOG_ERROR,
|
||||
// 1 = LOG_WARN,
|
||||
// 2 = LOG_INFO,
|
||||
// 3 = LOG_DEBUG,
|
||||
// 4 = LOG_VERBOSE
|
||||
// kLogging*: All logging flags found in oemcrypto/include/oemcrypto_logging.h
|
||||
// can be turned on (1) or off (0).
|
||||
// security_level: returned by OEMCrypto_SecurityLevel.
|
||||
// secure_lib: If set, then this will be used as a path to
|
||||
// the L1 liboemcrypto.so that we can use secure buffers.
|
||||
// current_hdcp: returned by OEMCrypto_GetHDCPCapability and
|
||||
// used to verify the key control block in methods like DecryptCENC.
|
||||
// HDCP_NONE = 0, // No HDCP supported, no secure data path.
|
||||
// HDCP_V1 = 1, // HDCP version 1.0
|
||||
// HDCP_V2 = 2, // HDCP version 2.0 Type 1.
|
||||
// HDCP_V2_1 = 3, // HDCP version 2.1 Type 1.
|
||||
// HDCP_V2_2 = 4, // HDCP version 2.2 Type 1.
|
||||
// HDCP_NO_DIGITAL_OUTPUT = 0xff // No digital output.
|
||||
// max_hdcp: returned by OEMCrypto_GetHDCPCapability. Same values as above.
|
||||
// srm_update_supported: If "1", then srm update is supported.
|
||||
// srm_initial_version: Initial value for srm version.
|
||||
// This will be ignored after a reset. If this is not set, CurrentSRM will
|
||||
// return NOT_IMPLEMENTED.
|
||||
// srm_load_fail: If set to a nonzero number, then load_srm will
|
||||
// fail and the version will not be updated. The number is converted to
|
||||
// an OEMCryptoResult and returned.
|
||||
// srm_load_version: If this is set, then it will be used as the
|
||||
// new srm version after loading an SRM -- ignoring the contents of the SRM.
|
||||
// srm_blacklisted_device_attached: If set to "1", then a
|
||||
// oemcrypto will act as if a blacklisted device is attached.
|
||||
// security_patch_level: This is the value returned by
|
||||
// OEMCrypto_Security_Patch_Level. If the key control block requires a
|
||||
// higher level, then OEMCrypto_LoadKeys will fail.
|
||||
// max_buffer_size: maximum size of a buffer accepted by DecryptCENC and
|
||||
// friends. If this is 0, there is no restriction. If it is 1, the
|
||||
// minimum allowed value is used.
|
||||
// use_keybox: If this is 1, then the test keybox is used. If this is zero,
|
||||
// then the test OEM certificate is used.
|
||||
// use_fallback: If this is nonzero, then the installed Level 1 library will
|
||||
// be used to play content to a secure buffer. Decryption and key
|
||||
// verification are done by the mock, but then the data is copied to the
|
||||
// secure buffer using OEMCrypto_CopyBuffer. The filename of the fallback
|
||||
// library is hardcoded to "level1_backup_liboemcrypto.so". It is
|
||||
// recommended you use the install script to ensure you have the right
|
||||
// filename.
|
||||
//
|
||||
#include <arpa/inet.h>
|
||||
#include <dlfcn.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
#include "log.h"
|
||||
#include "oem_cert.h"
|
||||
#include "oemcrypto_engine_mock.h"
|
||||
#include "oemcrypto_logging.h"
|
||||
#include "properties.h"
|
||||
|
||||
namespace wvoec_mock {
|
||||
namespace {
|
||||
typedef OEMCryptoResult (*L1_Initialize_t)(void);
|
||||
typedef OEMCryptoResult (*L1_Terminate_t)(void);
|
||||
typedef OEMCryptoResult (*L1_CopyBuffer_t)(const uint8_t* data_addr,
|
||||
size_t data_length,
|
||||
OEMCrypto_DestBufferDesc* out_buffer,
|
||||
uint8_t subsample_flags);
|
||||
const std::string kDefaultOptionsFile = "/data/mediadrm/oemcrypto/options.txt";
|
||||
} // namespace
|
||||
|
||||
class AndroidModifiableCryptoEngine : public CryptoEngine {
|
||||
public:
|
||||
AndroidModifiableCryptoEngine(wvcdm::FileSystem *file_system)
|
||||
: CryptoEngine(file_system),
|
||||
options_file_(kDefaultOptionsFile),
|
||||
srm_loaded_(false),
|
||||
srm_version_(0),
|
||||
level1_valid_(false),
|
||||
level1_library_(NULL) {
|
||||
std::string path;
|
||||
if (wvcdm::Properties::GetDeviceFilesBasePath(wvcdm::kSecurityLevelL3,
|
||||
&path)) {
|
||||
options_file_ = path + "options.txt";
|
||||
}
|
||||
}
|
||||
|
||||
void MaybeReadOptionsFile() {
|
||||
static time_t last_check = 0;
|
||||
static time_t last_changed = 0;
|
||||
time_t now = time(NULL);
|
||||
if (now > last_check + 5) { // Check every five seconds.
|
||||
last_check = now;
|
||||
struct stat file_stat;
|
||||
if (stat(options_file_.c_str(), &file_stat)) {
|
||||
LOGE("Could not stat %s: %s", options_file_.c_str(), strerror(errno));
|
||||
return;
|
||||
}
|
||||
if (file_stat.st_mtime > last_changed) {
|
||||
last_changed = file_stat.st_mtime;
|
||||
ReadOptionsFile();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ReadOptionsFile() {
|
||||
FILE *file = fopen(options_file_.c_str(), "r");
|
||||
if (!file) {
|
||||
LOGE("Could not read %s %s", options_file_.c_str(), strerror(errno));
|
||||
return;
|
||||
}
|
||||
while (!feof(file)) {
|
||||
char name[80 + 1];
|
||||
int value;
|
||||
if (fscanf(file, "%80s %d", name, &value)) {
|
||||
LOGV("Option %s = %d", name, value);
|
||||
options_[std::string(name)] = value;
|
||||
}
|
||||
}
|
||||
fclose(file);
|
||||
InitializeLogging();
|
||||
}
|
||||
|
||||
int GetOption(const std::string &key, int default_value) {
|
||||
MaybeReadOptionsFile();
|
||||
if (options_.find(key) == options_.end() ) {
|
||||
LOGV("Option %s not set. Using default %d", key.c_str(), default_value);
|
||||
return default_value;
|
||||
}
|
||||
return options_[key];
|
||||
}
|
||||
|
||||
void InitializeLogging() {
|
||||
int log_level = GetOption("log_level", 3);
|
||||
int categories = 0;
|
||||
if (GetOption("kLoggingTraceOEMCryptoCalls", 0) > 0)
|
||||
categories |= kLoggingTraceOEMCryptoCalls;
|
||||
if (GetOption("kLoggingDumpContentKeys", 0) > 0)
|
||||
categories |= kLoggingDumpContentKeys;
|
||||
if (GetOption("kLoggingDumpKeyControlBlocks", 0) > 0)
|
||||
categories |= kLoggingDumpKeyControlBlocks;
|
||||
if (GetOption("kLoggingDumpDerivedKeys", 0) > 0)
|
||||
categories |= kLoggingDumpDerivedKeys;
|
||||
if (GetOption("kLoggingTraceNonce", 0) > 0)
|
||||
categories |= kLoggingTraceNonce;
|
||||
if (GetOption("kLoggingTraceDecryption", 0) > 0)
|
||||
categories |= kLoggingTraceDecryption;
|
||||
if (GetOption("kLoggingTraceUsageTable", 0) > 0)
|
||||
categories |= kLoggingTraceUsageTable;
|
||||
if (GetOption("kLoggingTraceDecryptCalls", 0) > 0)
|
||||
categories |= kLoggingTraceDecryptCalls;
|
||||
if (GetOption("kLoggingDumpTraceAll", 0) > 0)
|
||||
categories |= kLoggingDumpTraceAll;
|
||||
SetLoggingSettings(log_level, categories);
|
||||
}
|
||||
|
||||
#define QUOTE_DEFINE(A) #A
|
||||
#define QUOTE(A) QUOTE_DEFINE(A)
|
||||
#define LOOKUP(Name, Function) \
|
||||
Name = (L1_##Name##t)dlsym(level1_library_, QUOTE(Function)); \
|
||||
if (!Name) { \
|
||||
LOGW("Could not load L1 %s.", \
|
||||
QUOTE(Function)); \
|
||||
Terminate(); \
|
||||
return false; \
|
||||
}
|
||||
|
||||
virtual bool Initialize() {
|
||||
LOGD("OEMCrypto Mock With Options " " " __DATE__ " " __TIME__);
|
||||
MaybeReadOptionsFile();
|
||||
if (!GetOption("use_fallback", 1)) {
|
||||
LOGD("Level 1 fallback ignored.");
|
||||
return true;
|
||||
}
|
||||
level1_library_ = dlopen("level1_backup_liboemcrypto.so", RTLD_NOW);
|
||||
if (level1_library_ == NULL) {
|
||||
LOGE("Could not load backup: %s", dlerror());
|
||||
return false;
|
||||
}
|
||||
LOOKUP(Initialize_, OEMCrypto_Initialize);
|
||||
LOOKUP(Terminate_, OEMCrypto_Terminate);
|
||||
LOOKUP(CopyBuffer_, OEMCrypto_CopyBuffer);
|
||||
level1_valid_ = true;
|
||||
OEMCryptoResult sts = Initialize_();
|
||||
LOGD("L1 fall back initialized. status = %d.", sts);
|
||||
if (sts != OEMCrypto_SUCCESS) {
|
||||
LOGW("Terminating L1 because init failed.");
|
||||
Terminate();
|
||||
LOGW("Continuing Mock without L1 fallback.");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void Terminate() {
|
||||
if (level1_valid_) Terminate_();
|
||||
if (level1_library_ != NULL) {
|
||||
LOGD("Closing L1 fall back.\n");
|
||||
dlclose(level1_library_);
|
||||
level1_valid_ = false;
|
||||
level1_library_ = NULL;
|
||||
CopyBuffer_ = NULL;
|
||||
Initialize_ = NULL;
|
||||
Terminate_ = NULL;
|
||||
} else {
|
||||
LOGD("Terminate mock.\n");
|
||||
}
|
||||
}
|
||||
|
||||
const char *HDCPCapabilityAsString(OEMCrypto_HDCP_Capability value) {
|
||||
switch (value) {
|
||||
case HDCP_NONE:
|
||||
return "No HDCP supported, no secure data path";
|
||||
case HDCP_V1:
|
||||
return "HDCP version 1.0";
|
||||
case HDCP_V2:
|
||||
return "HDCP version 2.0";
|
||||
case HDCP_V2_1:
|
||||
return "HDCP version 2.1";
|
||||
case HDCP_V2_2:
|
||||
return "HDCP version 2.2";
|
||||
case HDCP_NO_DIGITAL_OUTPUT:
|
||||
return "No HDCP device attached/using local display with secure path";
|
||||
default:
|
||||
return "<INVALID VALUE>";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
OEMCrypto_ProvisioningMethod config_provisioning_method() {
|
||||
if (GetOption("use_keybox", 1)) {
|
||||
return OEMCrypto_Keybox;
|
||||
} else {
|
||||
return OEMCrypto_OEMCertificate;
|
||||
}
|
||||
}
|
||||
|
||||
OEMCryptoResult get_oem_certificate(SessionContext* session,
|
||||
uint8_t* public_cert,
|
||||
size_t* public_cert_length) {
|
||||
if (GetOption("use_keybox", 1)) {
|
||||
LOGD("OEM Cert asked for when use_keybox = 1.");
|
||||
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
if (kOEMPublicCertSize == 0) {
|
||||
LOGD("OEM Cert Size is 0.");
|
||||
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
if (public_cert_length == NULL) {
|
||||
LOGD("OEM Cert length is 0.");
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
if (*public_cert_length < kOEMPublicCertSize) {
|
||||
*public_cert_length = kOEMPublicCertSize;
|
||||
return OEMCrypto_ERROR_SHORT_BUFFER;
|
||||
}
|
||||
*public_cert_length = kOEMPublicCertSize;
|
||||
if (public_cert == NULL) {
|
||||
return OEMCrypto_ERROR_SHORT_BUFFER;
|
||||
}
|
||||
memcpy(public_cert, kOEMPublicCert, kOEMPublicCertSize);
|
||||
if (!session->LoadRSAKey(kOEMPrivateKey, kOEMPrivateKeySize)) {
|
||||
LOGE("Private RSA Key did not load correctly.");
|
||||
return OEMCrypto_ERROR_INVALID_RSA_KEY;
|
||||
}
|
||||
return OEMCrypto_SUCCESS;
|
||||
}
|
||||
|
||||
// Returns "L3" for a software only library. L1 is for hardware protected
|
||||
// data paths.
|
||||
const char *config_security_level() {
|
||||
switch (GetOption("security_level", 0)) {
|
||||
default:
|
||||
LOGW("Option security_level not set. Default is L3.");
|
||||
case 3:
|
||||
return "L3";
|
||||
case 2:
|
||||
return "L2";
|
||||
case 1:
|
||||
return "L1";
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the HDCP version currently in use.
|
||||
OEMCrypto_HDCP_Capability config_current_hdcp_capability() {
|
||||
static OEMCrypto_HDCP_Capability current_hdcp = HDCP_NONE;
|
||||
OEMCrypto_HDCP_Capability new_current_hdcp =
|
||||
static_cast<OEMCrypto_HDCP_Capability>(GetOption("current_hdcp", 0));
|
||||
if (current_hdcp != new_current_hdcp) {
|
||||
LOGI("OEMCrypto current HDCP changed from %d (%s) to %d (%s)", current_hdcp,
|
||||
HDCPCapabilityAsString(current_hdcp), new_current_hdcp,
|
||||
HDCPCapabilityAsString(new_current_hdcp));
|
||||
current_hdcp = new_current_hdcp;
|
||||
}
|
||||
return current_hdcp;
|
||||
}
|
||||
|
||||
// Returns the max HDCP version supported.
|
||||
OEMCrypto_HDCP_Capability config_maximum_hdcp_capability() {
|
||||
static OEMCrypto_HDCP_Capability max_hdcp = HDCP_NONE;
|
||||
MaybeReadOptionsFile();
|
||||
OEMCrypto_HDCP_Capability new_max_hdcp =
|
||||
static_cast<OEMCrypto_HDCP_Capability>(GetOption("max_hdcp", 0));
|
||||
if (max_hdcp != new_max_hdcp) {
|
||||
LOGI("OEMCrypto max HDCP changed from %d (%s) to %d (%s)", max_hdcp,
|
||||
HDCPCapabilityAsString(max_hdcp), new_max_hdcp,
|
||||
HDCPCapabilityAsString(new_max_hdcp));
|
||||
max_hdcp = new_max_hdcp;
|
||||
}
|
||||
return max_hdcp;
|
||||
}
|
||||
|
||||
// This should start at 0, and be incremented only when a security patch has
|
||||
// been applied to the device that fixes a security bug.
|
||||
uint8_t config_security_patch_level() {
|
||||
return GetOption("security_patch_level", 0);
|
||||
}
|
||||
|
||||
size_t max_buffer_size() {
|
||||
int max = GetOption("max_buffer_size", 0);
|
||||
// If max is 1, just use default max buffer.
|
||||
if (max == 1) return CryptoEngine::max_buffer_size();
|
||||
return max; // If 0, no restriction. If something else, use that restriction.
|
||||
}
|
||||
|
||||
virtual void adjust_destination(OEMCrypto_DestBufferDesc *out_description,
|
||||
size_t data_length, uint8_t subsample_flags) {
|
||||
if (out_description->type != OEMCrypto_BufferType_Secure) return;
|
||||
if (!level1_valid_) {
|
||||
static bool warned_once = false;
|
||||
if (!warned_once) {
|
||||
warned_once = true;
|
||||
LOGW("OEMCrypto Mock: given secure buffer with no level1 fallback.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (subsample_flags & OEMCrypto_FirstSubsample) {
|
||||
final_destination_.type = OEMCrypto_BufferType_Secure;
|
||||
final_destination_.buffer.secure.handle =
|
||||
out_description->buffer.secure.handle;
|
||||
final_destination_.buffer.secure.max_length =
|
||||
out_description->buffer.secure.max_length;
|
||||
final_destination_.buffer.secure.offset =
|
||||
out_description->buffer.secure.offset;
|
||||
temp_buffer_.resize(final_destination_.buffer.secure.max_length);
|
||||
temp_buffer_length_ = 0;
|
||||
}
|
||||
if (temp_buffer_length_ != out_description->buffer.secure.offset) {
|
||||
LOGW("OEMCrypto: offset into secure buffer is not correct %zd != %zd.",
|
||||
temp_buffer_length_, out_description->buffer.secure.offset);
|
||||
}
|
||||
size_t new_length = temp_buffer_length_ + data_length;
|
||||
if (new_length > temp_buffer_.size()) {
|
||||
LOGW("Temp buffer was not big enough. %zd > %zd.", new_length,
|
||||
temp_buffer_.size());
|
||||
temp_buffer_.resize(new_length);
|
||||
}
|
||||
destination_ = &temp_buffer_[temp_buffer_length_];
|
||||
temp_buffer_length_ = new_length;
|
||||
}
|
||||
|
||||
// Push destination buffer to L1 output.
|
||||
virtual OEMCryptoResult PushDestination(
|
||||
OEMCrypto_DestBufferDesc *out_description, uint8_t subsample_flags) {
|
||||
if (level1_valid_ &&
|
||||
(out_description->type == OEMCrypto_BufferType_Secure)) {
|
||||
if (subsample_flags & OEMCrypto_LastSubsample) {
|
||||
return CopyBuffer_(&temp_buffer_[0], temp_buffer_length_,
|
||||
&final_destination_,
|
||||
OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample);
|
||||
}
|
||||
}
|
||||
return OEMCrypto_SUCCESS;
|
||||
}
|
||||
|
||||
private:
|
||||
// If the SRM version has been loaded or not. If not, we use the system
|
||||
// property to find the current SRM version.
|
||||
bool srm_loaded_;
|
||||
// Current srm version. Before an SRM has been loaded, this will be set from
|
||||
// the system property.
|
||||
int srm_version_;
|
||||
|
||||
std::map<std::string, int> options_;
|
||||
|
||||
std::string options_file_;
|
||||
bool level1_valid_;
|
||||
void* level1_library_;
|
||||
L1_CopyBuffer_t CopyBuffer_;
|
||||
L1_Initialize_t Initialize_;
|
||||
L1_Terminate_t Terminate_;
|
||||
OEMCrypto_DestBufferDesc final_destination_;
|
||||
std::vector<uint8_t> temp_buffer_;
|
||||
size_t temp_buffer_length_; // Length of temp buffer currently in use.
|
||||
};
|
||||
|
||||
CryptoEngine* CryptoEngine::MakeCryptoEngine(wvcdm::FileSystem *file_system) {
|
||||
return new AndroidModifiableCryptoEngine(file_system);
|
||||
}
|
||||
|
||||
} // namespace wvoec_mock
|
||||
Reference in New Issue
Block a user