Cherry pick 18.4 changes to udc-widevine-dev
Get the udc-widevine-dev Android branch and oemcrypto-v18 cdm branch in sync. The commit ID for 18.4 on oemcrypto-v18 is https://widevine-internal.git.corp.google.com/cdm/+/a2f23a2281e5e06dc2867585bdc516fa132b639. Merged from go/wvgerrit/190151 Bug: 290252845 Test: unit tests passing on Panther device Change-Id: I63fa3f1c784f737ca1480e5febe4f3f5a8a49948
This commit is contained in:
@@ -332,6 +332,12 @@ class CdmEngine {
|
||||
return CryptoSession::SetDebugIgnoreKeyboxCount(count);
|
||||
}
|
||||
|
||||
// This tells the OEMCrypto adapter to allow the device to continue with a
|
||||
// test keybox. Otherwise, the keybox is reported as invalid.
|
||||
static CdmResponseType SetAllowTestKeybox(bool allow) {
|
||||
return CryptoSession::SetAllowTestKeybox(allow);
|
||||
}
|
||||
|
||||
static CdmResponseType ParseDecryptHashString(const std::string& hash_string,
|
||||
CdmSessionId* id,
|
||||
uint32_t* frame_number,
|
||||
|
||||
@@ -338,6 +338,10 @@ class CryptoSession {
|
||||
// report that it needs provisioning instead.
|
||||
static CdmResponseType SetDebugIgnoreKeyboxCount(uint32_t count);
|
||||
|
||||
// This tells the OEMCrypto adapter to allow the device to continue with a
|
||||
// test keybox. Otherwise, the keybox is reported as invalid.
|
||||
static CdmResponseType SetAllowTestKeybox(bool allow);
|
||||
|
||||
// Returns a system-wide singleton instance of SystemFallbackPolicy
|
||||
// to be used for communicating OTA keybox provisioning state between
|
||||
// apps. Returns a null pointer if OTA provisioning is not supported,
|
||||
|
||||
@@ -20,6 +20,10 @@ OEMCryptoResult OEMCrypto_InitializeAndCheckKeybox(
|
||||
// report that it needs provisioning instead.
|
||||
OEMCryptoResult OEMCrypto_SetDebugIgnoreKeyboxCount(uint32_t count);
|
||||
|
||||
// This tells the OEMCrypto adapter to allow the device to continue with a
|
||||
// test keybox. Otherwise, the keybox is reported as invalid.
|
||||
OEMCryptoResult OEMCrypto_SetAllowTestKeybox(bool allow);
|
||||
|
||||
// This attempts to open a session at the desired security level.
|
||||
// If one level is not available, the other will be used instead.
|
||||
OEMCryptoResult OEMCrypto_OpenSession(OEMCrypto_SESSION* session,
|
||||
|
||||
@@ -2404,9 +2404,9 @@ CdmResponseType CdmEngine::SignRsa(const std::string& wrapped_key,
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> lock(session_map_lock_);
|
||||
if (!session_map_.FindSession(session_id, &session)) {
|
||||
LOGE("Session not found: session_id = %s", IdToString(session_id));
|
||||
CloseSession(session_id);
|
||||
return CdmResponseType(SESSION_NOT_FOUND_24);
|
||||
LOGE("Session not found: session_id = %s", IdToString(session_id));
|
||||
CloseSession(session_id);
|
||||
return CdmResponseType(SESSION_NOT_FOUND_24);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -263,14 +263,8 @@ CdmResponseType CdmSession::RestoreOfflineSession(const CdmKeySetId& key_set_id,
|
||||
usage_entry_ = std::move(license_data.usage_entry);
|
||||
usage_entry_index_ = license_data.usage_entry_index;
|
||||
|
||||
// If ATSC mode is enabled, use ATSC DRM cert/private key, rather than any
|
||||
// cert/private key embedded in the license.
|
||||
CdmResponseType result =
|
||||
atsc_mode_enabled_
|
||||
? LoadPrivateKey()
|
||||
: LoadPrivateOrLegacyKey(license_data.drm_certificate,
|
||||
license_data.wrapped_private_key);
|
||||
|
||||
CdmResponseType result = LoadPrivateOrLegacyKey(
|
||||
license_data.drm_certificate, license_data.wrapped_private_key);
|
||||
if (result != NO_ERROR) return result;
|
||||
|
||||
// Attempts to restore a released offline license are treated as a release
|
||||
|
||||
@@ -3375,6 +3375,11 @@ CdmResponseType CryptoSession::SetDebugIgnoreKeyboxCount(uint32_t count) {
|
||||
return MapOEMCryptoResult(status, UNKNOWN_ERROR, "SetDebugIgnoreKeyboxCount");
|
||||
}
|
||||
|
||||
CdmResponseType CryptoSession::SetAllowTestKeybox(bool allow) {
|
||||
OEMCryptoResult status = OEMCrypto_SetAllowTestKeybox(allow);
|
||||
return MapOEMCryptoResult(status, UNKNOWN_ERROR, "SetAllowTestKeybox");
|
||||
}
|
||||
|
||||
okp::SystemFallbackPolicy* CryptoSession::GetOkpFallbackPolicy() {
|
||||
const auto getter = [&]() -> okp::SystemFallbackPolicy* {
|
||||
// If not set, then OTA keybox provisioning is not supported or
|
||||
|
||||
@@ -626,6 +626,17 @@ std::string GetIgnoreCountFile() {
|
||||
return path;
|
||||
}
|
||||
|
||||
std::string GetAllowTestKeyboxFile() {
|
||||
std::string path;
|
||||
if (!wvcdm::Properties::GetDeviceFilesBasePath(wvcdm::kSecurityLevelL1,
|
||||
&path)) {
|
||||
LOGW("GetAllowTestKeyboxFile: Unable to get base path");
|
||||
path = "/data/";
|
||||
}
|
||||
path += "debug_allow_test_keybox.txt";
|
||||
return path;
|
||||
}
|
||||
|
||||
uint32_t GetDebugIgnoreKeyboxCount() {
|
||||
const std::string filename = GetIgnoreCountFile();
|
||||
wvutil::FileSystem file_system;
|
||||
@@ -678,6 +689,49 @@ OEMCryptoResult SetDebugIgnoreKeyboxCount(uint32_t count) {
|
||||
return OEMCrypto_SUCCESS;
|
||||
}
|
||||
|
||||
bool GetAllowTestKeybox() {
|
||||
const std::string filename = GetAllowTestKeyboxFile();
|
||||
wvutil::FileSystem file_system;
|
||||
if (!file_system.Exists(filename)) {
|
||||
return 0;
|
||||
}
|
||||
auto file = file_system.Open(filename, file_system.kReadOnly);
|
||||
if (!file) {
|
||||
LOGE("Error opening %s", filename.c_str());
|
||||
return 0;
|
||||
}
|
||||
ssize_t size = file_system.FileSize(filename);
|
||||
std::string contents(size, ' ');
|
||||
ssize_t size_read = file->Read(const_cast<char*>(contents.data()), size);
|
||||
if (size != size_read) {
|
||||
LOGE("Short allow_test_keybox = %zu", size_read);
|
||||
return 0;
|
||||
}
|
||||
// skip whitespace or any extra garbage.
|
||||
return (std::string::npos != contents.find("true"));
|
||||
}
|
||||
|
||||
OEMCryptoResult SetAllowTestKeybox(bool allow) {
|
||||
const std::string filename = GetAllowTestKeyboxFile();
|
||||
wvutil::FileSystem file_system;
|
||||
auto file =
|
||||
file_system.Open(filename, file_system.kCreate | file_system.kTruncate);
|
||||
if (!file) {
|
||||
LOGE("Could not create file %s", filename.c_str());
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
const std::string contents = allow ? "true\n" : "false\n";
|
||||
const size_t size = contents.size();
|
||||
ssize_t size_written = file->Write(contents.data(), size);
|
||||
if (static_cast<ssize_t>(size) != size_written) {
|
||||
LOGE("Wrote %zd bytes of %s, not %zd, to file %s", size_written,
|
||||
contents.c_str(), size, filename.c_str());
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
LOGD("Wrote %s to %s", contents.c_str(), filename.c_str());
|
||||
return OEMCrypto_SUCCESS;
|
||||
}
|
||||
|
||||
typedef enum OEMCryptoSessionType {
|
||||
SESSION_TYPE_OEMCRYPTO = 0,
|
||||
SESSION_TYPE_ENTITLED_KEY = 1,
|
||||
@@ -1260,6 +1314,18 @@ class Adapter {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Check the system ID of the keybox. This should only be called if the device
|
||||
// uses provisioning 2.0.
|
||||
bool UsingTestKeybox() {
|
||||
uint8_t key_data[256];
|
||||
size_t key_data_len = sizeof(key_data);
|
||||
OEMCryptoResult sts = OEMCrypto_GetKeyData(key_data, &key_data_len);
|
||||
if (sts != OEMCrypto_SUCCESS) return true;
|
||||
uint32_t* data = reinterpret_cast<uint32_t*>(key_data);
|
||||
uint32_t system_id = htonl(data[1]);
|
||||
return system_id == 7912;
|
||||
}
|
||||
|
||||
// Check the L1 keybox or cert. If it is valid, return success. If not, try to
|
||||
// install one. If one is not available, but OTA provisioning is supported,
|
||||
// return OEMCrypto_ERROR_NEEDS_KEYBOX_PROVISIONING. If none of these work,
|
||||
@@ -1295,6 +1361,19 @@ class Adapter {
|
||||
// Check if the keybox or oem certificate is valid, if so, we are finished
|
||||
// with initialization. Record some metrics and return success.
|
||||
const OEMCryptoResult rot_valid = level1_.IsKeyboxOrOEMCertValid();
|
||||
// For production systems, we do wish to use a test keybox. We do not force
|
||||
// a fallback to L3 at this point, because this can be overridden by test
|
||||
// code that requires a test keybox.
|
||||
if ((rot_valid == OEMCrypto_SUCCESS) &&
|
||||
(provisioning_method == OEMCrypto_Keybox) && UsingTestKeybox()) {
|
||||
if (GetAllowTestKeybox()) {
|
||||
LOGW("Allowing device with test keybox installed.");
|
||||
} else {
|
||||
LOGW("Device has test keybox installed.");
|
||||
return OEMCrypto_ERROR_KEYBOX_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
if (rot_valid == OEMCrypto_SUCCESS) {
|
||||
// The keybox or certificate is valid -- that means initialization is done
|
||||
// and we only have save some metrics and return.
|
||||
@@ -1745,6 +1824,9 @@ OEMCryptoResult OEMCrypto_GetOEMPublicCertificate(
|
||||
OEMCryptoResult OEMCrypto_SetDebugIgnoreKeyboxCount(uint32_t count) {
|
||||
return SetDebugIgnoreKeyboxCount(count);
|
||||
}
|
||||
OEMCryptoResult OEMCrypto_SetAllowTestKeybox(bool allow) {
|
||||
return SetAllowTestKeybox(allow);
|
||||
}
|
||||
|
||||
OEMCrypto_WatermarkingSupport OEMCrypto_GetWatermarkingSupport(
|
||||
wvcdm::RequestedSecurityLevel level) {
|
||||
|
||||
@@ -165,12 +165,25 @@ bool HttpSocket::ParseUrl(const std::string& url, std::string* scheme,
|
||||
return false;
|
||||
}
|
||||
|
||||
// Strip off the domain name and port. In the url it will be terminated by
|
||||
// either a splash or a question mark:
|
||||
// like this example.com?key=value
|
||||
// or this example.com/path/to/resource
|
||||
if (!Tokenize(url, "/", offset, domain_name, &offset)) {
|
||||
// The rest of the URL belongs to the domain name.
|
||||
domain_name->assign(url, offset, std::string::npos);
|
||||
// No explicit path after the domain name.
|
||||
path->assign("/");
|
||||
if (Tokenize(url, "?", offset, domain_name, &offset)) {
|
||||
// url had no '/', but it did have '?'. Use the default path but
|
||||
// keep the extra parameters. i.e. turn '?extra' into '/?extra'.
|
||||
path->assign("/");
|
||||
path->append(url, offset - 1, std::string::npos);
|
||||
} else {
|
||||
// url had no '/' or '?'.
|
||||
// The rest of the URL belongs to the domain name.
|
||||
domain_name->assign(url, offset, std::string::npos);
|
||||
// Use the default path.
|
||||
path->assign("/");
|
||||
}
|
||||
} else {
|
||||
// url had a '/'.
|
||||
// The rest of the URL, including the preceding slash, belongs to the path.
|
||||
path->assign(url, offset - 1, std::string::npos);
|
||||
}
|
||||
@@ -192,7 +205,7 @@ bool HttpSocket::ParseUrl(const std::string& url, std::string* scheme,
|
||||
}
|
||||
|
||||
HttpSocket::HttpSocket(const std::string& url)
|
||||
: socket_fd_(-1), ssl_(nullptr), ssl_ctx_(nullptr) {
|
||||
: url_(url), socket_fd_(-1), ssl_(nullptr), ssl_ctx_(nullptr) {
|
||||
valid_url_ = ParseUrl(url, &scheme_, &secure_connect_, &domain_name_, &port_,
|
||||
&resource_path_);
|
||||
create_time_ =
|
||||
@@ -280,10 +293,12 @@ bool HttpSocket::Connect(int timeout_in_ms) {
|
||||
if (ret == EAI_SYSTEM) {
|
||||
// EAI_SYSTEM implies an underlying system issue. Error is
|
||||
// specified by |errno|.
|
||||
LOGE("getaddrinfo failed due to system error: errno = %d", GetError());
|
||||
LOGE("getaddrinfo %s (port %s) failed due to system error: errno = %d",
|
||||
domain_name_.c_str(), port_.c_str(), GetError());
|
||||
} else {
|
||||
// Error is specified by return value.
|
||||
LOGE("getaddrinfo failed: ret = %d", ret);
|
||||
LOGE("getaddrinfo %s (port %s) failed: ret = %d", domain_name_.c_str(),
|
||||
port_.c_str(), ret);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -292,7 +307,8 @@ bool HttpSocket::Connect(int timeout_in_ms) {
|
||||
socket_fd_ = socket(addr_info->ai_family, addr_info->ai_socktype,
|
||||
addr_info->ai_protocol);
|
||||
if (socket_fd_ < 0) {
|
||||
LOGE("Cannot open socket: errno = %d", GetError());
|
||||
LOGE("Cannot open socket %s (port %s): errno = %d", domain_name_.c_str(),
|
||||
port_.c_str(), GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -300,19 +316,22 @@ bool HttpSocket::Connect(int timeout_in_ms) {
|
||||
#ifdef _WIN32
|
||||
u_long mode = 1; // Non-blocking mode.
|
||||
if (ioctlsocket(socket_fd_, FIONBIO, &mode) != 0) {
|
||||
LOGE("ioctlsocket error, wsa error = %d", WSAGetLastError());
|
||||
LOGE("ioctlsocket error %s (port %s), wsa error = %d", domain_name_.c_str(),
|
||||
port_.c_str(), WSAGetLastError());
|
||||
CloseSocket();
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
const int original_flags = fcntl(socket_fd_, F_GETFL, 0);
|
||||
if (original_flags == -1) {
|
||||
LOGE("fcntl error, errno = %d", errno);
|
||||
LOGE("fcntl error %s (port %s), errno = %d", domain_name_.c_str(),
|
||||
port_.c_str(), errno);
|
||||
CloseSocket();
|
||||
return false;
|
||||
}
|
||||
if (fcntl(socket_fd_, F_SETFL, original_flags | O_NONBLOCK) == -1) {
|
||||
LOGE("fcntl error, errno = %d", errno);
|
||||
LOGE("fcntl error %s (port %s), errno = %d", domain_name_.c_str(),
|
||||
port_.c_str(), errno);
|
||||
CloseSocket();
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -32,9 +32,7 @@ class HttpSocket {
|
||||
const std::string& domain_name() const { return domain_name_; }
|
||||
int port() const { return atoi(port_.c_str()); }
|
||||
const std::string& resource_path() const { return resource_path_; }
|
||||
std::string url() const {
|
||||
return scheme_ + "://" + domain_name_ + ":" + port_ + resource_path_;
|
||||
}
|
||||
const std::string& url() const { return url_; }
|
||||
|
||||
int ReadAndLogErrors(char* data, int len, int timeout_in_ms);
|
||||
int WriteAndLogErrors(const char* data, int len, int timeout_in_ms);
|
||||
@@ -57,6 +55,7 @@ class HttpSocket {
|
||||
bool Wait(bool for_read, int timeout_in_ms);
|
||||
FRIEND_TEST(HttpSocketTest, ParseUrlTest);
|
||||
|
||||
std::string url_;
|
||||
std::string scheme_;
|
||||
bool secure_connect_;
|
||||
std::string domain_name_;
|
||||
|
||||
218
libwvdrmengine/cdm/core/test/message_dumper.cpp
Normal file
218
libwvdrmengine/cdm/core/test/message_dumper.cpp
Normal file
@@ -0,0 +1,218 @@
|
||||
// Copyright 2023 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine License
|
||||
// Agreement.
|
||||
|
||||
#include "message_dumper.h"
|
||||
|
||||
#include "license_request.h"
|
||||
#include "odk.h"
|
||||
#include "odk_message.h"
|
||||
#include "odk_serialize.h"
|
||||
#include "odk_structs.h"
|
||||
#include "odk_structs_priv.h"
|
||||
#include "oec_device_features.h"
|
||||
#include "test_base.h"
|
||||
|
||||
using video_widevine::License;
|
||||
using video_widevine::LicenseRequest;
|
||||
using video_widevine::SignedMessage;
|
||||
using video_widevine::SignedProvisioningMessage;
|
||||
|
||||
namespace wvcdm {
|
||||
namespace {
|
||||
void DumpHeader(std::ofstream* out, const std::string& type) {
|
||||
const ::testing::TestInfo* const test_info =
|
||||
::testing::UnitTest::GetInstance()->current_test_info();
|
||||
const std::string suite = test_info->test_case_name();
|
||||
const std::string name = test_info->name();
|
||||
std::string new_test_name = suite + "_" + name;
|
||||
// Replace the slashes with underscores so we can use it as a name again.
|
||||
std::replace(new_test_name.begin(), new_test_name.end(), '/', '_');
|
||||
*out << "\nTEST_F(ODKGolden" << type << "V" << ODK_MAJOR_VERSION << ", "
|
||||
<< new_test_name << ") {\n";
|
||||
}
|
||||
|
||||
void DumpHex(std::ofstream* out, const std::string& name,
|
||||
const std::string& value) {
|
||||
*out << "const uint8_t " << name << "_raw[] = {\n";
|
||||
*out << " ";
|
||||
for (unsigned int i = 0; i < value.length(); i++) {
|
||||
if ((i > 0) && (i % 10 == 0)) *out << "\n ";
|
||||
uint8_t c = value[i];
|
||||
*out << "0x" << std::hex << std::setw(2) << std::setfill('0') << int(c)
|
||||
<< ", ";
|
||||
}
|
||||
*out << "\n};\n";
|
||||
*out << name << "_ = std::string (\n"
|
||||
<< " reinterpret_cast<const char *>(" << name << "_raw), \n"
|
||||
<< " sizeof(" << name << "_raw));\n";
|
||||
*out << std::dec; // Turn off hex when we're done.
|
||||
}
|
||||
} // namespace
|
||||
|
||||
std::ofstream MessageDumper::license_file;
|
||||
std::ofstream MessageDumper::renewal_file;
|
||||
std::ofstream MessageDumper::provision_file;
|
||||
|
||||
void MessageDumper::SetUp() {
|
||||
LOGD("Creating golden data files for ODK golden data tests.");
|
||||
license_file.open("license_data.cpp");
|
||||
if (!license_file) LOGE("Could not open dump file license_data.cpp");
|
||||
renewal_file.open("renewal_data.cpp");
|
||||
if (!renewal_file) LOGE("Could not open dump file renewal_data.cpp");
|
||||
provision_file.open("provision_data.cpp");
|
||||
if (!provision_file) LOGE("Could not open dump file provision_data.cpp");
|
||||
}
|
||||
|
||||
void MessageDumper::TearDown() {
|
||||
LOGD("Closing golden data files.");
|
||||
license_file.close();
|
||||
renewal_file.close();
|
||||
provision_file.close();
|
||||
}
|
||||
|
||||
void MessageDumper::DumpLicenseRequest(const CdmKeyRequest& request) {
|
||||
SignedMessage signed_message;
|
||||
DumpHeader(&license_file, "License");
|
||||
EXPECT_TRUE(signed_message.ParseFromString(request.message));
|
||||
EXPECT_TRUE(signed_message.has_oemcrypto_core_message());
|
||||
DumpHex(&license_file, "core_request",
|
||||
signed_message.oemcrypto_core_message());
|
||||
// Since this is run within a test, we can also verify that the
|
||||
// request is valid.
|
||||
video_widevine::LicenseRequest license_request;
|
||||
EXPECT_TRUE(license_request.ParseFromString(signed_message.msg()));
|
||||
}
|
||||
|
||||
void MessageDumper::DumpLicense(const std::string& response) {
|
||||
SignedMessage signed_response;
|
||||
EXPECT_TRUE(signed_response.ParseFromString(response));
|
||||
EXPECT_TRUE(signed_response.has_oemcrypto_core_message());
|
||||
DumpHex(&license_file, "core_response",
|
||||
signed_response.oemcrypto_core_message());
|
||||
|
||||
video_widevine::License license;
|
||||
EXPECT_TRUE(license.ParseFromString(signed_response.msg()));
|
||||
DumpHex(&license_file, "serialized_license", signed_response.msg());
|
||||
|
||||
std::string message =
|
||||
signed_response.oemcrypto_core_message() + signed_response.msg();
|
||||
ODK_Message odk_msg = ODK_Message_Create(
|
||||
reinterpret_cast<uint8_t*>(const_cast<char*>(message.c_str())),
|
||||
message.length());
|
||||
ODK_Message_SetSize(&odk_msg,
|
||||
signed_response.oemcrypto_core_message().length());
|
||||
ODK_ParsedLicense odk_parsed_license = {};
|
||||
ODK_LicenseResponse odk_license_response = {};
|
||||
odk_license_response.parsed_license = &odk_parsed_license;
|
||||
Unpack_ODK_LicenseResponse(&odk_msg, &odk_license_response);
|
||||
EXPECT_EQ(ODK_Message_GetStatus(&odk_msg), MESSAGE_STATUS_OK);
|
||||
// Valid hash is only needed for v16 messages.
|
||||
std::string hash(ODK_SHA256_HASH_SIZE, ' ');
|
||||
DumpHex(&license_file, "core_request_sha256", hash);
|
||||
license_file << " nonce_required_ = "
|
||||
<< (odk_parsed_license.nonce_required ? "true" : "false")
|
||||
<< ";\n";
|
||||
license_file << " RunTest();\n";
|
||||
license_file << "}\n\n";
|
||||
}
|
||||
|
||||
void MessageDumper::DumpRenewalRequest(const CdmKeyRequest& request) {
|
||||
DumpHeader(&renewal_file, "Renewal");
|
||||
SignedMessage signed_message;
|
||||
EXPECT_TRUE(signed_message.ParseFromString(request.message));
|
||||
EXPECT_TRUE(signed_message.has_oemcrypto_core_message());
|
||||
DumpHex(&renewal_file, "core_request",
|
||||
signed_message.oemcrypto_core_message());
|
||||
|
||||
video_widevine::LicenseRequest renewal_request;
|
||||
EXPECT_TRUE(renewal_request.ParseFromString(signed_message.msg()));
|
||||
}
|
||||
|
||||
void MessageDumper::DumpRenewal(const std::string& response) {
|
||||
SignedMessage signed_response;
|
||||
EXPECT_TRUE(signed_response.ParseFromString(response))
|
||||
<< "Response = " << wvutil::b2a_hex(response);
|
||||
EXPECT_TRUE(signed_response.has_oemcrypto_core_message());
|
||||
DumpHex(&renewal_file, "core_response",
|
||||
signed_response.oemcrypto_core_message());
|
||||
|
||||
video_widevine::License renewal;
|
||||
EXPECT_TRUE(renewal.ParseFromString(signed_response.msg()));
|
||||
DumpHex(&renewal_file, "renewal", signed_response.msg());
|
||||
|
||||
std::string message =
|
||||
signed_response.oemcrypto_core_message() + signed_response.msg();
|
||||
ODK_Message odk_msg = ODK_Message_Create(
|
||||
reinterpret_cast<uint8_t*>(const_cast<char*>(message.c_str())),
|
||||
message.length());
|
||||
ODK_Message_SetSize(&odk_msg,
|
||||
signed_response.oemcrypto_core_message().length());
|
||||
ODK_RenewalResponse odk_renewal_response = {};
|
||||
Unpack_ODK_RenewalResponse(&odk_msg, &odk_renewal_response);
|
||||
EXPECT_EQ(ODK_Message_GetStatus(&odk_msg), MESSAGE_STATUS_OK);
|
||||
renewal_file << " renewal_duration_seconds_ = "
|
||||
<< odk_renewal_response.renewal_duration_seconds << ";\n";
|
||||
renewal_file << " RunTest();\n";
|
||||
renewal_file << "}\n\n";
|
||||
}
|
||||
|
||||
void MessageDumper::DumpProvisioningRequest(
|
||||
const CdmProvisioningRequest& request) {
|
||||
if (wvoec::global_features.derive_key_method ==
|
||||
wvoec::DeviceFeatures::TEST_PROVISION_40) {
|
||||
LOGD("Provisioning 4.0 does not have a v17 or v18 core message.");
|
||||
} else {
|
||||
DumpHeader(&provision_file, "Provision");
|
||||
SignedProvisioningMessage signed_message;
|
||||
EXPECT_TRUE(signed_message.ParseFromString(request))
|
||||
<< "Request = " << wvutil::b2a_hex(request);
|
||||
EXPECT_TRUE(signed_message.has_oemcrypto_core_message());
|
||||
DumpHex(&provision_file, "core_request",
|
||||
signed_message.oemcrypto_core_message());
|
||||
}
|
||||
}
|
||||
|
||||
void MessageDumper::DumpProvisioning(const CdmProvisioningResponse& response) {
|
||||
if (wvoec::global_features.derive_key_method ==
|
||||
wvoec::DeviceFeatures::TEST_PROVISION_40) {
|
||||
LOGD("Provisioning 4.0 does not have a v17 core message.");
|
||||
} else {
|
||||
SignedProvisioningMessage signed_response;
|
||||
if (!signed_response.ParseFromString(response)) {
|
||||
// A binary provisioning response is buried within a json structure.
|
||||
std::string extracted_message;
|
||||
EXPECT_TRUE(CertificateProvisioning::ExtractAndDecodeSignedMessage(
|
||||
response, &extracted_message));
|
||||
EXPECT_TRUE(signed_response.ParseFromString(extracted_message));
|
||||
}
|
||||
EXPECT_TRUE(signed_response.has_oemcrypto_core_message());
|
||||
DumpHex(&provision_file, "core_response",
|
||||
signed_response.oemcrypto_core_message());
|
||||
DumpHex(&provision_file, "provisioning_response",
|
||||
signed_response.message());
|
||||
// The choice of ECC or RSA key is decided at the server, based on
|
||||
// information in the DCSL. We can only reproduce this by looking
|
||||
// at the current response.
|
||||
std::string message =
|
||||
signed_response.oemcrypto_core_message() + signed_response.message();
|
||||
ODK_Message odk_msg = ODK_Message_Create(
|
||||
reinterpret_cast<uint8_t*>(const_cast<char*>(message.c_str())),
|
||||
message.length());
|
||||
ODK_Message_SetSize(&odk_msg,
|
||||
signed_response.oemcrypto_core_message().length());
|
||||
ODK_ParsedProvisioning odk_parsed_response;
|
||||
ODK_ProvisioningResponse provisioning_response;
|
||||
provisioning_response.parsed_provisioning = &odk_parsed_response;
|
||||
Unpack_ODK_ProvisioningResponse(&odk_msg, &provisioning_response);
|
||||
EXPECT_EQ(ODK_Message_GetStatus(&odk_msg), MESSAGE_STATUS_OK);
|
||||
provision_file << " device_key_type_ = "
|
||||
<< ((odk_parsed_response.key_type ==
|
||||
OEMCrypto_RSA_Private_Key)
|
||||
? "OEMCrypto_RSA_Private_Key;\n"
|
||||
: "OEMCrypto_ECC_Private_Key;\n");
|
||||
provision_file << " RunTest();\n";
|
||||
provision_file << "}\n\n";
|
||||
}
|
||||
}
|
||||
} // namespace wvcdm
|
||||
39
libwvdrmengine/cdm/core/test/message_dumper.h
Normal file
39
libwvdrmengine/cdm/core/test/message_dumper.h
Normal file
@@ -0,0 +1,39 @@
|
||||
// Copyright 2023 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine License
|
||||
// Agreement.
|
||||
|
||||
#ifndef WVCDM_CORE_TEST_MESSAGE_DUMPER_H_
|
||||
#define WVCDM_CORE_TEST_MESSAGE_DUMPER_H_
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "log.h"
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
class MessageDumper : public ::testing::Environment {
|
||||
// This dumps messages to a file so that the data can easily be turned
|
||||
// into golden data tests for the ODK library.
|
||||
public:
|
||||
~MessageDumper() override {}
|
||||
void SetUp() override;
|
||||
void TearDown() override;
|
||||
|
||||
static void DumpLicenseRequest(const CdmKeyRequest& request);
|
||||
static void DumpLicense(const std::string& response);
|
||||
static void DumpRenewalRequest(const CdmKeyRequest& request);
|
||||
static void DumpRenewal(const std::string& response);
|
||||
static void DumpProvisioningRequest(const CdmProvisioningRequest& request);
|
||||
static void DumpProvisioning(const CdmProvisioningResponse& response);
|
||||
static std::ofstream license_file;
|
||||
static std::ofstream renewal_file;
|
||||
static std::ofstream provision_file;
|
||||
};
|
||||
} // namespace wvcdm
|
||||
|
||||
#endif // WVCDM_CORE_TEST_MESSAGE_DUMPER_H_
|
||||
@@ -205,6 +205,7 @@ TestCryptoSession::TestCryptoSession(metrics::CryptoMetrics* crypto_metrics,
|
||||
|
||||
void TestCryptoSession::MaybeInstallTestKeybox() {
|
||||
if (IsTestKeyboxNeeded()) {
|
||||
CryptoSession::SetAllowTestKeybox(true);
|
||||
ReinitializeForTest();
|
||||
WvCdmTestBase::InstallTestRootOfTrust();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user