Merge changes Icd280b53,I6eed117e into udc-dev am: 1a42aed25b
Original change: https://googleplex-android-review.googlesource.com/c/platform/vendor/widevine/+/23049485 Change-Id: Iab9ef74a334cad8d5e77853b62b11ca7abd3abbd Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -10,6 +10,7 @@
|
|||||||
#include "create_test_file_system.h"
|
#include "create_test_file_system.h"
|
||||||
#include "crypto_session.h"
|
#include "crypto_session.h"
|
||||||
#include "properties.h"
|
#include "properties.h"
|
||||||
|
#include "provisioning_holder.h"
|
||||||
#include "test_base.h"
|
#include "test_base.h"
|
||||||
#include "test_printers.h"
|
#include "test_printers.h"
|
||||||
#include "test_sleep.h"
|
#include "test_sleep.h"
|
||||||
@@ -43,47 +44,11 @@ class CdmOtaKeyboxTest : public ::testing::Test {
|
|||||||
|
|
||||||
void Provision(TestCdmEngine* cdm_engine) {
|
void Provision(TestCdmEngine* cdm_engine) {
|
||||||
ConfigTestEnv config = *WvCdmTestBase::default_config_;
|
ConfigTestEnv config = *WvCdmTestBase::default_config_;
|
||||||
|
ProvisioningHolder provisioner(cdm_engine, config.provisioning_server(),
|
||||||
|
config.provisioning_service_certificate());
|
||||||
CdmCertificateType cert_type = kCertificateWidevine;
|
CdmCertificateType cert_type = kCertificateWidevine;
|
||||||
std::string cert_authority;
|
constexpr bool binary_provisioning = false;
|
||||||
CdmProvisioningRequest prov_request;
|
provisioner.Provision(cert_type, binary_provisioning);
|
||||||
std::string provisioning_server_url;
|
|
||||||
CdmResponseType result(CERT_PROVISIONING_NONCE_GENERATION_ERROR);
|
|
||||||
// Get a provisioning request. We might need one retry if there is a nonce
|
|
||||||
// flood failure.
|
|
||||||
for (int i = 0; i < 2; i++) {
|
|
||||||
result = cdm_engine->GetProvisioningRequest(
|
|
||||||
cert_type, cert_authority, config.provisioning_service_certificate(),
|
|
||||||
kLevelDefault, &prov_request, &provisioning_server_url);
|
|
||||||
if (result == CERT_PROVISIONING_NONCE_GENERATION_ERROR) {
|
|
||||||
wvutil::TestSleep::Sleep(2);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
ASSERT_EQ(NO_ERROR, result);
|
|
||||||
LOGV("Provisioning request: 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());
|
|
||||||
|
|
||||||
// Make request.
|
|
||||||
UrlRequest url_request(provisioning_server_url);
|
|
||||||
if (!url_request.is_connected()) {
|
|
||||||
LOGE("Failed to connect to provisioning server: url = %s",
|
|
||||||
provisioning_server_url.c_str());
|
|
||||||
}
|
|
||||||
url_request.PostCertRequestInQueryString(prov_request);
|
|
||||||
|
|
||||||
// Receive and parse response.
|
|
||||||
std::string http_message;
|
|
||||||
ASSERT_NO_FATAL_FAILURE(url_request.AssertOkResponse(&http_message))
|
|
||||||
<< "Keybox OTA provisioning request failed.";
|
|
||||||
LOGV("http_message: \n%s\n", http_message.c_str());
|
|
||||||
|
|
||||||
std::string cert, wrapped_key;
|
|
||||||
ASSERT_EQ(NO_ERROR, cdm_engine->HandleProvisioningResponse(
|
|
||||||
http_message, kLevelDefault, &cert, &wrapped_key));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,9 @@
|
|||||||
// would do. They verify that policies specified on UAT are honored on the
|
// would do. They verify that policies specified on UAT are honored on the
|
||||||
// device.
|
// device.
|
||||||
|
|
||||||
|
#include <openssl/err.h>
|
||||||
|
#include <openssl/rsa.h>
|
||||||
|
#include <openssl/x509.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -16,9 +19,9 @@
|
|||||||
#include "license_holder.h"
|
#include "license_holder.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "oec_device_features.h"
|
#include "oec_device_features.h"
|
||||||
|
#include "provisioning_holder.h"
|
||||||
#include "test_base.h"
|
#include "test_base.h"
|
||||||
#include "test_printers.h"
|
#include "test_printers.h"
|
||||||
|
|
||||||
#include "wv_cdm_types.h"
|
#include "wv_cdm_types.h"
|
||||||
|
|
||||||
namespace wvcdm {
|
namespace wvcdm {
|
||||||
@@ -111,4 +114,101 @@ TEST_F(CorePIGTest, OfflineHWSecureRequired) {
|
|||||||
ASSERT_NO_FATAL_FAILURE(holder.CloseSession());
|
ASSERT_NO_FATAL_FAILURE(holder.CloseSession());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(CorePIGTest, CastReceiverProvisioning) {
|
||||||
|
auto digest = wvutil::a2b_hex( // digest info header
|
||||||
|
"3021300906052b0e03021a05000414"
|
||||||
|
// sha1 of kMessage
|
||||||
|
"d2662f893aaec72f3ca6decc2aa942f3949e8b21");
|
||||||
|
|
||||||
|
if (!wvoec::global_features.cast_receiver) {
|
||||||
|
GTEST_SKIP() << "OEMCrypto does not support CAST Receiver functionality";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provision x509 cert for CAST Receiver.
|
||||||
|
ProvisioningHolder provisioner(&cdm_engine_, config_.provisioning_server(),
|
||||||
|
config_.provisioning_service_certificate());
|
||||||
|
provisioner.Provision(kCertificateX509, binary_provisioning_);
|
||||||
|
|
||||||
|
// cdm_engine_.OpenSession here is to load test keybox
|
||||||
|
// in order to successfully OEMCrypto_LoadDRMPrivateKey
|
||||||
|
std::string session_id;
|
||||||
|
CdmResponseType status = cdm_engine_.OpenSession(
|
||||||
|
config_.key_system(), nullptr, nullptr, &session_id);
|
||||||
|
ASSERT_EQ(NO_ERROR, status);
|
||||||
|
ASSERT_TRUE(cdm_engine_.IsOpenSession(session_id));
|
||||||
|
|
||||||
|
std::string wrapped_key_str = provisioner.wrapped_key();
|
||||||
|
std::vector<uint8_t> wrapped_key(wrapped_key_str.begin(),
|
||||||
|
wrapped_key_str.end());
|
||||||
|
OEMCrypto_SESSION oemcrypto_session;
|
||||||
|
OEMCryptoResult sts =
|
||||||
|
OEMCrypto_OpenSession(&oemcrypto_session, kLevelDefault);
|
||||||
|
if (sts != OEMCrypto_SUCCESS) {
|
||||||
|
LOGE("Fail in OEMCrypto_OpenSession");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop test when OEMCrypto_LoadDRMPrivateKey fails.
|
||||||
|
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_LoadDRMPrivateKey(
|
||||||
|
oemcrypto_session, OEMCrypto_RSA_Private_Key,
|
||||||
|
wrapped_key.data(), wrapped_key.size()));
|
||||||
|
|
||||||
|
// Generate signature for the digest
|
||||||
|
size_t signatureSize = 0;
|
||||||
|
ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER,
|
||||||
|
OEMCrypto_GenerateRSASignature(oemcrypto_session, digest.data(),
|
||||||
|
digest.size(), nullptr,
|
||||||
|
&signatureSize, kSign_PKCS1_Block1));
|
||||||
|
std::vector<uint8_t> signature;
|
||||||
|
signature.resize(signatureSize);
|
||||||
|
ASSERT_EQ(OEMCrypto_SUCCESS,
|
||||||
|
OEMCrypto_GenerateRSASignature(oemcrypto_session, digest.data(),
|
||||||
|
digest.size(), signature.data(),
|
||||||
|
&signatureSize, kSign_PKCS1_Block1));
|
||||||
|
LOGI("digest.size(): %zu, signature.size(): %zu", digest.size(),
|
||||||
|
signature.size());
|
||||||
|
|
||||||
|
// Verify the generated signature
|
||||||
|
std::string cert = provisioner.certificate();
|
||||||
|
const char* cert_str_ptr = cert.c_str();
|
||||||
|
LOGI("cert: %s", cert_str_ptr);
|
||||||
|
|
||||||
|
// Extract the public key from the x509 cert chain
|
||||||
|
BIO* bio = BIO_new(BIO_s_mem());
|
||||||
|
ASSERT_NE(bio, nullptr);
|
||||||
|
ASSERT_GT(BIO_puts(bio, cert_str_ptr), 0);
|
||||||
|
X509* x509 = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr);
|
||||||
|
ASSERT_NE(x509, nullptr);
|
||||||
|
EVP_PKEY* pubkey = X509_get_pubkey(x509);
|
||||||
|
ASSERT_NE(pubkey, nullptr);
|
||||||
|
|
||||||
|
// remove digest info header for verification
|
||||||
|
// SHA1 is 20 bytes long
|
||||||
|
digest.erase(digest.begin(), digest.begin() + digest.size() - 20);
|
||||||
|
|
||||||
|
// Modified from openssl example
|
||||||
|
// https://www.openssl.org/docs/man3.0/man3/EVP_PKEY_verify_init.html
|
||||||
|
// Set RSA padding as RSA_PKCS1_PADDING and digest algo to SHA1.
|
||||||
|
EVP_PKEY_CTX* ctx;
|
||||||
|
unsigned char* md = digest.data();
|
||||||
|
unsigned char* sig = signature.data();
|
||||||
|
size_t mdlen = digest.size();
|
||||||
|
size_t siglen = signature.size();
|
||||||
|
|
||||||
|
ctx = EVP_PKEY_CTX_new(pubkey, nullptr /* no engine */);
|
||||||
|
ASSERT_NE(ctx, nullptr);
|
||||||
|
ASSERT_GT(EVP_PKEY_verify_init(ctx), 0);
|
||||||
|
ASSERT_GT(EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING), 0);
|
||||||
|
ASSERT_GT(EVP_PKEY_CTX_set_signature_md(ctx, EVP_sha1()), 0);
|
||||||
|
|
||||||
|
/* Perform operation */
|
||||||
|
EXPECT_EQ(1, EVP_PKEY_verify(ctx, sig, siglen, md, mdlen));
|
||||||
|
|
||||||
|
EVP_PKEY_CTX_free(ctx);
|
||||||
|
EVP_PKEY_free(pubkey);
|
||||||
|
BIO_free(bio);
|
||||||
|
X509_free(x509);
|
||||||
|
|
||||||
|
OEMCrypto_CloseSession(oemcrypto_session);
|
||||||
|
cdm_engine_.CloseSession(session_id);
|
||||||
|
}
|
||||||
} // namespace wvcdm
|
} // namespace wvcdm
|
||||||
|
|||||||
193
libwvdrmengine/cdm/core/test/provisioning_holder.cpp
Normal file
193
libwvdrmengine/cdm/core/test/provisioning_holder.cpp
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
// 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 "provisioning_holder.h"
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include "cdm_engine.h"
|
||||||
|
#include "config_test_env.h"
|
||||||
|
|
||||||
|
#include "log.h"
|
||||||
|
#include "oec_device_features.h"
|
||||||
|
#include "test_printers.h"
|
||||||
|
#include "test_sleep.h"
|
||||||
|
#include "url_request.h"
|
||||||
|
|
||||||
|
namespace wvcdm {
|
||||||
|
|
||||||
|
void ProvisioningHolder::Provision(CdmCertificateType cert_type,
|
||||||
|
bool binary_provisioning) {
|
||||||
|
CdmProvisioningRequest request;
|
||||||
|
std::string provisioning_server_url;
|
||||||
|
std::string cert_authority;
|
||||||
|
|
||||||
|
CdmSessionId session_id;
|
||||||
|
|
||||||
|
if (cert_type == kCertificateX509) {
|
||||||
|
cert_authority = "cast.google.com";
|
||||||
|
}
|
||||||
|
LOGV("Provision with type %s.", CdmCertificateTypeToString(cert_type));
|
||||||
|
CdmResponseType result(CERT_PROVISIONING_NONCE_GENERATION_ERROR);
|
||||||
|
// Get a provisioning request. We might need one retry if there is a nonce
|
||||||
|
// flood failure.
|
||||||
|
for (int i = 0; i < 2 && result == CERT_PROVISIONING_NONCE_GENERATION_ERROR;
|
||||||
|
i++) {
|
||||||
|
result = cdm_engine_->GetProvisioningRequest(
|
||||||
|
cert_type, cert_authority, provisioning_service_certificate_,
|
||||||
|
kLevelDefault, &request, &provisioning_server_url);
|
||||||
|
if (result == CERT_PROVISIONING_NONCE_GENERATION_ERROR) {
|
||||||
|
wvutil::TestSleep::Sleep(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ASSERT_EQ(NO_ERROR, result);
|
||||||
|
LOGV("cert_authority = %s", cert_authority.c_str());
|
||||||
|
|
||||||
|
if (binary_provisioning) {
|
||||||
|
request = wvutil::Base64SafeEncodeNoPad(request);
|
||||||
|
}
|
||||||
|
LOGV("Provisioning request: req = %s", request.c_str());
|
||||||
|
|
||||||
|
// Ignore URL provided by CdmEngine. Use ours, as configured
|
||||||
|
// for test vs. production server.
|
||||||
|
provisioning_server_url.assign(provisioning_server_url_);
|
||||||
|
|
||||||
|
// Make request.
|
||||||
|
UrlRequest url_request(provisioning_server_url);
|
||||||
|
if (!url_request.is_connected()) {
|
||||||
|
LOGE("Failed to connect to provisioning server: url = %s",
|
||||||
|
provisioning_server_url.c_str());
|
||||||
|
}
|
||||||
|
url_request.PostCertRequestInQueryString(request);
|
||||||
|
|
||||||
|
// Receive and parse response.
|
||||||
|
std::string response;
|
||||||
|
ASSERT_NO_FATAL_FAILURE(url_request.AssertOkResponse(&response))
|
||||||
|
<< "Failed to fetch provisioning response. "
|
||||||
|
<< DumpProvAttempt(request, response, cert_type);
|
||||||
|
|
||||||
|
if (binary_provisioning) {
|
||||||
|
// extract provisioning response from received message
|
||||||
|
// Extracts signed response from JSON string, result is serialized
|
||||||
|
// protobuf.
|
||||||
|
std::string protobuf_response;
|
||||||
|
const bool extract_ok = ExtractSignedMessage(response, &protobuf_response);
|
||||||
|
ASSERT_TRUE(extract_ok) << "Failed to extract signed serialized "
|
||||||
|
"response from JSON response";
|
||||||
|
LOGV("Extracted response message: \n%s\n", protobuf_response.c_str());
|
||||||
|
|
||||||
|
ASSERT_FALSE(protobuf_response.empty())
|
||||||
|
<< "Protobuf response is unexpectedly empty";
|
||||||
|
|
||||||
|
// base64 decode response to yield binary protobuf
|
||||||
|
const std::vector<uint8_t> response_vec(
|
||||||
|
wvutil::Base64SafeDecode(protobuf_response));
|
||||||
|
ASSERT_FALSE(response_vec.empty())
|
||||||
|
<< "Failed to decode base64 of response: response = "
|
||||||
|
<< protobuf_response;
|
||||||
|
|
||||||
|
const std::string binary_protobuf_response(response_vec.begin(),
|
||||||
|
response_vec.end());
|
||||||
|
|
||||||
|
ASSERT_EQ(NO_ERROR, cdm_engine_->HandleProvisioningResponse(
|
||||||
|
binary_protobuf_response, kLevelDefault,
|
||||||
|
&certificate_, &wrapped_key_))
|
||||||
|
<< "Binary provisioning failed. "
|
||||||
|
<< DumpProvAttempt(request, response, cert_type);
|
||||||
|
} else {
|
||||||
|
ASSERT_EQ(NO_ERROR,
|
||||||
|
cdm_engine_->HandleProvisioningResponse(
|
||||||
|
response, kLevelDefault, &certificate_, &wrapped_key_))
|
||||||
|
<< "Non-binary provisioning failed. "
|
||||||
|
<< DumpProvAttempt(request, response, cert_type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ProvisioningHolder::ExtractSignedMessage(const std::string& response,
|
||||||
|
std::string* result) {
|
||||||
|
static const std::string kMessageStart = "\"signedResponse\": \"";
|
||||||
|
static const std::string kMessageEnd = "\"";
|
||||||
|
std::string response_string;
|
||||||
|
size_t start = response.find(kMessageStart);
|
||||||
|
|
||||||
|
if (start == response.npos) {
|
||||||
|
// Assume serialized protobuf message.
|
||||||
|
result->assign(response);
|
||||||
|
} else {
|
||||||
|
// Assume JSON-wrapped protobuf.
|
||||||
|
size_t end = response.find(kMessageEnd, start + kMessageStart.length());
|
||||||
|
if (end == response.npos) {
|
||||||
|
LOGE("ExtractSignedMessage cannot locate end substring");
|
||||||
|
result->clear();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
size_t result_string_size = end - start - kMessageStart.length();
|
||||||
|
result->assign(response, start + kMessageStart.length(),
|
||||||
|
result_string_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result->empty()) {
|
||||||
|
LOGE("ExtractSignedMessage: Response message is empty");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ProvisioningHolder::DumpProvAttempt(const std::string& request,
|
||||||
|
const std::string& response,
|
||||||
|
CdmCertificateType cert_type) {
|
||||||
|
std::stringstream info;
|
||||||
|
info << "Cert Type: ";
|
||||||
|
PrintTo(cert_type, &info);
|
||||||
|
info << "\n";
|
||||||
|
info << "Provisioning url: " << provisioning_server_url_ << "\n";
|
||||||
|
info << "Provisioning Request: " << request << "\n\n";
|
||||||
|
info << "Provisioning Response: " << response << "\n\n";
|
||||||
|
std::string system_id;
|
||||||
|
cdm_engine_->QueryStatus(kLevelDefault, QUERY_KEY_SYSTEM_ID, &system_id);
|
||||||
|
info << "system id: " << system_id << "\n";
|
||||||
|
if (wvoec::global_features.derive_key_method ==
|
||||||
|
wvoec::DeviceFeatures::TEST_PROVISION_30) {
|
||||||
|
std::vector<uint8_t> cert;
|
||||||
|
size_t cert_length = 0;
|
||||||
|
OEMCryptoResult result = OEMCrypto_GetOEMPublicCertificate(
|
||||||
|
cert.data(), &cert_length, kLevelDefault);
|
||||||
|
if (result == OEMCrypto_ERROR_SHORT_BUFFER) {
|
||||||
|
cert.resize(cert_length);
|
||||||
|
result = OEMCrypto_GetOEMPublicCertificate(cert.data(), &cert_length,
|
||||||
|
kLevelDefault);
|
||||||
|
}
|
||||||
|
if (result != OEMCrypto_SUCCESS) {
|
||||||
|
info << "--- ERROR GETTING CERT. result=" << result;
|
||||||
|
} else {
|
||||||
|
info << "OEM Cert = (len=" << cert_length << ") "
|
||||||
|
<< wvutil::unlimited_b2a_hex(cert);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (wvoec::global_features.derive_key_method ==
|
||||||
|
wvoec::DeviceFeatures::TEST_PROVISION_40) {
|
||||||
|
std::vector<uint8_t> bcc;
|
||||||
|
size_t bcc_length = 0;
|
||||||
|
std::vector<uint8_t> signature;
|
||||||
|
size_t signature_length = 0;
|
||||||
|
OEMCryptoResult result = OEMCrypto_GetBootCertificateChain(
|
||||||
|
bcc.data(), &bcc_length, signature.data(), &signature_length);
|
||||||
|
if (result == OEMCrypto_ERROR_SHORT_BUFFER) {
|
||||||
|
bcc.resize(bcc_length);
|
||||||
|
signature.resize(signature_length);
|
||||||
|
result = OEMCrypto_GetBootCertificateChain(
|
||||||
|
bcc.data(), &bcc_length, signature.data(), &signature_length);
|
||||||
|
}
|
||||||
|
if (result != OEMCrypto_SUCCESS) {
|
||||||
|
info << "--- ERROR GETTING BCC. result=" << result;
|
||||||
|
} else {
|
||||||
|
info << "BCC = (len=" << bcc_length << ") "
|
||||||
|
<< wvutil::unlimited_b2a_hex(bcc) << "\n"
|
||||||
|
<< "Additional Sig = (len=" << signature_length << ") "
|
||||||
|
<< wvutil::unlimited_b2a_hex(signature) << "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return info.str();
|
||||||
|
}
|
||||||
|
} // namespace wvcdm
|
||||||
48
libwvdrmengine/cdm/core/test/provisioning_holder.h
Normal file
48
libwvdrmengine/cdm/core/test/provisioning_holder.h
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
// 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_PROVISIONING_HOLDER_H_
|
||||||
|
#define WVCDM_CORE_TEST_PROVISIONING_HOLDER_H_
|
||||||
|
|
||||||
|
#include "test_base.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace wvcdm {
|
||||||
|
|
||||||
|
class ProvisioningHolder {
|
||||||
|
public:
|
||||||
|
ProvisioningHolder(TestCdmEngine* cdm_engine,
|
||||||
|
const std::string& provisioning_server_url,
|
||||||
|
const std::string& provisioning_service_certificate)
|
||||||
|
: cdm_engine_(cdm_engine),
|
||||||
|
provisioning_server_url_(provisioning_server_url),
|
||||||
|
provisioning_service_certificate_(provisioning_service_certificate) {}
|
||||||
|
void Provision(CdmCertificateType cert_type, bool binary_provisioning);
|
||||||
|
std::string certificate() const { return certificate_; }
|
||||||
|
std::string wrapped_key() const { return wrapped_key_; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
TestCdmEngine* cdm_engine_;
|
||||||
|
std::string provisioning_server_url_;
|
||||||
|
std::string provisioning_service_certificate_;
|
||||||
|
std::string certificate_;
|
||||||
|
std::string wrapped_key_;
|
||||||
|
|
||||||
|
// Locate the portion of the server's provisioning 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, std::string* result);
|
||||||
|
// Dump request and response information for use in a debug or failure log.
|
||||||
|
std::string DumpProvAttempt(const std::string& request,
|
||||||
|
const std::string& response,
|
||||||
|
CdmCertificateType cert_type);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace wvcdm
|
||||||
|
|
||||||
|
#endif // WVCDM_CORE_TEST_PROVISIONING_HOLDER_H_
|
||||||
@@ -26,6 +26,7 @@
|
|||||||
#include "oec_test_data.h"
|
#include "oec_test_data.h"
|
||||||
#include "platform.h"
|
#include "platform.h"
|
||||||
#include "properties.h"
|
#include "properties.h"
|
||||||
|
#include "provisioning_holder.h"
|
||||||
#include "test_printers.h"
|
#include "test_printers.h"
|
||||||
#include "test_sleep.h"
|
#include "test_sleep.h"
|
||||||
#include "url_request.h"
|
#include "url_request.h"
|
||||||
@@ -143,101 +144,6 @@ void show_menu(const char* prog_name, const std::string& extra_help_text) {
|
|||||||
|
|
||||||
std::cout << extra_help_text << std::endl;
|
std::cout << extra_help_text << 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(b/242744857): This extra debugging may not be needed in all cases. When
|
|
||||||
// provisioning fails, this dumps the cert and other information.
|
|
||||||
std::string DumpProvAttempt(const std::string& url, const std::string& request,
|
|
||||||
const std::string& http_message,
|
|
||||||
CdmEngine* cdm_engine) {
|
|
||||||
std::stringstream info;
|
|
||||||
info << "Provisioning url: " << url << "\n";
|
|
||||||
info << "Request: " << wvutil::unlimited_b2a_hex(request) << "\n";
|
|
||||||
info << "http_message: " << wvutil::unlimited_b2a_hex(http_message) << "\n";
|
|
||||||
std::string system_id;
|
|
||||||
cdm_engine->QueryStatus(kLevelDefault, QUERY_KEY_SYSTEM_ID, &system_id);
|
|
||||||
info << "system id: " << system_id << "\n";
|
|
||||||
if (wvoec::global_features.derive_key_method ==
|
|
||||||
wvoec::DeviceFeatures::TEST_PROVISION_30) {
|
|
||||||
std::vector<uint8_t> cert;
|
|
||||||
size_t cert_length = 0;
|
|
||||||
OEMCryptoResult result = OEMCrypto_GetOEMPublicCertificate(
|
|
||||||
cert.data(), &cert_length, kLevelDefault);
|
|
||||||
if (result == OEMCrypto_ERROR_SHORT_BUFFER) {
|
|
||||||
cert.resize(cert_length);
|
|
||||||
result = OEMCrypto_GetOEMPublicCertificate(cert.data(), &cert_length,
|
|
||||||
kLevelDefault);
|
|
||||||
}
|
|
||||||
if (result != OEMCrypto_SUCCESS) {
|
|
||||||
info << "--- ERROR GETTING CERT. result=" << result;
|
|
||||||
} else {
|
|
||||||
info << "OEM Cert = (len=" << cert_length << ") "
|
|
||||||
<< wvutil::unlimited_b2a_hex(cert);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (wvoec::global_features.derive_key_method ==
|
|
||||||
wvoec::DeviceFeatures::TEST_PROVISION_40) {
|
|
||||||
std::vector<uint8_t> bcc;
|
|
||||||
size_t bcc_length = 0;
|
|
||||||
std::vector<uint8_t> signature;
|
|
||||||
size_t signature_length = 0;
|
|
||||||
OEMCryptoResult result = OEMCrypto_GetBootCertificateChain(
|
|
||||||
bcc.data(), &bcc_length, signature.data(), &signature_length);
|
|
||||||
if (result == OEMCrypto_ERROR_SHORT_BUFFER) {
|
|
||||||
bcc.resize(bcc_length);
|
|
||||||
signature.resize(signature_length);
|
|
||||||
result = OEMCrypto_GetBootCertificateChain(
|
|
||||||
bcc.data(), &bcc_length, signature.data(), &signature_length);
|
|
||||||
}
|
|
||||||
if (result != OEMCrypto_SUCCESS) {
|
|
||||||
info << "--- ERROR GETTING BCC. result=" << result;
|
|
||||||
} else {
|
|
||||||
info << "BCC = (len=" << bcc_length << ") "
|
|
||||||
<< wvutil::unlimited_b2a_hex(bcc) << "\n"
|
|
||||||
<< "Additional Sig = (len=" << signature_length << ") "
|
|
||||||
<< wvutil::unlimited_b2a_hex(signature) << "\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return info.str();
|
|
||||||
}
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
std::unique_ptr<ConfigTestEnv> WvCdmTestBase::default_config_;
|
std::unique_ptr<ConfigTestEnv> WvCdmTestBase::default_config_;
|
||||||
@@ -412,117 +318,15 @@ WvCdmTestBase::WvCdmTestBase()
|
|||||||
}
|
}
|
||||||
|
|
||||||
void WvCdmTestBase::Provision() {
|
void WvCdmTestBase::Provision() {
|
||||||
CdmProvisioningRequest prov_request;
|
const CdmCertificateType cert_type = kCertificateWidevine;
|
||||||
std::string provisioning_server_url;
|
|
||||||
CdmCertificateType cert_type = kCertificateWidevine;
|
|
||||||
std::string cert_authority;
|
|
||||||
std::string cert, wrapped_key;
|
|
||||||
|
|
||||||
CdmSessionId session_id;
|
|
||||||
std::unique_ptr<wvutil::FileSystem> file_system(CreateTestFileSystem());
|
std::unique_ptr<wvutil::FileSystem> file_system(CreateTestFileSystem());
|
||||||
|
TestCdmEngine cdm_engine(file_system.get(),
|
||||||
if (config_.provisioning_server() == "fake") {
|
std::shared_ptr<EngineMetrics>(new EngineMetrics));
|
||||||
LOGD("Using fake provisioning server.");
|
ProvisioningHolder provisioner(&cdm_engine, config_.provisioning_server(),
|
||||||
|
config_.provisioning_service_certificate());
|
||||||
TestCdmEngine cdm_engine(file_system.get(),
|
provisioner.Provision(cert_type, binary_provisioning_);
|
||||||
std::shared_ptr<EngineMetrics>(new EngineMetrics));
|
|
||||||
FakeProvisioningServer server;
|
|
||||||
CdmResponseType result = cdm_engine.GetProvisioningRequest(
|
|
||||||
cert_type, cert_authority, server.service_certificate(), kLevelDefault,
|
|
||||||
&prov_request, &provisioning_server_url);
|
|
||||||
ASSERT_EQ(NO_ERROR, result);
|
|
||||||
if (!binary_provisioning_) {
|
|
||||||
std::vector<uint8_t> prov_request_v =
|
|
||||||
wvutil::Base64SafeDecode(prov_request);
|
|
||||||
prov_request = std::string(prov_request_v.begin(), prov_request_v.end());
|
|
||||||
}
|
|
||||||
std::string response;
|
|
||||||
ASSERT_TRUE(server.MakeResponse(prov_request, &response))
|
|
||||||
<< "Fake provisioning server could not provision";
|
|
||||||
result = cdm_engine.HandleProvisioningResponse(response, kLevelDefault,
|
|
||||||
&cert, &wrapped_key);
|
|
||||||
EXPECT_EQ(NO_ERROR, result);
|
|
||||||
} else {
|
|
||||||
// TODO(fredgc): provision for different SPOIDs.
|
|
||||||
TestCdmEngine cdm_engine(file_system.get(),
|
|
||||||
std::shared_ptr<EngineMetrics>(new EngineMetrics));
|
|
||||||
|
|
||||||
CdmResponseType result = cdm_engine.GetProvisioningRequest(
|
|
||||||
cert_type, cert_authority, config_.provisioning_service_certificate(),
|
|
||||||
kLevelDefault, &prov_request, &provisioning_server_url);
|
|
||||||
ASSERT_EQ(NO_ERROR, result);
|
|
||||||
|
|
||||||
if (binary_provisioning_) {
|
|
||||||
prov_request = wvutil::Base64SafeEncodeNoPad(prov_request);
|
|
||||||
}
|
|
||||||
|
|
||||||
LOGV("Provisioning request: 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());
|
|
||||||
|
|
||||||
// Make request.
|
|
||||||
UrlRequest url_request(provisioning_server_url);
|
|
||||||
if (!url_request.is_connected()) {
|
|
||||||
LOGE("Failed to connect to provisioning server: url = %s",
|
|
||||||
provisioning_server_url.c_str());
|
|
||||||
}
|
|
||||||
url_request.PostCertRequestInQueryString(prov_request);
|
|
||||||
|
|
||||||
// Receive and parse response.
|
|
||||||
std::string http_message;
|
|
||||||
ASSERT_NO_FATAL_FAILURE(url_request.AssertOkResponse(&http_message))
|
|
||||||
<< "Failed to fetch provisioning response. "
|
|
||||||
<< DumpProvAttempt(provisioning_server_url, prov_request, http_message,
|
|
||||||
&cdm_engine);
|
|
||||||
|
|
||||||
if (binary_provisioning_) {
|
|
||||||
// extract provisioning response from received message
|
|
||||||
// Extracts signed response from JSON string, result is serialized
|
|
||||||
// protobuf.
|
|
||||||
static const std::string kMessageStart = "\"signedResponse\": \"";
|
|
||||||
static const std::string kMessageEnd = "\"";
|
|
||||||
std::string protobuf_response;
|
|
||||||
const bool extract_ok = ExtractSignedMessage(
|
|
||||||
http_message, kMessageStart, kMessageEnd, &protobuf_response);
|
|
||||||
ASSERT_TRUE(extract_ok) << "Failed to extract signed serialized "
|
|
||||||
"response from JSON response";
|
|
||||||
LOGV("Extracted response message: \n%s\n", protobuf_response.c_str());
|
|
||||||
|
|
||||||
ASSERT_FALSE(protobuf_response.empty())
|
|
||||||
<< "Protobuf response is unexpectedly empty";
|
|
||||||
|
|
||||||
// base64 decode response to yield binary protobuf
|
|
||||||
const std::vector<uint8_t> response_vec(
|
|
||||||
wvutil::Base64SafeDecode(protobuf_response));
|
|
||||||
ASSERT_FALSE(response_vec.empty())
|
|
||||||
<< "Failed to decode base64 of response: response = "
|
|
||||||
<< protobuf_response;
|
|
||||||
|
|
||||||
const std::string binary_protobuf_response(response_vec.begin(),
|
|
||||||
response_vec.end());
|
|
||||||
|
|
||||||
ASSERT_EQ(NO_ERROR, cdm_engine.HandleProvisioningResponse(
|
|
||||||
binary_protobuf_response, kLevelDefault, &cert,
|
|
||||||
&wrapped_key))
|
|
||||||
<< "Binary provisioning failed. "
|
|
||||||
<< DumpProvAttempt(provisioning_server_url, prov_request,
|
|
||||||
http_message, &cdm_engine);
|
|
||||||
} else {
|
|
||||||
ASSERT_EQ(NO_ERROR, cdm_engine.HandleProvisioningResponse(
|
|
||||||
http_message, kLevelDefault, &cert, &wrapped_key))
|
|
||||||
<< "Non-binary provisioning failed. "
|
|
||||||
<< DumpProvAttempt(provisioning_server_url, prov_request,
|
|
||||||
http_message, &cdm_engine);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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() {
|
void WvCdmTestBase::EnsureProvisioned() {
|
||||||
CdmSessionId session_id;
|
CdmSessionId session_id;
|
||||||
std::unique_ptr<wvutil::FileSystem> file_system(CreateTestFileSystem());
|
std::unique_ptr<wvutil::FileSystem> file_system(CreateTestFileSystem());
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ class TestCdmEngine : public CdmEngine {
|
|||||||
TestCdmEngine(wvutil::FileSystem* file_system,
|
TestCdmEngine(wvutil::FileSystem* file_system,
|
||||||
std::shared_ptr<metrics::EngineMetrics> metrics)
|
std::shared_ptr<metrics::EngineMetrics> metrics)
|
||||||
: CdmEngine(file_system, metrics) {}
|
: CdmEngine(file_system, metrics) {}
|
||||||
|
const CdmSession* GetCdmSession(std::string sessionId) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
class WvCdmTestBaseWithEngine : public WvCdmTestBase {
|
class WvCdmTestBaseWithEngine : public WvCdmTestBase {
|
||||||
|
|||||||
@@ -217,8 +217,8 @@ bool UrlRequest::PostRequestWithPath(const std::string& path,
|
|||||||
|
|
||||||
const int ret = socket_.WriteAndLogErrors(
|
const int ret = socket_.WriteAndLogErrors(
|
||||||
request.c_str(), static_cast<int>(request.size()), kWriteTimeoutMs);
|
request.c_str(), static_cast<int>(request.size()), kWriteTimeoutMs);
|
||||||
LOGV("HTTP request: (%zu): %s", request.size(),
|
LOGV("HTTP request: (%zu): %s", request.size(), request.c_str());
|
||||||
wvutil::b2a_hex(request).c_str());
|
LOGV("HTTP request hex: %s", wvutil::b2a_hex(request).c_str());
|
||||||
return ret != -1;
|
return ret != -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ LOCAL_SRC_FILES := \
|
|||||||
../core/test/http_socket.cpp \
|
../core/test/http_socket.cpp \
|
||||||
../core/test/license_holder.cpp \
|
../core/test/license_holder.cpp \
|
||||||
../core/test/license_request.cpp \
|
../core/test/license_request.cpp \
|
||||||
|
../core/test/provisioning_holder.cpp \
|
||||||
../core/test/test_base.cpp \
|
../core/test/test_base.cpp \
|
||||||
../core/test/test_printers.cpp \
|
../core/test/test_printers.cpp \
|
||||||
../core/test/url_request.cpp \
|
../core/test/url_request.cpp \
|
||||||
|
|||||||
@@ -2534,33 +2534,6 @@ TEST_F(WvCdmRequestLicenseTest, ProvisioningInterspersedRetryTest) {
|
|||||||
decryptor_->CloseSession(session_id_);
|
decryptor_->CloseSession(session_id_);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(WvCdmRequestLicenseTest, DISABLED_X509ProvisioningTest) {
|
|
||||||
decryptor_->OpenSession(config_.key_system(), nullptr, kDefaultCdmIdentifier,
|
|
||||||
nullptr, &session_id_);
|
|
||||||
std::string provisioning_server;
|
|
||||||
CdmCertificateType cert_type = kCertificateX509;
|
|
||||||
// TODO(rfrias): insert appropriate CA here
|
|
||||||
std::string cert_authority = "cast.google.com";
|
|
||||||
std::string cert, wrapped_key;
|
|
||||||
|
|
||||||
EXPECT_EQ(wvcdm::NO_ERROR,
|
|
||||||
decryptor_->GetProvisioningRequest(
|
|
||||||
cert_type, cert_authority, kDefaultCdmIdentifier,
|
|
||||||
kEmptyServiceCertificate, kLevelDefault, &key_msg_,
|
|
||||||
&provisioning_server));
|
|
||||||
EXPECT_THAT(provisioning_server,
|
|
||||||
IsSimilarUrlTo(kDefaultProvisioningServerUrl));
|
|
||||||
|
|
||||||
std::string response = GetCertRequestResponse(config_.provisioning_server());
|
|
||||||
EXPECT_NE(0, static_cast<int>(response.size()));
|
|
||||||
EXPECT_EQ(wvcdm::NO_ERROR, decryptor_->HandleProvisioningResponse(
|
|
||||||
kDefaultCdmIdentifier, response, kLevelDefault,
|
|
||||||
&cert, &wrapped_key));
|
|
||||||
EXPECT_NE(0, static_cast<int>(cert.size()));
|
|
||||||
EXPECT_NE(0, static_cast<int>(wrapped_key.size()));
|
|
||||||
decryptor_->CloseSession(session_id_);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(WvCdmRequestLicenseTest, ProvisioningSpoidTest) {
|
TEST_F(WvCdmRequestLicenseTest, ProvisioningSpoidTest) {
|
||||||
CdmProvisioningResponse provisioning_response;
|
CdmProvisioningResponse provisioning_response;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user