Merge changes I55b1eb04,I839db69a,I43e845b8,I56b6d301,Ia59bfacf, ... into main

* changes:
  Unit tests for forbidden RSA key usage
  Add DRM reprovisioning request generation
  Correct copyright header
  Fix bcc length for printing
  Update ODK version to 18.4
  Adjust skipping tests when provisioning skipped
  Change test storage to use protobuf
  Remove WvCdmEnginePreProvTestStaging
  Rename and clarify Drm Reprovisioning token types
This commit is contained in:
Rahul Frias
2024-03-26 06:09:26 +00:00
committed by Android (Google) Code Review
22 changed files with 67 additions and 258 deletions

View File

@@ -607,7 +607,9 @@ enum CdmClientTokenType : int32_t {
kClientTokenOemCert,
kClientTokenUninitialized,
kClientTokenBootCertChain,
kClientTokenDrmReprovisioning,
// For use by internal L3 CDMs supporting individualization of embedded
// drm certificates.
kClientTokenDrmCertificateReprovisioning,
};
// kNonSecureUsageSupport - TEE does not provide any support for usage

View File

@@ -754,7 +754,7 @@ CdmResponseType CdmEngine::QueryStatus(RequestedSecurityLevel security_level,
}
switch (token_type) {
case kClientTokenDrmCert:
case kClientTokenDrmReprovisioning:
case kClientTokenDrmCertificateReprovisioning:
*query_response = QUERY_VALUE_DRM_CERTIFICATE;
break;
case kClientTokenKeybox:

View File

@@ -169,7 +169,7 @@ CertificateProvisioning::GetProvisioningType() {
return SignedProvisioningMessage::PROVISIONING_40;
case kClientTokenOemCert:
return SignedProvisioningMessage::PROVISIONING_30;
case kClientTokenDrmReprovisioning:
case kClientTokenDrmCertificateReprovisioning:
return SignedProvisioningMessage::DRM_REPROVISIONING;
default:
return SignedProvisioningMessage::PROVISIONING_20;

View File

@@ -404,7 +404,7 @@ bool ClientIdentification::GetProvisioningTokenType(
}
return true;
}
case kClientTokenDrmReprovisioning:
case kClientTokenDrmCertificateReprovisioning:
*token_type =
video_widevine::ClientIdentification::DRM_DEVICE_CERTIFICATE;
return true;

View File

@@ -348,7 +348,7 @@ CdmResponseType CryptoSession::GetProvisioningMethod(
type = kClientTokenBootCertChain;
break;
case OEMCrypto_DrmReprovisioning:
type = kClientTokenDrmReprovisioning;
type = kClientTokenDrmCertificateReprovisioning;
break;
case OEMCrypto_ProvisioningError:
default:
@@ -666,7 +666,8 @@ CdmResponseType CryptoSession::GetProvisioningToken(
} else if (pre_provision_token_type_ == kClientTokenBootCertChain) {
status = GetBootCertificateChain(requested_security_level, token,
additional_token);
} else if (pre_provision_token_type_ == kClientTokenDrmReprovisioning) {
} else if (pre_provision_token_type_ ==
kClientTokenDrmCertificateReprovisioning) {
status = GetTokenFromEmbeddedCertificate(token);
}
metrics_->crypto_session_get_token_.Increment(status);
@@ -1275,7 +1276,8 @@ CdmResponseType CryptoSession::PrepareAndSignProvisioningRequest(
should_specify_algorithm = true;
// Do nothing here. The key to signing the provisioning 4.0 request for each
// stage has been loaded already when it was generated by OEMCrypto.
} else if (pre_provision_token_type_ == kClientTokenDrmReprovisioning) {
} else if (pre_provision_token_type_ ==
kClientTokenDrmCertificateReprovisioning) {
should_specify_algorithm = false;
// Do nothing here. The baked-in certificate used as the token has already
// been loaded when the EncryptedClientId was filled in.
@@ -1462,7 +1464,7 @@ CdmResponseType CryptoSession::GetTokenFromEmbeddedCertificate(
LOGE("Failed to get token type");
return sts;
}
if (token_type != kClientTokenDrmReprovisioning) {
if (token_type != kClientTokenDrmCertificateReprovisioning) {
token->clear();
return CdmResponseType(NO_ERROR);
}

View File

@@ -20,6 +20,10 @@ message NameValue {
optional string value = 2;
}
message SavedStorage {
map<string, string> files = 1;
}
message OemCertificate {
enum PrivateKeyType {
RSA = 0;

View File

@@ -1026,7 +1026,8 @@ message SignedProvisioningMessage {
ARCPP_PROVISIONING = 4; // ChromeOS/Arc++ devices.
// Android-Attestation-based OTA keyboxes.
ANDROID_ATTESTATION_KEYBOX_OTA = 6;
// Certificate reprovisioning for internal L3 CDMs only.
// DRM certificate reprovisioning for individualization of embedded
// DRM certificates used by internal L3 CDMs only.
DRM_REPROVISIONING = 7;
INTEL_SIGMA_101 = 101; // Intel Sigma 1.0.1 protocol.
INTEL_SIGMA_210 = 210; // Intel Sigma 2.1.0 protocol.
@@ -1275,8 +1276,9 @@ message DrmCertificate {
DEVICE = 2;
SERVICE = 3;
PROVISIONER = 4;
// Only used by baked-in certificates with internal L3 CDMs that support
// Drm Reprovisioning.
// Only used by internal L3 CDMs with baked-in (embedded) certificates that
// support the Drm Reprovisioning method for individualization of embedded
// certificates.
DEVICE_EMBEDDED = 5;
}
enum ServiceType {

View File

@@ -60,7 +60,7 @@ bool SystemIdExtractor::ExtractSystemId(uint32_t* system_id) {
switch (type) {
case kClientTokenDrmCert:
// TODO: b/309675153 - Extract system id when using DRM reprovisioning.
case kClientTokenDrmReprovisioning:
case kClientTokenDrmCertificateReprovisioning:
LOGW(
"Cannot get a system ID from a DRM certificate, "
"using null system ID: security_level = %s",

View File

@@ -76,8 +76,8 @@ const char* CdmClientTokenTypeToString(CdmClientTokenType type) {
return "BootCertChain";
case kClientTokenUninitialized:
return "Uninitialized";
case kClientTokenDrmReprovisioning:
return "DrmReprovisioning";
case kClientTokenDrmCertificateReprovisioning:
return "DrmCertificateReprovisioning";
}
return UnknownValueRep(type);
}

View File

@@ -119,13 +119,6 @@ class WvCdmEnginePreProvTest : public WvCdmTestBaseWithEngine {
std::string session_id_;
};
class WvCdmEnginePreProvTestStaging : public WvCdmEnginePreProvTest {
public:
WvCdmEnginePreProvTestStaging() {
config_ = ConfigTestEnv(kContentProtectionStagingServer);
}
};
class WvCdmEnginePreProvTestProd : public WvCdmEnginePreProvTest {
public:
WvCdmEnginePreProvTestProd() {
@@ -342,8 +335,6 @@ TEST_F(WvCdmEngineTest, SetLicensingServiceInvalidCertificate) {
NO_ERROR);
};
TEST_F(WvCdmEnginePreProvTestStaging, ProvisioningTest) { EnsureProvisioned(); }
TEST_F(WvCdmEnginePreProvTestUatBinary, ProvisioningTest) {
EnsureProvisioned();
}

View File

@@ -482,7 +482,7 @@ TEST_P(CertificateProvisioningTest, ProvisioningResponseSuccess) {
INSTANTIATE_TEST_SUITE_P(
CertificateProvisioningTests, CertificateProvisioningTest,
testing::Values(kClientTokenKeybox, kClientTokenOemCert,
kClientTokenDrmReprovisioning),
kClientTokenDrmCertificateReprovisioning),
[](const testing::TestParamInfo<CertificateProvisioningTest::ParamType>&
param_type) {
return CdmClientTokenTypeToString(param_type.param);

View File

@@ -99,7 +99,7 @@ TEST_F(CryptoSessionMetricsTest, OpenSessionValidMetrics) {
} else if (token_type == kClientTokenBootCertChain) {
EXPECT_EQ(OEMCrypto_BootCertificateChain,
metrics_proto.oemcrypto_provisioning_method().int_value());
} else if (token_type == kClientTokenDrmReprovisioning) {
} else if (token_type == kClientTokenDrmCertificateReprovisioning) {
EXPECT_EQ(OEMCrypto_DrmReprovisioning,
metrics_proto.oemcrypto_provisioning_method().int_value());
} else {
@@ -143,7 +143,7 @@ TEST_F(CryptoSessionMetricsTest, GetProvisioningTokenValidMetrics) {
} else if (token_type == kClientTokenBootCertChain) {
EXPECT_EQ(OEMCrypto_BootCertificateChain,
metrics_proto.oemcrypto_provisioning_method().int_value());
} else if (token_type == kClientTokenDrmReprovisioning) {
} else if (token_type == kClientTokenDrmCertificateReprovisioning) {
EXPECT_EQ(OEMCrypto_DrmReprovisioning,
metrics_proto.oemcrypto_provisioning_method().int_value());
} else {

View File

@@ -128,6 +128,12 @@ class CdmDurationTest : public WvCdmTestBaseWithEngine,
// appended to it.
void SetUp() override {
WvCdmTestBase::SetUp();
// TODO: b/305093063 - Remove when Drm Reprovisioning server is implemented.
if (wvoec::global_features.provisioning_method ==
OEMCrypto_DrmReprovisioning) {
GTEST_SKIP()
<< "Skipping until Drm Reprovisioning server support is implemented.";
}
EnsureProvisioned();
license_holder_.set_can_persist(GetParam());
ASSERT_NO_FATAL_FAILURE(license_holder_.OpenSession());
@@ -152,6 +158,14 @@ class CdmDurationTest : public WvCdmTestBaseWithEngine,
}
void TearDown() override {
// TODO: b/305093063 - Remove when Drm Reprovisioning server is implemented.
if (wvoec::global_features.provisioning_method ==
OEMCrypto_DrmReprovisioning) {
// Since the session was not opened above. We can skip closing the session
// here too. This should be removed when EnsureProvisioning no longer
// skips this test.
return;
}
license_holder_.CloseSession();
// Log the time used in this test suite. When this comment was written,
// these tests took over three hours. If we want to improve that, we need to

View File

@@ -185,6 +185,7 @@ std::string ProvisioningHolder::DumpProvAttempt(const std::string& request,
if (result != OEMCrypto_SUCCESS) {
info << "--- ERROR GETTING BCC. result=" << result;
} else {
bcc.resize(bcc_length);
info << "BCC = (len=" << bcc_length << ") "
<< wvutil::unlimited_b2a_hex(bcc) << "\n"
<< "Additional Sig = (len=" << signature_length << ") "

View File

@@ -11,14 +11,14 @@
#include <sstream>
#include "create_test_file_system.h"
#include "device_files.pb.h"
#include "license_holder.h"
#include "log.h"
#include "test_sleep.h"
using wvutil::a2b_hex;
using video_widevine_client::sdk::SavedStorage;
using wvutil::FileSystem;
using wvutil::TestSleep;
using wvutil::unlimited_b2a_hex;
namespace wvcdm {
FileSystem* RebootTest::file_system_;
@@ -27,179 +27,8 @@ namespace {
// How much fudge or round off error do we allow in license durations for reboot
// tests.
constexpr int64_t kFudge = 10;
// We will encode a value string by wrapping it in braces, or as hex.
// If the string is not printable, or if it has unmatched braces, then we use
// hex. Otherwise, we surround the whole string with braces.
std::string EncodeString(const std::string& data) {
int braces_count = 0;
for (size_t i = 0; i < data.length(); i++) {
if (data[i] == '{') braces_count++;
if (data[i] == '}') braces_count--;
// If printable or whitespace (because '\n' is not printable?!?).
bool printable = isprint(data[i]) || isspace(data[i]);
// If there are any unprintable characters, except whitespace, or if we
// close a brace before we open it, then just use hex.
if (!printable || braces_count < 0) {
return "0x" + unlimited_b2a_hex(data) + ",";
}
}
// If we left any braces open, then use hex.
if (braces_count != 0) return "0x" + unlimited_b2a_hex(data) + ",";
return "{" + data + "},";
}
// Encode a map key for dumping. When we encode a map, we expect the keys to be
// like filenames, so we can separate them with colons and whitespace. If the
// key has these special characters, we will encode as hex.
std::string EncodeKey(const std::string& data) {
if (data.length() == 0) {
LOGE("Encoding empty string as key!");
return "EMPTY:";
}
// When decoding, we assume that a key starting with "0x" is in hex. So we
// can't have any keys that start with "0x".
if (data.substr(0, 2) == "0x") return "0x" + unlimited_b2a_hex(data) + ":";
// If the key is just is not printable, or if it has unmatched braces, then
// we use hex. Otherwise, we surround the whole string with braces.
for (size_t i = 0; i < data.length(); i++) {
if (!isprint(data[i]) || (data[i] == ':')) {
return "0x" + unlimited_b2a_hex(data) + ":";
}
}
return data + ":";
}
// In between keys and values, we will ignore whitespace. This allows a human to
// edit the persistent data a little bit without breaking anything.
void SkipSpace(const std::string& encoded, size_t* index) {
if (!index) return;
while (*index < encoded.length() && isspace(encoded[*index])) (*index)++;
}
// Decode a string that was encoded using EncodeString.
std::string DecodeString(const std::string& encoded, size_t* index) {
if (!index) return "";
SkipSpace(encoded, index);
if (*index + 2 >=
encoded.length()) { // Encoded string has at least 3 characters.
LOGE("Error decoding short string from %s at %zd", encoded.c_str(), *index);
*index = encoded.length();
return "";
}
if (encoded[*index] == '{') {
(*index)++;
size_t start = *index;
int braces_count = 1;
while (*index < encoded.length()) {
if (encoded[*index] == '{') braces_count++;
if (encoded[*index] == '}') braces_count--;
if (braces_count == 0) {
size_t end = *index;
(*index) += 2; // absorb the comma and the '}', too.
return encoded.substr(start, end - start);
}
(*index)++;
}
std::string tail = encoded.substr(start);
LOGE("Non-terminated brace %s at %zd: %s", encoded.c_str(), start,
tail.c_str());
*index = encoded.length();
return "";
}
if (encoded[*index] != '0' || encoded[*index + 1] != 'x') {
std::string tail = encoded.substr(*index);
LOGE("Hex should start with 0x in %s at %zd: %s", encoded.c_str(), *index,
tail.c_str());
*index = encoded.length();
return "";
}
*index += 2;
size_t start = *index;
while (*index < encoded.length()) {
if (encoded[*index] == ',') {
size_t end = *index;
std::vector<uint8_t> result = a2b_hex(encoded.substr(start, end - start));
(*index)++; // absorb the comma.
return std::string(result.begin(), result.end());
}
(*index)++;
}
std::string tail = encoded.substr(start);
LOGE("Bad encoding in %s at %zd: %s", encoded.c_str(), start, tail.c_str());
*index = encoded.length();
return "";
}
// Decode a string that was encoded with EncodeKey.
std::string DecodeKey(const std::string& encoded, size_t* index) {
if (!index) return "";
SkipSpace(encoded, index);
if (*index + 1 >= encoded.length()) {
LOGE("Error decoding key from %s at %zd", encoded.c_str(), *index);
*index = encoded.length();
return "";
}
// If it starts with 0x, then it is in hex.
if (encoded[*index] == '0' && encoded[*index + 1] == 'x') {
size_t start = *index + 2;
while (*index < encoded.length() && encoded[*index] != ':') (*index)++;
size_t end = *index;
std::vector<uint8_t> result = a2b_hex(encoded.substr(start, end - start));
(*index)++; // skip the colon.
return std::string(result.begin(), result.end());
}
size_t start = *index;
while (*index < encoded.length() && encoded[*index] != ':') (*index)++;
size_t end = *index;
(*index)++; // skip the colon.
return encoded.substr(start, end - start);
}
} // namespace
std::string RebootTest::DumpData(
const std::map<std::string, std::string>& data) {
std::ostringstream output;
output << "{\n";
for (const auto& entry : data) {
output << " " << EncodeKey(entry.first) << " "
<< EncodeString(entry.second) + "\n";
}
output << "}\n";
return output.str();
}
bool RebootTest::ParseDump(const std::string& dump,
std::map<std::string, std::string>* data) {
size_t index = 0;
SkipSpace(dump, &index);
if (index >= dump.length()) return false;
if (dump[index] != '{') {
LOGE("Dump does not start with '{'");
return false;
}
index++; // absorb '{'
while (true) {
SkipSpace(dump, &index);
if (index >= dump.length()) return false;
if (dump[index] == '}') {
index++; // absorb '}'
SkipSpace(dump, &index);
if (index != dump.length()) {
std::string tail = dump.substr(index);
LOGE("Trailing data in dump. %s at %zd: %s", dump.c_str(), index,
tail.c_str());
return false;
}
return true;
}
std::string tail = dump.substr(index);
std::string key = DecodeKey(dump, &index);
std::string value = DecodeString(dump, &index);
(*data)[key] = value;
}
}
void RebootTest::SetUp() {
WvCdmTestBase::SetUp();
if (!file_system_) file_system_ = CreateTestFileSystem();
@@ -221,7 +50,10 @@ void RebootTest::SetUp() {
std::string dump(file_size, ' ');
ssize_t read = file->Read(&dump[0], dump.size());
EXPECT_EQ(read, file_size) << "Error reading persistent data file.";
EXPECT_TRUE(ParseDump(dump, &persistent_data_));
SavedStorage proto;
EXPECT_TRUE(proto.ParseFromString(dump));
persistent_data_.insert(proto.files().begin(), proto.files().end());
}
TestSleep::SyncFakeClock();
}
@@ -231,7 +63,13 @@ void RebootTest::TearDown() {
auto file = file_system_->Open(persistent_data_filename_,
FileSystem::kCreate | FileSystem::kTruncate);
ASSERT_TRUE(file) << "Failed to open file: " << persistent_data_filename_;
std::string dump = DumpData(persistent_data_);
SavedStorage proto;
proto.mutable_files()->insert(persistent_data_.begin(),
persistent_data_.end());
std::string dump;
ASSERT_TRUE(proto.SerializeToString(&dump));
const ssize_t bytes_written = file->Write(dump.data(), dump.length());
EXPECT_EQ(bytes_written, static_cast<ssize_t>(dump.length()));
WvCdmTestBase::TearDown();
@@ -254,41 +92,6 @@ void RebootTest::SaveTime(const std::string& key, int64_t time) {
persistent_data_[key] = std::to_string(time);
}
/** Test the dump and restore functions above. This does not test CDM
functionality. */
TEST_F(RebootTest, TestDumpUtil) {
// Check that an empty map can be saved.
std::map<std::string, std::string> map1;
const std::string dump = DumpData(map1);
std::map<std::string, std::string> map2;
EXPECT_TRUE(ParseDump(dump, &map2));
EXPECT_EQ(map1, map2);
// Now fill it with some data and try again.
map1["key1"] = "this is a string. ";
map1["key2"] = "mismatch } {";
map1["key3"] = "mismatch } ";
map1["key4"] = "mismatch {";
map1["key5"] = "this: { has { matched } } braces { /.,)(**&^$&^% }";
map1["key6"] = "";
map1["00 whitespace in key 00"] = "value is ok";
// This key looks like it might be hex. It should show up as hex in the
// save file.
map1["0x_bad_key_00"] = "value is ok";
std::string big_string = "start with something {binary";
// Double big_string 8 times, i.e. times 256, so it's bigger than 2k:
for (int i = 0; i < 8; i++) big_string = big_string + big_string;
map1["big_file"] = big_string;
const std::string dump2 = DumpData(map1);
std::map<std::string, std::string> map3;
EXPECT_TRUE(ParseDump(dump2, &map3));
EXPECT_EQ(map1, map3);
if (test_pass() == 0) {
persistent_data_ = map1;
} else {
EXPECT_EQ(persistent_data_, map1);
}
}
/** Verify that the file system stores files from one test pass to the next. */
TEST_F(RebootTest, FilesArePersistent) {
const std::string key = "saved_value";

View File

@@ -22,17 +22,6 @@ class RebootTest : public WvCdmTestBaseWithEngine {
static void set_file_system(wvutil::FileSystem* file_system) {
file_system_ = file_system;
}
// Dump a map to a std string in an almost human readable way so that the map
// can be rebuilt using ParseDump below. The keys in the map must be standard
// identifier strings, which means no special characters or whitespace. By
// "almost human readable", we mean that a human debugging the dump will be
// able to find the keys, and see the values if they are printable or see a
// hex dump of the values if they are not.
static std::string DumpData(const std::map<std::string, std::string>& data);
// Parse a dump generated by DumpData and recreate the original data map.
// Returns true on success.
static bool ParseDump(const std::string& dump,
std::map<std::string, std::string>* data);
static int test_pass() { return default_config_->test_pass(); }

View File

@@ -26,9 +26,9 @@ struct CoreMessageFeatures {
// This is the published version of the ODK Core Message library. The default
// behavior is for the server to restrict messages to at most this version
// number. The default is 18.4.
uint32_t maximum_major_version = 18;
uint32_t maximum_minor_version = 4;
// number. The default is 19.0.
uint32_t maximum_major_version = 19;
uint32_t maximum_minor_version = 0;
bool operator==(const CoreMessageFeatures &other) const;
bool operator!=(const CoreMessageFeatures &other) const {

View File

@@ -19,7 +19,7 @@ extern "C" {
#define ODK_MINOR_VERSION 0
/* ODK Version string. Date changed automatically on each release. */
#define ODK_RELEASE_DATE "ODK v19.0 2024-02-10"
#define ODK_RELEASE_DATE "ODK v19.0 2024-02-23"
/* The lowest version number for an ODK message. */
#define ODK_FIRST_VERSION 16

View File

@@ -1,4 +1,4 @@
// Copyright 2019 Google LLC. All rights reserved. This file and proprietary
// Copyright 2019 Google LLC. This file and proprietary
// source code may only be used and distributed under the Widevine
// License Agreement.

View File

@@ -1292,7 +1292,8 @@ std::vector<VersionParameters> TestCases() {
{0, 16, 5, 16, 5},
{0, 17, 1, 17, 1},
{0, 17, 2, 17, 2},
{0, 18, 4, 18, 4}, // Change to 19 when the default version is updated.
{0, 18, 3, 18, 3}, // Change to 19 when the default version is updated.
{0, 18, 4, 18, 4},
};
return test_cases;
}

View File

@@ -151,6 +151,7 @@ void DeviceFeatures::PickDerivedKey() {
derive_key_method = TEST_PROVISION_30;
return;
case OEMCrypto_DrmCertificate:
case OEMCrypto_DrmReprovisioning:
if (OEMCrypto_ERROR_NOT_IMPLEMENTED != OEMCrypto_LoadTestRSAKey()) {
derive_key_method = LOAD_TEST_RSA_KEY;
}
@@ -237,6 +238,8 @@ const char* ProvisioningMethodName(OEMCrypto_ProvisioningMethod method) {
return "OEMCrypto_OEMCertificate";
case OEMCrypto_BootCertificateChain:
return "OEMCrypto_BootCertificateChain";
case OEMCrypto_DrmReprovisioning:
return "OEMCrypto_DrmReprovisioning";
}
// Not reachable
return "";

View File

@@ -11,7 +11,7 @@ using ::testing::Range;
namespace wvoec {
/// @addtogroup generic
/// @addtogroup cast
/// @{
/** If a device can load a private key with the alternate padding schemes, it
@@ -82,10 +82,7 @@ TEST_F(OEMCryptoLoadsCertificateAlternates, ForbidPrepAndSign) {
OEMCryptoResult result = OEMCrypto_PrepAndSignLicenseRequest(
s.session_id(), message.data(), message.size(), &core_message_length,
signature.data(), &signature_length);
// TODO: remove OEMCrypto_ERROR_INVALID_RSA_KEY once OEMCrypto v16 is not
// supported anymore. This error code has been deprecated since v17.
ASSERT_TRUE(result == OEMCrypto_ERROR_INVALID_KEY ||
result == OEMCrypto_ERROR_INVALID_RSA_KEY);
ASSERT_EQ(OEMCrypto_ERROR_INVALID_KEY, result);
const vector<uint8_t> zero(signature.size(), 0);
ASSERT_EQ(signature, zero); // Signature should not have been computed.
}