From 5c768c896e93641016b7172d7c4f7d1f01076776 Mon Sep 17 00:00:00 2001 From: Fred Gylys-Colwell Date: Fri, 14 Apr 2017 13:49:41 -0700 Subject: [PATCH] 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 --- ...oemcrypto_engine_device_properties_mod.cpp | 414 ++++++++++++++++++ .../mock/src/oemcrypto_engine_mock.cpp | 1 + .../mock/src/oemcrypto_engine_mock.h | 11 + .../oemcrypto/mock/src/oemcrypto_mock.cpp | 6 +- 4 files changed, 430 insertions(+), 2 deletions(-) create mode 100644 libwvdrmengine/oemcrypto/mock/src/oemcrypto_engine_device_properties_mod.cpp diff --git a/libwvdrmengine/oemcrypto/mock/src/oemcrypto_engine_device_properties_mod.cpp b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_engine_device_properties_mod.cpp new file mode 100644 index 00000000..63f588ef --- /dev/null +++ b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_engine_device_properties_mod.cpp @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#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 ""; + } + } + + + 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(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(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 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 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 diff --git a/libwvdrmengine/oemcrypto/mock/src/oemcrypto_engine_mock.cpp b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_engine_mock.cpp index 1a8df6a9..a3cfdaa5 100644 --- a/libwvdrmengine/oemcrypto/mock/src/oemcrypto_engine_mock.cpp +++ b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_engine_mock.cpp @@ -112,6 +112,7 @@ OEMCryptoResult CryptoEngine::SetDestination( LOGE("[SetDestination(): OEMCrypto_ERROR_SHORT_BUFFER]"); return OEMCrypto_ERROR_SHORT_BUFFER; } + adjust_destination(out_description, data_length, subsample_flags); if ((out_description->type != OEMCrypto_BufferType_Direct) && (destination_ == NULL)) { return OEMCrypto_ERROR_INVALID_CONTEXT; diff --git a/libwvdrmengine/oemcrypto/mock/src/oemcrypto_engine_mock.h b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_engine_mock.h index f1f5a378..3452f2c0 100644 --- a/libwvdrmengine/oemcrypto/mock/src/oemcrypto_engine_mock.h +++ b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_engine_mock.h @@ -130,6 +130,17 @@ class CryptoEngine { // The current destination. uint8_t* destination() { return destination_; } + // Subclasses can adjust the destination -- for use in testing. + virtual void adjust_destination(OEMCrypto_DestBufferDesc* out_description, + size_t data_length, uint8_t subsample_flags) { + } + + // Push destination buffer to output -- used by subclasses for testing. + virtual OEMCryptoResult PushDestination( + OEMCrypto_DestBufferDesc* out_description, uint8_t subsample_flags) { + return OEMCrypto_SUCCESS; + } + protected: explicit CryptoEngine(wvcdm::FileSystem* file_system); uint8_t* destination_; diff --git a/libwvdrmengine/oemcrypto/mock/src/oemcrypto_mock.cpp b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_mock.cpp index d6374370..3f956950 100644 --- a/libwvdrmengine/oemcrypto/mock/src/oemcrypto_mock.cpp +++ b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_mock.cpp @@ -570,9 +570,11 @@ extern "C" OEMCryptoResult OEMCrypto_DecryptCENC( return OEMCrypto_ERROR_INVALID_SESSION; } - return session_ctx->DecryptCENC( + OEMCryptoResult result = session_ctx->DecryptCENC( iv, block_offset, pattern, data_addr, data_length, is_encrypted, crypto_engine->destination(), out_buffer->type); + if (result != OEMCrypto_SUCCESS) return result; + return crypto_engine->PushDestination(out_buffer, subsample_flags); } extern "C" OEMCryptoResult OEMCrypto_CopyBuffer( @@ -602,7 +604,7 @@ extern "C" OEMCryptoResult OEMCrypto_CopyBuffer( if (crypto_engine->destination() != NULL) { memcpy(crypto_engine->destination(), data_addr, data_length); } - return OEMCrypto_SUCCESS; + return crypto_engine->PushDestination(out_buffer, subsample_flags); } extern "C" OEMCryptoResult OEMCrypto_WrapKeybox(const uint8_t* keybox,