Refactor provisioning tests
Merge from Widevine repo of http://go/wvgerrit/56522 This CL moves provisioning from core/test/cdm_engine_test.cpp to test_base.cpp because other tests should also only be run when the device has been provisioned. It also adds a fake license server. The license holder helps a test create a license request and then generates a bare-bones license, without actually sending anything to a real license server. Test: more unit tests pass than before. Bug: 72354901 Fix Generic Crypto tests. Change-Id: Iec067a6a1fb91fa8fd7b904fdf36e90981e293a3
This commit is contained in:
@@ -31,97 +31,23 @@
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
using drm_metrics::WvCdmMetrics;
|
||||
using drm_metrics::DistributionMetric;
|
||||
using drm_metrics::WvCdmMetrics;
|
||||
|
||||
namespace {
|
||||
|
||||
// Http OK response code.
|
||||
const int kHttpOk = 200;
|
||||
|
||||
// TODO(fredgc): Move these to test_base.cpp in next CL.
|
||||
// Default license server, can be configured using --server command line option
|
||||
// Default key id (pssh), can be configured using --keyid command line option
|
||||
std::string g_client_auth;
|
||||
KeyId g_key_id_pssh;
|
||||
KeyId g_key_id_unwrapped;
|
||||
CdmKeySystem g_key_system;
|
||||
std::string g_license_server;
|
||||
std::string g_provisioning_server;
|
||||
std::string g_provisioning_service_certificate;
|
||||
std::string g_license_service_certificate;
|
||||
|
||||
const std::string kCencMimeType = "video/mp4";
|
||||
const std::string kWebmMimeType = "video/webm";
|
||||
|
||||
static void CommonSetup(ServerConfigurationId which,
|
||||
bool bin_prov = false) {
|
||||
|
||||
Properties::set_provisioning_messages_are_binary(bin_prov);
|
||||
Properties::Init();
|
||||
|
||||
// NOTE: Select configuration
|
||||
ConfigTestEnv config(which);
|
||||
|
||||
g_client_auth.assign(config.client_auth());
|
||||
g_key_system.assign(config.key_system());
|
||||
g_license_server.assign(config.license_server());
|
||||
g_key_id_pssh.assign(a2bs_hex(config.key_id()));
|
||||
g_provisioning_service_certificate.assign(
|
||||
config.provisioning_service_certificate());
|
||||
g_license_service_certificate.assign(config.license_service_certificate());
|
||||
g_provisioning_server.assign(config.provisioning_server());
|
||||
|
||||
// Extract the key ID from the PSSH box.
|
||||
InitializationData extractor(CENC_INIT_DATA_FORMAT, g_key_id_pssh);
|
||||
g_key_id_unwrapped = extractor.data();
|
||||
}
|
||||
|
||||
/*
|
||||
* Locate the portion of the server's response message that is between
|
||||
* the strings jason_start_substr and json_end_substr. Returns the string
|
||||
* through *result. If the start substring match fails, assume the entire
|
||||
* string represents a serialized protobuf mesaage and return true with
|
||||
* the entire string. If the end_substring match fails, return false with
|
||||
* an empty *result.
|
||||
*/
|
||||
bool ExtractSignedMessage(const std::string& response,
|
||||
const std::string& json_start_substr,
|
||||
const std::string& json_end_substr,
|
||||
std::string* result) {
|
||||
std::string response_string;
|
||||
size_t start = response.find(json_start_substr);
|
||||
|
||||
if (start == response.npos) {
|
||||
// Assume serialized protobuf message.
|
||||
result->assign(response);
|
||||
} else {
|
||||
// Assume JSON-wrapped protobuf.
|
||||
size_t end = response.find(json_end_substr,
|
||||
start + json_start_substr.length());
|
||||
if (end == response.npos) {
|
||||
LOGE("ExtractSignedMessage cannot locate end substring");
|
||||
result->clear();
|
||||
return false;
|
||||
}
|
||||
size_t result_string_size = end - start - json_start_substr.length();
|
||||
result->assign(response, start + json_start_substr.length(),
|
||||
result_string_size);
|
||||
}
|
||||
|
||||
if (result->empty()) {
|
||||
LOGE("ExtractSignedMessage: Response message is empty");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class WvCdmEnginePreProvTest : public WvCdmTestBase {
|
||||
public:
|
||||
WvCdmEnginePreProvTest() : cdm_engine_(&file_system_),
|
||||
session_opened_(false) {}
|
||||
WvCdmEnginePreProvTest()
|
||||
: cdm_engine_(&file_system_), session_opened_(false) {}
|
||||
|
||||
virtual ~WvCdmEnginePreProvTest() {}
|
||||
|
||||
@@ -132,10 +58,11 @@ class WvCdmEnginePreProvTest : public WvCdmTestBase {
|
||||
|
||||
virtual void OpenSession() {
|
||||
CdmResponseType status =
|
||||
cdm_engine_.OpenSession(g_key_system, NULL, NULL, &session_id_);
|
||||
cdm_engine_.OpenSession(config_.key_system(), NULL, NULL, &session_id_);
|
||||
if (status == NEED_PROVISIONING) {
|
||||
Provision();
|
||||
status = cdm_engine_.OpenSession(g_key_system, NULL, NULL, &session_id_);
|
||||
status = cdm_engine_.OpenSession(config_.key_system(), NULL, NULL,
|
||||
&session_id_);
|
||||
}
|
||||
ASSERT_EQ(status, NO_ERROR);
|
||||
ASSERT_NE("", session_id_) << "Could not open CDM session.";
|
||||
@@ -157,13 +84,12 @@ class WvCdmEnginePreProvTest : public WvCdmTestBase {
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
// Trade request for response via the license server.
|
||||
virtual bool LicenseServerRequestResponse(const std::string& request,
|
||||
std::string* response) {
|
||||
LOGV("LicenseServerRequestResponse: server url: %s",
|
||||
g_license_server.c_str());
|
||||
UrlRequest url_request(g_license_server + g_client_auth);
|
||||
config_.license_server().c_str());
|
||||
UrlRequest url_request(config_.license_server() + config_.client_auth());
|
||||
url_request.PostRequest(request);
|
||||
|
||||
std::string http_response;
|
||||
@@ -178,55 +104,12 @@ class WvCdmEnginePreProvTest : public WvCdmTestBase {
|
||||
license_request.GetDrmMessage(http_response, *response);
|
||||
|
||||
LOGV("response: size=%u, string:\n%s\n", response->size(),
|
||||
Base64SafeEncode(std::vector<uint8_t>(response->begin(),
|
||||
response->end())).c_str());
|
||||
Base64SafeEncode(
|
||||
std::vector<uint8_t>(response->begin(), response->end()))
|
||||
.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void Provision() {
|
||||
LOGV("WvCdmEnginePreProvTest::Provision: url=%s",
|
||||
g_provisioning_server.c_str());
|
||||
CdmProvisioningRequest prov_request;
|
||||
std::string provisioning_server_url;
|
||||
CdmCertificateType cert_type = kCertificateWidevine;
|
||||
std::string cert_authority;
|
||||
std::string cert, wrapped_key;
|
||||
|
||||
CdmResponseType result = NO_ERROR;
|
||||
for(int i = 0; i < 2; ++i) { // Retry once if there is a nonce problem.
|
||||
result = cdm_engine_.GetProvisioningRequest(
|
||||
cert_type, cert_authority, g_provisioning_service_certificate,
|
||||
&prov_request, &provisioning_server_url);
|
||||
if (result == CERT_PROVISIONING_NONCE_GENERATION_ERROR) {
|
||||
LOGW("Woops. Nonce problem. Try again?");
|
||||
sleep(1);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
ASSERT_EQ(NO_ERROR, result);
|
||||
|
||||
LOGV("WvCdmEnginePreProvTest::Provision: req=%s", prov_request.c_str());
|
||||
|
||||
// Ignore URL provided by CdmEngine. Use ours, as configured
|
||||
// for test vs. production server.
|
||||
provisioning_server_url.assign(g_provisioning_server);
|
||||
UrlRequest url_request(provisioning_server_url);
|
||||
EXPECT_TRUE(url_request.is_connected());
|
||||
url_request.PostCertRequestInQueryString(prov_request);
|
||||
std::string http_message;
|
||||
bool ok = url_request.GetResponse(&http_message);
|
||||
EXPECT_TRUE(ok) << http_message;
|
||||
|
||||
LOGV("WvCdmEnginePreProvTest::Provision: http_message: \n%s\n",
|
||||
http_message.c_str());
|
||||
|
||||
ASSERT_EQ(NO_ERROR,
|
||||
cdm_engine_.HandleProvisioningResponse(http_message,
|
||||
&cert, &wrapped_key))
|
||||
<< "message = " << http_message;
|
||||
}
|
||||
|
||||
FileSystem file_system_;
|
||||
CdmEngine cdm_engine_;
|
||||
bool session_opened_;
|
||||
@@ -236,125 +119,44 @@ class WvCdmEnginePreProvTest : public WvCdmTestBase {
|
||||
|
||||
class WvCdmEnginePreProvTestStaging : public WvCdmEnginePreProvTest {
|
||||
public:
|
||||
WvCdmEnginePreProvTestStaging() {}
|
||||
|
||||
virtual ~WvCdmEnginePreProvTestStaging() {}
|
||||
|
||||
static void SetUpTestCase() {
|
||||
// NOTE: Select server configuration
|
||||
CommonSetup(kContentProtectionStagingServer);
|
||||
WvCdmEnginePreProvTestStaging() {
|
||||
config_ = ConfigTestEnv(kContentProtectionStagingServer);
|
||||
}
|
||||
};
|
||||
|
||||
class WvCdmEnginePreProvTestProd : public WvCdmEnginePreProvTest {
|
||||
public:
|
||||
WvCdmEnginePreProvTestProd() {}
|
||||
|
||||
virtual ~WvCdmEnginePreProvTestProd() {}
|
||||
|
||||
static void SetUpTestCase() {
|
||||
// NOTE: Select server configuration
|
||||
CommonSetup(kContentProtectionProductionServer);
|
||||
WvCdmEnginePreProvTestProd() {
|
||||
config_ = ConfigTestEnv(kContentProtectionProductionServer);
|
||||
}
|
||||
};
|
||||
|
||||
class WvCdmEnginePreProvTestUat : public WvCdmEnginePreProvTest {
|
||||
public:
|
||||
WvCdmEnginePreProvTestUat() {}
|
||||
|
||||
virtual ~WvCdmEnginePreProvTestUat() {}
|
||||
|
||||
static void SetUpTestCase() {
|
||||
// NOTE: Select server configuration
|
||||
CommonSetup(kContentProtectionUatServer);
|
||||
WvCdmEnginePreProvTestUat() {
|
||||
config_ = ConfigTestEnv(kContentProtectionUatServer);
|
||||
}
|
||||
};
|
||||
|
||||
class WvCdmEnginePreProvTestUatBinary : public WvCdmEnginePreProvTest {
|
||||
public:
|
||||
WvCdmEnginePreProvTestUatBinary() {}
|
||||
|
||||
virtual ~WvCdmEnginePreProvTestUatBinary() {}
|
||||
|
||||
static void SetUpTestCase() {
|
||||
// NOTE: Select server configuration
|
||||
WvCdmEnginePreProvTestUatBinary() {
|
||||
config_ = ConfigTestEnv(kContentProtectionUatServer);
|
||||
// Override default setting of provisioning_messages_are_binary property
|
||||
CommonSetup(kContentProtectionUatServer, true);
|
||||
binary_provisioning_ = true;
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
virtual void Provision() {
|
||||
LOGV("WvCdmEnginePreProvTestProv30Binary::Provision: url=%s",
|
||||
g_provisioning_server.c_str());
|
||||
CdmProvisioningRequest binary_prov_request;
|
||||
std::string provisioning_server_url;
|
||||
CdmCertificateType cert_type = kCertificateWidevine;
|
||||
std::string cert_authority;
|
||||
std::string cert, wrapped_key;
|
||||
ASSERT_EQ(NO_ERROR, cdm_engine_.GetProvisioningRequest(
|
||||
cert_type, cert_authority, g_provisioning_service_certificate,
|
||||
&binary_prov_request, &provisioning_server_url));
|
||||
|
||||
// prov_request is binary - base64 encode it
|
||||
std::string prov_request(Base64SafeEncodeNoPad(
|
||||
std::vector<uint8_t>(binary_prov_request.begin(),
|
||||
binary_prov_request.end())));
|
||||
|
||||
LOGV("WvCdmEnginePreProvTest::Provision: req=%s", prov_request.c_str());
|
||||
|
||||
// Ignore URL provided by CdmEngine. Use ours, as configured
|
||||
// for test vs. production server.
|
||||
provisioning_server_url.assign(g_provisioning_server);
|
||||
UrlRequest url_request(provisioning_server_url);
|
||||
EXPECT_TRUE(url_request.is_connected());
|
||||
url_request.PostCertRequestInQueryString(prov_request);
|
||||
std::string http_message;
|
||||
bool ok = url_request.GetResponse(&http_message);
|
||||
EXPECT_TRUE(ok);
|
||||
|
||||
LOGV("WvCdmEnginePreProvTest::Provision: http_message: \n%s\n",
|
||||
http_message.c_str());
|
||||
|
||||
// extract provisioning response from received message
|
||||
// Extracts signed response from JSON string, result is serialized protobuf.
|
||||
const std::string kMessageStart = "\"signedResponse\": \"";
|
||||
const std::string kMessageEnd = "\"";
|
||||
std::string protobuf_response;
|
||||
EXPECT_TRUE (ExtractSignedMessage(http_message, kMessageStart, kMessageEnd,
|
||||
&protobuf_response)) <<
|
||||
"Failed to extract signed serialized response from JSON response";
|
||||
|
||||
LOGV("WvCdmEnginePreProvTest::Provision: extracted response "
|
||||
"message: \n%s\n", protobuf_response.c_str());
|
||||
|
||||
// base64 decode response to yield binary protobuf
|
||||
std::vector<uint8_t> response_vec(Base64SafeDecode(
|
||||
std::string(protobuf_response.begin(), protobuf_response.end())));
|
||||
std::string binary_protobuf_response(response_vec.begin(),
|
||||
response_vec.end());
|
||||
ASSERT_EQ(NO_ERROR,
|
||||
cdm_engine_.HandleProvisioningResponse(binary_protobuf_response,
|
||||
&cert, &wrapped_key));
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
class WvCdmEngineTest : public WvCdmEnginePreProvTest {
|
||||
public:
|
||||
WvCdmEngineTest() {}
|
||||
|
||||
static void SetUpTestCase() {
|
||||
// NOTE: Select server configuration
|
||||
CommonSetup(kContentProtectionUatServer);
|
||||
}
|
||||
|
||||
virtual void SetUp() {
|
||||
CdmResponseType status =
|
||||
cdm_engine_.OpenSession(g_key_system, NULL, NULL, &session_id_);
|
||||
cdm_engine_.OpenSession(config_.key_system(), NULL, NULL, &session_id_);
|
||||
if (status == NEED_PROVISIONING) {
|
||||
Provision();
|
||||
status = cdm_engine_.OpenSession(g_key_system, NULL, NULL, &session_id_);
|
||||
status = cdm_engine_.OpenSession(config_.key_system(), NULL, NULL, &session_id_);
|
||||
}
|
||||
ASSERT_EQ(NO_ERROR, status);
|
||||
ASSERT_NE("", session_id_) << "Could not open CDM session.";
|
||||
@@ -362,7 +164,6 @@ class WvCdmEngineTest : public WvCdmEnginePreProvTest {
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
void GenerateKeyRequest(const std::string& key_id,
|
||||
const std::string& init_data_type_string) {
|
||||
CdmAppParameterMap app_parameters;
|
||||
@@ -421,8 +222,8 @@ class WvCdmEngineTest : public WvCdmEnginePreProvTest {
|
||||
EXPECT_TRUE(ok);
|
||||
|
||||
int status_code = url_request.GetStatusCode(response);
|
||||
if (expect_success) EXPECT_EQ(kHttpOk, status_code)
|
||||
<< "Error response: " << response;
|
||||
if (expect_success)
|
||||
EXPECT_EQ(kHttpOk, status_code) << "Error response: " << response;
|
||||
|
||||
if (status_code != kHttpOk) {
|
||||
return "";
|
||||
@@ -432,7 +233,8 @@ class WvCdmEngineTest : public WvCdmEnginePreProvTest {
|
||||
lic_request.GetDrmMessage(response, drm_msg);
|
||||
LOGV("drm msg: %u bytes\r\n%s", drm_msg.size(),
|
||||
HexEncode(reinterpret_cast<const uint8_t*>(drm_msg.data()),
|
||||
drm_msg.size()).c_str());
|
||||
drm_msg.size())
|
||||
.c_str());
|
||||
return drm_msg;
|
||||
}
|
||||
}
|
||||
@@ -441,8 +243,7 @@ class WvCdmEngineTest : public WvCdmEnginePreProvTest {
|
||||
const std::string& client_auth) {
|
||||
std::string resp = GetKeyRequestResponse(server_url, client_auth);
|
||||
CdmKeySetId key_set_id;
|
||||
EXPECT_EQ(KEY_ADDED,
|
||||
cdm_engine_.AddKey(session_id_, resp, &key_set_id));
|
||||
EXPECT_EQ(KEY_ADDED, cdm_engine_.AddKey(session_id_, resp, &key_set_id));
|
||||
VerifyLicenseRequestLatency(kKeyRequestTypeInitial,
|
||||
*cdm_engine_.GetMetrics());
|
||||
}
|
||||
@@ -469,8 +270,9 @@ class WvCdmEngineTest : public WvCdmEnginePreProvTest {
|
||||
j++) {
|
||||
DistributionMetric latency_distribution =
|
||||
session_metrics.cdm_session_license_request_latency_ms(j);
|
||||
if (latency_distribution.attributes().key_request_type()
|
||||
== key_request_type && latency_distribution.operation_count() > 0) {
|
||||
if (latency_distribution.attributes().key_request_type() ==
|
||||
key_request_type &&
|
||||
latency_distribution.operation_count() > 0) {
|
||||
has_request_type = true;
|
||||
}
|
||||
}
|
||||
@@ -487,88 +289,88 @@ class WvCdmEngineTest : public WvCdmEnginePreProvTest {
|
||||
|
||||
// Tests to validate service certificate
|
||||
TEST_F(WvCdmEnginePreProvTestUat, ProvisioningServiceCertificateValidTest) {
|
||||
ASSERT_EQ(
|
||||
cdm_engine_.ValidateServiceCertificate(
|
||||
g_provisioning_service_certificate),
|
||||
NO_ERROR);
|
||||
ASSERT_EQ(cdm_engine_.ValidateServiceCertificate(
|
||||
config_.provisioning_service_certificate()),
|
||||
NO_ERROR);
|
||||
};
|
||||
|
||||
TEST_F(WvCdmEnginePreProvTestUat, ProvisioningServiceCertificateInvalidTest) {
|
||||
std::string certificate = g_provisioning_service_certificate;
|
||||
std::string certificate = config_.provisioning_service_certificate();
|
||||
// Add four nulls to the beginning of the cert to invalidate it
|
||||
certificate.insert(0, 4, '\0');
|
||||
|
||||
ASSERT_NE(cdm_engine_.ValidateServiceCertificate(certificate), NO_ERROR);
|
||||
};
|
||||
|
||||
// Test that provisioning works, even if device is already provisioned.
|
||||
TEST_F(WvCdmEnginePreProvTestStaging, DISABLED_ProvisioningTest) {
|
||||
uint32_t nonce = 0;
|
||||
uint8_t buffer[1];
|
||||
size_t size = 0;
|
||||
|
||||
int result = OEMCrypto_RewrapDeviceRSAKey(
|
||||
0, buffer, 0, buffer, 0, &nonce, buffer, 0, buffer, buffer, &size);
|
||||
int result30 = OEMCrypto_RewrapDeviceRSAKey30(
|
||||
0, &nonce, buffer, 0, buffer, 0, buffer, buffer, &size);
|
||||
int method = OEMCrypto_GetProvisioningMethod(kLevelDefault);
|
||||
|
||||
if (result == OEMCrypto_ERROR_NOT_IMPLEMENTED &&
|
||||
result30 == OEMCrypto_ERROR_NOT_IMPLEMENTED) {
|
||||
LOGW("WARNING: Skipping ProvisioningTest because the device does not "
|
||||
"support provisioning. If you are using a baked-in certificate, "
|
||||
"this is expected. Otherwise, something is wrong.");
|
||||
ASSERT_EQ(method, OEMCrypto_DrmCertificate);
|
||||
} else {
|
||||
if (result == OEMCrypto_ERROR_NOT_IMPLEMENTED) {
|
||||
ASSERT_EQ(method, OEMCrypto_OEMCertificate);
|
||||
} else {
|
||||
ASSERT_EQ(method, OEMCrypto_Keybox);
|
||||
}
|
||||
}
|
||||
|
||||
Provision();
|
||||
}
|
||||
TEST_F(WvCdmEnginePreProvTestStaging, ProvisioningTest) { Provision(); }
|
||||
|
||||
// TODO(b/112046733): This test is broken. It should be fixed.
|
||||
TEST_F(WvCdmEnginePreProvTestUatBinary, DISABLED_ProvisioningTest) {
|
||||
Provision();
|
||||
}
|
||||
|
||||
// Test that provisioning works, even if device is already provisioned.
|
||||
TEST_F(WvCdmEngineTest, DISABLED_ProvisioningTest) {
|
||||
// Test that provisioning works.
|
||||
TEST_F(WvCdmEngineTest, ProvisioningTest) {
|
||||
Provision();
|
||||
}
|
||||
|
||||
// Test that provisioning works, even if device is already provisioned.
|
||||
TEST_F(WvCdmEngineTest, ReprovisioningTest) {
|
||||
// Provision once.
|
||||
Provision();
|
||||
// Verify that we can provision a second time, even though we already
|
||||
// provisioned once.
|
||||
Provision();
|
||||
}
|
||||
|
||||
TEST_F(WvCdmEngineTest, BaseIsoBmffMessageTest) {
|
||||
GenerateKeyRequest(g_key_id_pssh, kCencMimeType);
|
||||
GetKeyRequestResponse(g_license_server, g_client_auth);
|
||||
GenerateKeyRequest(binary_key_id(), kCencMimeType);
|
||||
GetKeyRequestResponse(config_.license_server(), config_.client_auth());
|
||||
}
|
||||
|
||||
// TODO(juce): Set up with correct test data.
|
||||
TEST_F(WvCdmEngineTest, DISABLED_BaseWebmMessageTest) {
|
||||
GenerateKeyRequest(g_key_id_unwrapped, kWebmMimeType);
|
||||
GetKeyRequestResponse(g_license_server, g_client_auth);
|
||||
// Extract the key ID from the PSSH box.
|
||||
InitializationData extractor(CENC_INIT_DATA_FORMAT, binary_key_id());
|
||||
KeyId key_id_unwrapped = extractor.data();
|
||||
GenerateKeyRequest(key_id_unwrapped, kWebmMimeType);
|
||||
GetKeyRequestResponse(config_.license_server(), config_.client_auth());
|
||||
}
|
||||
|
||||
TEST_F(WvCdmEngineTest, NormalDecryptionIsoBmff) {
|
||||
GenerateKeyRequest(g_key_id_pssh, kCencMimeType);
|
||||
VerifyNewKeyResponse(g_license_server, g_client_auth);
|
||||
GenerateKeyRequest(binary_key_id(), kCencMimeType);
|
||||
VerifyNewKeyResponse(config_.license_server(), config_.client_auth());
|
||||
}
|
||||
|
||||
// TODO(juce): Set up with correct test data.
|
||||
TEST_F(WvCdmEngineTest, DISABLED_NormalDecryptionWebm) {
|
||||
GenerateKeyRequest(g_key_id_unwrapped, kWebmMimeType);
|
||||
VerifyNewKeyResponse(g_license_server, g_client_auth);
|
||||
// Extract the key ID from the PSSH box.
|
||||
InitializationData extractor(CENC_INIT_DATA_FORMAT, binary_key_id());
|
||||
KeyId key_id_unwrapped = extractor.data();
|
||||
GenerateKeyRequest(key_id_unwrapped, kWebmMimeType);
|
||||
VerifyNewKeyResponse(config_.license_server(), config_.client_auth());
|
||||
}
|
||||
|
||||
TEST_F(WvCdmEngineTest, LoadKey) {
|
||||
EnsureProvisioned();
|
||||
TestLicenseHolder holder(&cdm_engine_);
|
||||
holder.OpenSession(config_.key_system());
|
||||
holder.GenerateKeyRequest(binary_key_id(), ISO_BMFF_VIDEO_MIME_TYPE);
|
||||
holder.CreateDefaultLicense();
|
||||
std::vector<uint8_t> key_data(KEY_SIZE, '1');
|
||||
wvoec::KeyControlBlock block = {};
|
||||
holder.AddKey("key_one", key_data, block);
|
||||
holder.SignAndLoadLicense();
|
||||
}
|
||||
|
||||
TEST_F(WvCdmEngineTest, LicenseRenewal) {
|
||||
GenerateKeyRequest(g_key_id_pssh, kCencMimeType);
|
||||
VerifyNewKeyResponse(g_license_server, g_client_auth);
|
||||
GenerateKeyRequest(binary_key_id(), kCencMimeType);
|
||||
VerifyNewKeyResponse(config_.license_server(), config_.client_auth());
|
||||
|
||||
GenerateRenewalRequest();
|
||||
VerifyRenewalKeyResponse(server_url_.empty() ? g_license_server : server_url_,
|
||||
g_client_auth);
|
||||
VerifyRenewalKeyResponse(
|
||||
server_url_.empty() ? config_.license_server() : server_url_,
|
||||
config_.client_auth());
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "cdm_engine.h"
|
||||
#include "crypto_session.h"
|
||||
#include "file_store.h"
|
||||
#include "license.h"
|
||||
#include "log.h"
|
||||
#include "oec_device_features.h"
|
||||
#include "oec_test_data.h"
|
||||
@@ -89,10 +90,92 @@ void show_menu(char* prog_name) {
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
/*
|
||||
* Locate the portion of the server's response message that is between
|
||||
* the strings jason_start_substr and json_end_substr. Returns the string
|
||||
* through *result. If the start substring match fails, assume the entire
|
||||
* string represents a serialized protobuf mesaage and return true with
|
||||
* the entire string. If the end_substring match fails, return false with
|
||||
* an empty *result.
|
||||
*/
|
||||
bool ExtractSignedMessage(const std::string& response,
|
||||
const std::string& json_start_substr,
|
||||
const std::string& json_end_substr,
|
||||
std::string* result) {
|
||||
std::string response_string;
|
||||
size_t start = response.find(json_start_substr);
|
||||
|
||||
if (start == response.npos) {
|
||||
// Assume serialized protobuf message.
|
||||
result->assign(response);
|
||||
} else {
|
||||
// Assume JSON-wrapped protobuf.
|
||||
size_t end =
|
||||
response.find(json_end_substr, start + json_start_substr.length());
|
||||
if (end == response.npos) {
|
||||
LOGE("ExtractSignedMessage cannot locate end substring");
|
||||
result->clear();
|
||||
return false;
|
||||
}
|
||||
size_t result_string_size = end - start - json_start_substr.length();
|
||||
result->assign(response, start + json_start_substr.length(),
|
||||
result_string_size);
|
||||
}
|
||||
|
||||
if (result->empty()) {
|
||||
LOGE("ExtractSignedMessage: Response message is empty");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ConfigTestEnv WvCdmTestBase::default_config_(kContentProtectionUatServer);
|
||||
|
||||
void WvCdmTestBase::StripeBuffer(std::vector<uint8_t>* buffer, size_t size,
|
||||
uint8_t init) {
|
||||
buffer->assign(size, 0);
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
(*buffer)[i] = init + i % 250;
|
||||
}
|
||||
}
|
||||
|
||||
std::string WvCdmTestBase::Aes128CbcEncrypt(std::vector<uint8_t> key,
|
||||
const std::vector<uint8_t>& clear,
|
||||
const std::vector<uint8_t> iv) {
|
||||
std::vector<uint8_t> encrypted(clear.size());
|
||||
std::vector<uint8_t> iv_mod(iv.begin(), iv.end());
|
||||
AES_KEY aes_key;
|
||||
AES_set_encrypt_key(&key[0], 128, &aes_key);
|
||||
AES_cbc_encrypt(&clear[0], &encrypted[0], clear.size(), &aes_key, &iv_mod[0],
|
||||
AES_ENCRYPT);
|
||||
return std::string(encrypted.begin(), encrypted.end());
|
||||
}
|
||||
|
||||
std::string WvCdmTestBase::Aes128CbcDecrypt(std::vector<uint8_t> key,
|
||||
const std::vector<uint8_t>& clear,
|
||||
const std::vector<uint8_t> iv) {
|
||||
std::vector<uint8_t> encrypted(clear.size());
|
||||
std::vector<uint8_t> iv_mod(iv.begin(), iv.end());
|
||||
AES_KEY aes_key;
|
||||
AES_set_decrypt_key(&key[0], 128, &aes_key);
|
||||
AES_cbc_encrypt(&clear[0], &encrypted[0], clear.size(), &aes_key, &iv_mod[0],
|
||||
AES_DECRYPT);
|
||||
return std::string(encrypted.begin(), encrypted.end());
|
||||
}
|
||||
|
||||
std::string WvCdmTestBase::SignHMAC(const std::string& message,
|
||||
const std::vector<uint8_t>& key) {
|
||||
uint8_t signature[SHA256_DIGEST_LENGTH];
|
||||
unsigned int md_len = SHA256_DIGEST_LENGTH;
|
||||
HMAC(EVP_sha256(), &key[0], key.size(),
|
||||
reinterpret_cast<const uint8_t*>(message.data()), message.size(),
|
||||
signature, &md_len);
|
||||
std::string result(signature, signature + SHA256_DIGEST_LENGTH);
|
||||
return result;
|
||||
}
|
||||
|
||||
TestCryptoSession::TestCryptoSession(metrics::CryptoMetrics* crypto_metrics)
|
||||
: CryptoSession(crypto_metrics) {
|
||||
// The first CryptoSession should have initialized OEMCrypto. This is right
|
||||
@@ -119,6 +202,7 @@ class TestCryptoSessionFactory : public CryptoSessionFactory {
|
||||
|
||||
void WvCdmTestBase::SetUp() {
|
||||
::testing::Test::SetUp();
|
||||
Properties::set_provisioning_messages_are_binary(binary_provisioning_);
|
||||
Properties::Init();
|
||||
const ::testing::TestInfo* const test_info =
|
||||
::testing::UnitTest::GetInstance()->current_test_info();
|
||||
@@ -147,6 +231,93 @@ void WvCdmTestBase::InstallTestRootOfTrust() {
|
||||
}
|
||||
}
|
||||
|
||||
void WvCdmTestBase::Provision() {
|
||||
CdmProvisioningRequest prov_request;
|
||||
CdmProvisioningRequest binary_prov_request;
|
||||
std::string provisioning_server_url;
|
||||
CdmCertificateType cert_type = kCertificateWidevine;
|
||||
std::string cert_authority;
|
||||
std::string cert, wrapped_key;
|
||||
|
||||
CdmSessionId session_id;
|
||||
FileSystem file_system;
|
||||
// TODO(fredgc): provision for different SPOIDs.
|
||||
CdmEngine cdm_engine(&file_system);
|
||||
|
||||
CdmResponseType result = cdm_engine.GetProvisioningRequest(
|
||||
cert_type, cert_authority, config_.provisioning_service_certificate(),
|
||||
&prov_request, &provisioning_server_url);
|
||||
ASSERT_EQ(NO_ERROR, result);
|
||||
|
||||
if (binary_provisioning_) {
|
||||
binary_prov_request = prov_request;
|
||||
prov_request = std::string(Base64SafeEncodeNoPad(std::vector<uint8_t>(
|
||||
binary_prov_request.begin(), binary_prov_request.end())));
|
||||
}
|
||||
|
||||
LOGV("WvCdmTestBase::Provision: req=%s", prov_request.c_str());
|
||||
|
||||
// Ignore URL provided by CdmEngine. Use ours, as configured
|
||||
// for test vs. production server.
|
||||
provisioning_server_url.assign(config_.provisioning_server());
|
||||
UrlRequest url_request(provisioning_server_url);
|
||||
EXPECT_TRUE(url_request.is_connected());
|
||||
url_request.PostCertRequestInQueryString(prov_request);
|
||||
std::string http_message;
|
||||
bool ok = url_request.GetResponse(&http_message);
|
||||
EXPECT_TRUE(ok) << http_message;
|
||||
|
||||
LOGV("WvCdmTestBase::Provision: http_message: \n%s\n", http_message.c_str());
|
||||
|
||||
if (binary_provisioning_) {
|
||||
// extract provisioning response from received message
|
||||
// Extracts signed response from JSON string, result is serialized protobuf.
|
||||
const std::string kMessageStart = "\"signedResponse\": \"";
|
||||
const std::string kMessageEnd = "\"";
|
||||
std::string protobuf_response;
|
||||
EXPECT_TRUE(ExtractSignedMessage(http_message, kMessageStart, kMessageEnd,
|
||||
&protobuf_response))
|
||||
<< "Failed to extract signed serialized response from JSON response";
|
||||
|
||||
LOGV("WvCdmEnginePreProvTest::Provision: extracted response message: \n"
|
||||
"%s\n", protobuf_response.c_str());
|
||||
|
||||
// base64 decode response to yield binary protobuf
|
||||
std::vector<uint8_t> response_vec(Base64SafeDecode(
|
||||
std::string(protobuf_response.begin(), protobuf_response.end())));
|
||||
std::string binary_protobuf_response(response_vec.begin(),
|
||||
response_vec.end());
|
||||
ASSERT_EQ(NO_ERROR, cdm_engine.HandleProvisioningResponse(
|
||||
binary_protobuf_response, &cert, &wrapped_key))
|
||||
<< "message = " << http_message;
|
||||
} else {
|
||||
ASSERT_EQ(NO_ERROR, cdm_engine.HandleProvisioningResponse(
|
||||
http_message, &cert, &wrapped_key))
|
||||
<< "message = " << http_message;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(fredgc): Replace this with a pre-defined DRM certificate. We could do
|
||||
// that because either the device is using a known test keybox with a known
|
||||
// device key, or the device is using an OEM certificate, and we can extract
|
||||
// that certificate from the provisioning request.
|
||||
void WvCdmTestBase::EnsureProvisioned() {
|
||||
CdmSessionId session_id;
|
||||
FileSystem file_system;
|
||||
CdmEngine cdm_engine(&file_system);
|
||||
CdmResponseType status =
|
||||
cdm_engine.OpenSession(config_.key_system(), NULL, NULL, &session_id);
|
||||
if (status == NEED_PROVISIONING) {
|
||||
Provision();
|
||||
status =
|
||||
cdm_engine.OpenSession(config_.key_system(), NULL, NULL, &session_id);
|
||||
}
|
||||
ASSERT_EQ(NO_ERROR, status);
|
||||
ASSERT_NE("", session_id) << "Could not open CDM session.";
|
||||
ASSERT_TRUE(cdm_engine.IsOpenSession(session_id));
|
||||
ASSERT_EQ(NO_ERROR, cdm_engine.CloseSession(session_id));
|
||||
}
|
||||
|
||||
bool WvCdmTestBase::Initialize(int argc, char **argv) {
|
||||
Properties::Init();
|
||||
bool is_cast_receiver = false;
|
||||
@@ -256,4 +427,239 @@ bool WvCdmTestBase::Initialize(int argc, char **argv) {
|
||||
return true;
|
||||
}
|
||||
|
||||
TestLicenseHolder::TestLicenseHolder(CdmEngine* cdm_engine)
|
||||
: cdm_engine_(cdm_engine),
|
||||
session_opened_(false),
|
||||
// Keys are initialized with simple values, and the correct size:
|
||||
derived_mac_key_server_(MAC_KEY_SIZE, 'a'),
|
||||
derived_mac_key_client_(MAC_KEY_SIZE, 'b'),
|
||||
mac_key_server_(MAC_KEY_SIZE, 'c'),
|
||||
mac_key_client_(MAC_KEY_SIZE, 'd'),
|
||||
enc_key_(KEY_SIZE, 'e'),
|
||||
session_key_(KEY_SIZE, 'f') {}
|
||||
|
||||
TestLicenseHolder::~TestLicenseHolder() {
|
||||
CloseSession();
|
||||
}
|
||||
|
||||
void TestLicenseHolder::OpenSession(const std::string& key_system) {
|
||||
CdmResponseType status =
|
||||
cdm_engine_->OpenSession(key_system, NULL, NULL, &session_id_);
|
||||
ASSERT_EQ(status, NO_ERROR);
|
||||
ASSERT_NE("", session_id_) << "Could not open CDM session.";
|
||||
ASSERT_TRUE(cdm_engine_->IsOpenSession(session_id_));
|
||||
session_opened_ = true;
|
||||
}
|
||||
|
||||
void TestLicenseHolder::CloseSession() {
|
||||
if (session_opened_) {
|
||||
cdm_engine_->CloseSession(session_id_);
|
||||
session_opened_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
void TestLicenseHolder::GenerateKeyRequest(
|
||||
const std::string& key_id, const std::string& init_data_type_string) {
|
||||
ASSERT_TRUE(session_opened_);
|
||||
CdmAppParameterMap app_parameters;
|
||||
CdmKeySetId key_set_id;
|
||||
InitializationData init_data(init_data_type_string, key_id);
|
||||
CdmKeyRequest key_request;
|
||||
CdmResponseType result = cdm_engine_->GenerateKeyRequest(
|
||||
session_id_, key_set_id, init_data, kLicenseTypeStreaming, app_parameters,
|
||||
&key_request);
|
||||
EXPECT_EQ(KEY_MESSAGE, result);
|
||||
signed_license_request_data_ = key_request.message;
|
||||
EXPECT_EQ(kKeyRequestTypeInitial, key_request.type);
|
||||
}
|
||||
|
||||
void TestLicenseHolder::CreateDefaultLicense() {
|
||||
video_widevine::SignedMessage signed_message;
|
||||
EXPECT_TRUE(signed_message.ParseFromString(signed_license_request_data_));
|
||||
license_request_data_ = signed_message.msg();
|
||||
video_widevine::LicenseRequest license_request;
|
||||
EXPECT_TRUE(license_request.ParseFromString(license_request_data_));
|
||||
video_widevine::ClientIdentification client_id = license_request.client_id();
|
||||
|
||||
EXPECT_EQ(
|
||||
video_widevine::ClientIdentification_TokenType_DRM_DEVICE_CERTIFICATE,
|
||||
client_id.type());
|
||||
|
||||
// Extract the RSA key from the DRM certificate.
|
||||
std::string token = client_id.token();
|
||||
video_widevine::SignedDrmDeviceCertificate signed_drm_cert;
|
||||
EXPECT_TRUE(signed_drm_cert.ParseFromString(token));
|
||||
video_widevine::DrmDeviceCertificate drm_cert;
|
||||
EXPECT_TRUE(drm_cert.ParseFromString(signed_drm_cert.drm_certificate()));
|
||||
EXPECT_TRUE(rsa_key_.Init(drm_cert.public_key()));
|
||||
EXPECT_TRUE(rsa_key_.VerifySignature(signed_message.msg(),
|
||||
signed_message.signature()));
|
||||
|
||||
DeriveKeysFromSessionKey();
|
||||
|
||||
video_widevine::LicenseIdentification* license_id = license()->mutable_id();
|
||||
license_id->set_request_id("TestCase");
|
||||
license_id->set_session_id(session_id_);
|
||||
license_id->set_type(video_widevine::STREAMING);
|
||||
license_id->set_version(0);
|
||||
|
||||
::video_widevine::License_Policy* policy = license()->mutable_policy();
|
||||
policy->set_can_play(true);
|
||||
policy->set_can_persist(false);
|
||||
policy->set_can_renew(false);
|
||||
policy->set_playback_duration_seconds(0);
|
||||
policy->set_license_duration_seconds(0);
|
||||
|
||||
AddMacKey();
|
||||
}
|
||||
|
||||
void TestLicenseHolder::AddMacKey() {
|
||||
video_widevine::License_KeyContainer* key_container = license()->add_key();
|
||||
std::vector<uint8_t> iv(KEY_SIZE, 'v');
|
||||
std::string iv_s(iv.begin(), iv.end());
|
||||
key_container->set_iv(iv_s);
|
||||
key_container->set_type(video_widevine::License_KeyContainer_KeyType_SIGNING);
|
||||
|
||||
// Combine server and client mac keys.
|
||||
std::vector<uint8_t> keys(mac_key_server_);
|
||||
keys.insert(keys.end(), mac_key_client_.begin(), mac_key_client_.end());
|
||||
std::string encrypted_keys =
|
||||
WvCdmTestBase::Aes128CbcEncrypt(enc_key_, keys, iv);
|
||||
key_container->set_key(encrypted_keys);
|
||||
}
|
||||
|
||||
video_widevine::License_KeyContainer* TestLicenseHolder::AddKey(
|
||||
const KeyId& key_id, const std::vector<uint8_t>& key_data,
|
||||
const wvoec::KeyControlBlock& block_in) {
|
||||
video_widevine::License_KeyContainer* key_container = license()->add_key();
|
||||
wvoec::KeyControlBlock block = block_in;
|
||||
if (block.verification[0] == 0) {
|
||||
block.verification[0] = 'k';
|
||||
block.verification[1] = 'c';
|
||||
block.verification[2] = '1';
|
||||
// This will work until oemcrypto api 20.
|
||||
block.verification[3] = '0' + wvoec::global_features.api_version - 10;
|
||||
}
|
||||
key_container->set_id(key_id);
|
||||
key_container->set_type(video_widevine::License_KeyContainer_KeyType_CONTENT);
|
||||
key_container->set_level(
|
||||
video_widevine::License_KeyContainer_SecurityLevel_SW_SECURE_CRYPTO);
|
||||
|
||||
std::vector<uint8_t> iv(KEY_SIZE, 'v');
|
||||
std::string iv_s(iv.begin(), iv.end());
|
||||
key_container->set_iv(iv_s);
|
||||
|
||||
std::string encrypted_key_data =
|
||||
WvCdmTestBase::Aes128CbcEncrypt(enc_key_, key_data, iv);
|
||||
// TODO(b/111069024): remove this!
|
||||
std::string padding(KEY_SIZE, '-');
|
||||
key_container->set_key(encrypted_key_data + padding);
|
||||
|
||||
std::vector<uint8_t> block_v(
|
||||
reinterpret_cast<const uint8_t*>(&block),
|
||||
reinterpret_cast<const uint8_t*>(&block) + sizeof(block));
|
||||
std::vector<uint8_t> block_iv(KEY_SIZE, 'w');
|
||||
std::string block_iv_s(block_iv.begin(), block_iv.end());
|
||||
std::string encrypted_block =
|
||||
WvCdmTestBase::Aes128CbcEncrypt(key_data, block_v, block_iv);
|
||||
key_container->mutable_key_control()->set_iv(block_iv_s);
|
||||
key_container->mutable_key_control()->set_key_control_block(encrypted_block);
|
||||
return key_container;
|
||||
}
|
||||
|
||||
void TestLicenseHolder::SignAndLoadLicense() {
|
||||
#if 0 // Need to turn off protobuf_lite to use this.
|
||||
LOGV("License = %s\n", license_.DebugString().c_str());
|
||||
#endif
|
||||
std::string license_data;
|
||||
license_.SerializeToString(&license_data);
|
||||
|
||||
std::string signature =
|
||||
WvCdmTestBase::SignHMAC(license_data, derived_mac_key_server_);
|
||||
|
||||
std::string session_key_s(session_key_.begin(), session_key_.end());
|
||||
std::string encrypted_session_key;
|
||||
EXPECT_TRUE(rsa_key_.Encrypt(session_key_s, &encrypted_session_key));
|
||||
|
||||
video_widevine::SignedMessage signed_response;
|
||||
signed_response.set_msg(license_data);
|
||||
signed_response.set_type(video_widevine::SignedMessage_MessageType_LICENSE);
|
||||
signed_response.set_session_key(encrypted_session_key);
|
||||
signed_response.set_signature(signature);
|
||||
|
||||
std::string response_data;
|
||||
signed_response.SerializeToString(&response_data);
|
||||
|
||||
CdmKeySetId key_set_id;
|
||||
EXPECT_EQ(KEY_ADDED,
|
||||
cdm_engine_->AddKey(session_id_, response_data, &key_set_id));
|
||||
}
|
||||
|
||||
void TestLicenseHolder::DeriveKeysFromSessionKey() {
|
||||
std::string context;
|
||||
GenerateMacContext(license_request_data_, &context);
|
||||
std::vector<uint8_t> mac_key_context(context.begin(), context.end());
|
||||
GenerateEncryptContext(license_request_data_, &context);
|
||||
std::vector<uint8_t> enc_key_context(context.begin(), context.end());
|
||||
|
||||
ASSERT_TRUE(
|
||||
DeriveKey(session_key_, mac_key_context, 1, &derived_mac_key_server_));
|
||||
std::vector<uint8_t> mac_key_part2;
|
||||
ASSERT_TRUE(DeriveKey(session_key_, mac_key_context, 2, &mac_key_part2));
|
||||
derived_mac_key_server_.insert(derived_mac_key_server_.end(),
|
||||
mac_key_part2.begin(), mac_key_part2.end());
|
||||
|
||||
ASSERT_TRUE(
|
||||
DeriveKey(session_key_, mac_key_context, 3, &derived_mac_key_client_));
|
||||
ASSERT_TRUE(DeriveKey(session_key_, mac_key_context, 4, &mac_key_part2));
|
||||
derived_mac_key_client_.insert(derived_mac_key_client_.end(),
|
||||
mac_key_part2.begin(), mac_key_part2.end());
|
||||
|
||||
std::vector<uint8_t> enc_key;
|
||||
ASSERT_TRUE(DeriveKey(session_key_, enc_key_context, 1, &enc_key_));
|
||||
}
|
||||
|
||||
bool TestLicenseHolder::DeriveKey(const std::vector<uint8_t>& key,
|
||||
const std::vector<uint8_t>& context,
|
||||
int counter, std::vector<uint8_t>* out) {
|
||||
if (key.empty() || counter > 4 || context.empty() || out == NULL) {
|
||||
LOGE("DeriveKey(): bad context");
|
||||
return false;
|
||||
}
|
||||
const EVP_CIPHER* cipher = EVP_aes_128_cbc();
|
||||
CMAC_CTX* cmac_ctx = CMAC_CTX_new();
|
||||
|
||||
if (!cmac_ctx) {
|
||||
LOGE("DeriveKey(): cmac failure");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CMAC_Init(cmac_ctx, &key[0], key.size(), cipher, 0)) {
|
||||
LOGE("DeriveKey(): CMAC_Init");
|
||||
CMAC_CTX_free(cmac_ctx);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> message;
|
||||
message.push_back(counter);
|
||||
message.insert(message.end(), context.begin(), context.end());
|
||||
|
||||
if (!CMAC_Update(cmac_ctx, &message[0], message.size())) {
|
||||
LOGE("DeriveKey(): CMAC_Update");
|
||||
CMAC_CTX_free(cmac_ctx);
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t reslen;
|
||||
uint8_t res[128];
|
||||
if (!CMAC_Final(cmac_ctx, res, &reslen)) {
|
||||
LOGE("DeriveKey(): CMAC_Final");
|
||||
CMAC_CTX_free(cmac_ctx);
|
||||
return false;
|
||||
}
|
||||
out->assign(res, res + reslen);
|
||||
CMAC_CTX_free(cmac_ctx);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
|
||||
@@ -7,9 +7,11 @@
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "cdm_engine.h"
|
||||
#include "config_test_env.h"
|
||||
#include "crypto_session.h"
|
||||
#include "metrics_collections.h"
|
||||
#include "oec_session_util.h"
|
||||
#include "string_conversions.h"
|
||||
|
||||
namespace wvcdm {
|
||||
@@ -17,7 +19,7 @@ namespace wvcdm {
|
||||
// to configure OEMCrypto to use a test keybox.
|
||||
class WvCdmTestBase : public ::testing::Test {
|
||||
public:
|
||||
WvCdmTestBase() : config_(default_config_) {}
|
||||
WvCdmTestBase() : config_(default_config_), binary_provisioning_(false) {}
|
||||
virtual ~WvCdmTestBase() {}
|
||||
virtual void SetUp();
|
||||
virtual std::string binary_key_id() const { return a2bs_hex(config_.key_id()); }
|
||||
@@ -29,6 +31,28 @@ class WvCdmTestBase : public ::testing::Test {
|
||||
// Install a test keybox, if appropriate.
|
||||
static void InstallTestRootOfTrust();
|
||||
|
||||
// Send provisioning request to the server and handle response.
|
||||
virtual void Provision();
|
||||
// Calls Provision() if not already provisioned.
|
||||
virtual void EnsureProvisioned();
|
||||
|
||||
// Fill a buffer with some nonconstant data of the given size. The first byte
|
||||
// will be set to <init> to help you find the buffer when debugging.
|
||||
static void StripeBuffer(std::vector<uint8_t>* buffer, size_t size,
|
||||
uint8_t init);
|
||||
|
||||
// Helper method for doing cryptography.
|
||||
static std::string Aes128CbcEncrypt(std::vector<uint8_t> key,
|
||||
const std::vector<uint8_t>& clear,
|
||||
const std::vector<uint8_t> iv);
|
||||
// Helper method for doing cryptography.
|
||||
static std::string Aes128CbcDecrypt(std::vector<uint8_t> key,
|
||||
const std::vector<uint8_t>& clear,
|
||||
const std::vector<uint8_t> iv);
|
||||
// Helper method for doing cryptography.
|
||||
static std::string SignHMAC(const std::string& message,
|
||||
const std::vector<uint8_t>& key);
|
||||
|
||||
// The default test configuration. This is influenced by command line
|
||||
// arguments before any tests are created.
|
||||
static ConfigTestEnv default_config_;
|
||||
@@ -36,6 +60,10 @@ class WvCdmTestBase : public ::testing::Test {
|
||||
// Configuration for an individual test. This is initialized to be the
|
||||
// default configuration, but can be modified by the test itself.
|
||||
ConfigTestEnv config_;
|
||||
|
||||
// This should be set by test subclasses BEFORE calling SetUp -- i.e. in the
|
||||
// tests's constructor.
|
||||
bool binary_provisioning_;
|
||||
};
|
||||
|
||||
class TestCryptoSession : public CryptoSession {
|
||||
@@ -46,6 +74,68 @@ class TestCryptoSession : public CryptoSession {
|
||||
bool GenerateNonce(uint32_t* nonce);
|
||||
};
|
||||
|
||||
// A holder for a license. Users of this class will first open a session with
|
||||
// OpenSession, then generate a key request with GenerateKeyRequest, and then
|
||||
// call CreateDefaultLicense to create a bare-bones license with no keys in it.
|
||||
// The user may then access the license to adjust the policy, or use AddKey to
|
||||
// add keys to the license. The license is then loaded via SignAndLoadLicense.
|
||||
class TestLicenseHolder {
|
||||
public:
|
||||
// cdm_engine must exist and outlive the TestLicenseHolder.
|
||||
TestLicenseHolder(CdmEngine *cdm_engine);
|
||||
~TestLicenseHolder();
|
||||
// Caller must ensure device already provisioned.
|
||||
void OpenSession(const std::string& key_system);
|
||||
void CloseSession();
|
||||
// Use the cdm_engine to generate a key request in the session. This should
|
||||
// be called after OpenSession. This saves the signed license request, so
|
||||
// that the DRM certificate can be extracted in CreateDefaultLicense.
|
||||
void GenerateKeyRequest(const std::string& key_id,
|
||||
const std::string& init_data_type_string);
|
||||
// Create a bare-bones license from the license request. After this, the user
|
||||
// may access and modify the license using license() below.
|
||||
void CreateDefaultLicense();
|
||||
// Sign the license using the DRM certificate's RSA key. Then the license is
|
||||
// passed to the cdm_engine using AddKey. After this, the license is loaded
|
||||
// and the keys may be used.
|
||||
void SignAndLoadLicense();
|
||||
|
||||
// The session id. This is only valid after a call to OpenSession.
|
||||
const std::string& session_id() { return session_id_; }
|
||||
// The license protobuf. This is only valid after CreateDefaultLicense.
|
||||
video_widevine::License* license() { return &license_; };
|
||||
// Add a key with the given key control block and key data.
|
||||
// If the block's verification is empty, it will be set to a valid value.
|
||||
// The key data is encrypted correctly.
|
||||
video_widevine::License_KeyContainer* AddKey(
|
||||
const KeyId& key_id, const std::vector<uint8_t>& key_data,
|
||||
const wvoec::KeyControlBlock& block);
|
||||
|
||||
private:
|
||||
// Helper method to generate mac keys and encryption keys for the license.
|
||||
void DeriveKeysFromSessionKey();
|
||||
// Derive a single mac key or encryption key using CMAC.
|
||||
bool DeriveKey(const std::vector<uint8_t>& key,
|
||||
const std::vector<uint8_t>& context, int counter,
|
||||
std::vector<uint8_t>* out);
|
||||
// Add the mac keys to the license.
|
||||
void AddMacKey();
|
||||
|
||||
CdmEngine* cdm_engine_;
|
||||
std::string signed_license_request_data_;
|
||||
std::string license_request_data_;
|
||||
std::string session_id_;
|
||||
bool session_opened_;
|
||||
RsaPublicKey rsa_key_; // From the DRM Certificate.
|
||||
video_widevine::License license_;
|
||||
std::vector<uint8_t> derived_mac_key_server_;
|
||||
std::vector<uint8_t> derived_mac_key_client_;
|
||||
std::vector<uint8_t> mac_key_server_;
|
||||
std::vector<uint8_t> mac_key_client_;
|
||||
std::vector<uint8_t> enc_key_;
|
||||
std::vector<uint8_t> session_key_;
|
||||
};
|
||||
|
||||
} // namespace wvcdm
|
||||
|
||||
#endif // WVCDM_CORE_TEST_BASE_H_
|
||||
|
||||
Reference in New Issue
Block a user