Add test base that catches nonce flood

Merge from Widevine repo of http://go/wvgerrit/56520

This CL adds a test base that installs a test keybox and catches nonce
flood errors for all CDM tests.

In order to do this, a new class is added called a
CryptoSessionFactory.  The default factory just creates a new
CryptoSession.  All places in the code that create a new CryptoSession
now call the static method MakeCryptoSession, which uses the current
factory to create a CryptoSession.  If MakeCryptoSession is called and
there is no current factory, a default factory is created.

The CryptoSession constructor is now private, so that we do not
accidentally try to create one without using the factory.

For the new test base, we first create a special test
CryptoSessionFactory that creates a TestCryptoSession.  The test
factory catches the first call to MakeCryptoSession and injects an
installation of the test keybox after OEMCrypto_Initialize is called.

The TestCryptoSession injects a sleep statement and a retry whenever
it detects a nonce flood.

Test: current unit tests still pass.
bug: 72354901 Fix Generic Crypto tests.
bug: 111361440 Remove #ifdef from unit tests
Change-Id: I248e7f3c53721c04d2af412ef835e19bb4d15d9a
This commit is contained in:
Fred Gylys-Colwell
2018-08-03 17:08:09 -07:00
parent c06b55b42f
commit 4af5aaf18a
24 changed files with 305 additions and 151 deletions

View File

@@ -26,7 +26,7 @@ class ServiceCertificate;
class CertificateProvisioning {
public:
CertificateProvisioning(metrics::CryptoMetrics* metrics) :
crypto_session_(metrics),
crypto_session_(CryptoSession::MakeCryptoSession(metrics)),
cert_type_(kCertificateWidevine),
service_certificate_(new ServiceCertificate()) {}
~CertificateProvisioning() {}
@@ -53,7 +53,7 @@ class CertificateProvisioning {
video_widevine::SignedProvisioningMessage::ProtocolVersion
GetProtocolVersion();
CryptoSession crypto_session_;
scoped_ptr<CryptoSession> crypto_session_;
CdmCertificateType cert_type_;
scoped_ptr<ServiceCertificate> service_certificate_;

View File

@@ -34,6 +34,8 @@ void GenerateEncryptContext(const std::string& input_context,
size_t GetOffset(std::string message, std::string field);
OEMCryptoCipherMode ToOEMCryptoCipherMode(CdmCipherMode cipher_mode);
class CryptoSessionFactory;
class CryptoSession {
public:
typedef OEMCrypto_HDCP_Capability HdcpCapability;
@@ -52,7 +54,9 @@ class CryptoSession {
// Creates an instance of CryptoSession with the given |crypto_metrics|.
// |crypto_metrics| is owned by the caller, must NOT be null, and must
// exist as long as the new CryptoSession exists.
explicit CryptoSession(metrics::CryptoMetrics* crypto_metrics);
static CryptoSession* MakeCryptoSession(
metrics::CryptoMetrics* crypto_metrics);
virtual ~CryptoSession();
virtual bool GetProvisioningToken(std::string* client_token);
@@ -203,8 +207,26 @@ class CryptoSession {
SecurityLevel requested_security_level,
CdmClientTokenType* token_type);
protected:
// Creates an instance of CryptoSession with the given |crypto_metrics|.
// |crypto_metrics| is owned by the caller, must NOT be null, and must
// exist as long as the new CryptoSession exists.
explicit CryptoSession(metrics::CryptoMetrics* crypto_metrics);
int session_count() { return session_count_; }
private:
friend class CryptoSessionForTest;
friend class CryptoSessionFactory;
friend class WvCdmTestBase;
// The global factory method can be set to generate special crypto sessions
// just for testing. These sessions will avoid nonce floods and will ask
// OEMCrypto to use a test keybox.
// Ownership of the object is transfered to CryptoSession.
static void SetCryptoSessionFactory(CryptoSessionFactory* factory) {
factory_.reset(factory);
}
void Init();
void Terminate();
@@ -291,9 +313,25 @@ class CryptoSession {
CdmCipherMode cipher_mode_;
uint32_t api_version_;
static scoped_ptr<CryptoSessionFactory> factory_;
CORE_DISALLOW_COPY_AND_ASSIGN(CryptoSession);
};
class CryptoSessionFactory {
public:
virtual ~CryptoSessionFactory() {}
virtual CryptoSession* MakeCryptoSession(
metrics::CryptoMetrics* crypto_metrics);
protected:
friend class CryptoSession;
CryptoSessionFactory() {}
private:
CORE_DISALLOW_COPY_AND_ASSIGN(CryptoSessionFactory);
};
} // namespace wvcdm
#endif // WVCDM_CORE_CRYPTO_SESSION_H_

View File

@@ -18,6 +18,7 @@
#include "disallow_copy_and_assign.h"
#include "license_protocol.pb.h"
#include "privacy_crypto.h"
#include "scoped_ptr.h"
#include "wv_cdm_types.h"
namespace wvcdm {
@@ -78,7 +79,7 @@ class ServiceCertificate {
std::string provider_id_;
// Public key.
std::auto_ptr<RsaPublicKey> public_key_;
scoped_ptr<RsaPublicKey> public_key_;
CORE_DISALLOW_COPY_AND_ASSIGN(ServiceCertificate);
};

View File

@@ -492,10 +492,11 @@ CdmResponseType CdmEngine::QueryStatus(SecurityLevel security_level,
const std::string& query_token,
std::string* query_response) {
LOGI("CdmEngine::QueryStatus");
CryptoSession crypto_session(metrics_.GetCryptoMetrics());
scoped_ptr<CryptoSession> crypto_session(
CryptoSession::MakeCryptoSession(metrics_.GetCryptoMetrics()));
CdmResponseType status;
M_TIME(
status = crypto_session.Open(
status = crypto_session->Open(
security_level),
metrics_.GetCryptoMetrics(),
crypto_session_open_,
@@ -511,7 +512,7 @@ CdmResponseType CdmEngine::QueryStatus(SecurityLevel security_level,
if (query_token == QUERY_KEY_SECURITY_LEVEL) {
CdmSecurityLevel found_security_level =
crypto_session.GetSecurityLevel();
crypto_session->GetSecurityLevel();
switch (found_security_level) {
case kSecurityLevelL1:
*query_response = QUERY_VALUE_SECURITY_LEVEL_L1;
@@ -533,7 +534,7 @@ CdmResponseType CdmEngine::QueryStatus(SecurityLevel security_level,
}
} else if (query_token == QUERY_KEY_DEVICE_ID) {
std::string deviceId;
bool got_id = crypto_session.GetExternalDeviceUniqueId(&deviceId);
bool got_id = crypto_session->GetExternalDeviceUniqueId(&deviceId);
metrics_.GetCryptoMetrics()->crypto_session_get_device_unique_id_
.Increment(got_id);
if (!got_id) {
@@ -544,7 +545,7 @@ CdmResponseType CdmEngine::QueryStatus(SecurityLevel security_level,
*query_response = deviceId;
} else if (query_token == QUERY_KEY_SYSTEM_ID) {
uint32_t system_id;
bool got_id = crypto_session.GetSystemId(&system_id);
bool got_id = crypto_session->GetSystemId(&system_id);
if (!got_id) {
LOGW("CdmEngine::QueryStatus: QUERY_KEY_SYSTEM_ID unknown failure");
return UNKNOWN_ERROR;
@@ -555,7 +556,7 @@ CdmResponseType CdmEngine::QueryStatus(SecurityLevel security_level,
*query_response = system_id_stream.str();
} else if (query_token == QUERY_KEY_PROVISIONING_ID) {
std::string provisioning_id;
if (!crypto_session.GetProvisioningId(&provisioning_id)) {
if (!crypto_session->GetProvisioningId(&provisioning_id)) {
LOGW("CdmEngine::QueryStatus: GetProvisioningId failed");
return UNKNOWN_ERROR;
}
@@ -565,7 +566,7 @@ CdmResponseType CdmEngine::QueryStatus(SecurityLevel security_level,
query_token == QUERY_KEY_MAX_HDCP_LEVEL) {
CryptoSession::HdcpCapability current_hdcp;
CryptoSession::HdcpCapability max_hdcp;
if (!crypto_session.GetHdcpCapabilities(&current_hdcp, &max_hdcp)) {
if (!crypto_session->GetHdcpCapabilities(&current_hdcp, &max_hdcp)) {
LOGW("CdmEngine::QueryStatus: GetHdcpCapabilities failed");
return UNKNOWN_ERROR;
}
@@ -574,7 +575,7 @@ CdmResponseType CdmEngine::QueryStatus(SecurityLevel security_level,
current_hdcp : max_hdcp);
} else if (query_token == QUERY_KEY_USAGE_SUPPORT) {
bool supports_usage_reporting;
bool got_info = crypto_session.UsageInformationSupport(
bool got_info = crypto_session->UsageInformationSupport(
&supports_usage_reporting);
if (!got_info) {
@@ -590,7 +591,7 @@ CdmResponseType CdmEngine::QueryStatus(SecurityLevel security_level,
supports_usage_reporting ? QUERY_VALUE_TRUE : QUERY_VALUE_FALSE;
} else if (query_token == QUERY_KEY_NUMBER_OF_OPEN_SESSIONS) {
size_t number_of_open_sessions;
if (!crypto_session.GetNumberOfOpenSessions(&number_of_open_sessions)) {
if (!crypto_session->GetNumberOfOpenSessions(&number_of_open_sessions)) {
LOGW("CdmEngine::QueryStatus: GetNumberOfOpenSessions failed");
return UNKNOWN_ERROR;
}
@@ -600,7 +601,7 @@ CdmResponseType CdmEngine::QueryStatus(SecurityLevel security_level,
*query_response = open_sessions_stream.str();
} else if (query_token == QUERY_KEY_MAX_NUMBER_OF_SESSIONS) {
size_t maximum_number_of_sessions = 0;
if (!crypto_session.GetMaxNumberOfSessions(&maximum_number_of_sessions)) {
if (!crypto_session->GetMaxNumberOfSessions(&maximum_number_of_sessions)) {
LOGW("CdmEngine::QueryStatus: GetMaxNumberOfOpenSessions failed");
return UNKNOWN_ERROR;
}
@@ -610,7 +611,7 @@ CdmResponseType CdmEngine::QueryStatus(SecurityLevel security_level,
*query_response = max_sessions_stream.str();
} else if (query_token == QUERY_KEY_OEMCRYPTO_API_VERSION) {
uint32_t api_version;
if (!crypto_session.GetApiVersion(&api_version)) {
if (!crypto_session->GetApiVersion(&api_version)) {
LOGW("CdmEngine::QueryStatus: GetApiVersion failed");
return UNKNOWN_ERROR;
}
@@ -620,7 +621,7 @@ CdmResponseType CdmEngine::QueryStatus(SecurityLevel security_level,
*query_response = api_version_stream.str();
} else if (query_token == QUERY_KEY_CURRENT_SRM_VERSION) {
uint16_t current_srm_version;
if (!crypto_session.GetSrmVersion(&current_srm_version)) {
if (!crypto_session->GetSrmVersion(&current_srm_version)) {
LOGW("CdmEngine::QueryStatus: GetCurrentSRMVersion failed");
return UNKNOWN_ERROR;
}
@@ -629,7 +630,7 @@ CdmResponseType CdmEngine::QueryStatus(SecurityLevel security_level,
current_srm_version_stream << current_srm_version;
*query_response = current_srm_version_stream.str();
} else if (query_token == QUERY_KEY_SRM_UPDATE_SUPPORT) {
bool is_srm_update_supported = crypto_session.IsSrmUpdateSupported();
bool is_srm_update_supported = crypto_session->IsSrmUpdateSupported();
*query_response =
is_srm_update_supported ? QUERY_VALUE_TRUE : QUERY_VALUE_FALSE;
} else if (query_token == QUERY_KEY_WVCDM_VERSION) {
@@ -833,10 +834,11 @@ CdmResponseType CdmEngine::HandleProvisioningResponse(
if (NULL == cert_provisioning_.get()) {
// Certificate provisioning object has been released. Check if a concurrent
// provisioning attempt has succeeded before declaring failure.
CryptoSession crypto_session(metrics_.GetCryptoMetrics());
scoped_ptr<CryptoSession> crypto_session(
CryptoSession::MakeCryptoSession(metrics_.GetCryptoMetrics()));
CdmResponseType status;
M_TIME(
status = crypto_session.Open(
status = crypto_session->Open(
cert_provisioning_requested_security_level_),
metrics_.GetCryptoMetrics(),
crypto_session_open_,
@@ -848,7 +850,7 @@ CdmResponseType CdmEngine::HandleProvisioningResponse(
"missing and crypto session open failed.");
return EMPTY_PROVISIONING_CERTIFICATE_2;
}
CdmSecurityLevel security_level = crypto_session.GetSecurityLevel();
CdmSecurityLevel security_level = crypto_session->GetSecurityLevel();
if (!IsProvisioned(security_level)) {
LOGE(
"CdmEngine::HandleProvisioningResponse: provisioning object "
@@ -888,9 +890,10 @@ bool CdmEngine::IsProvisioned(CdmSecurityLevel security_level) {
CdmResponseType CdmEngine::Unprovision(CdmSecurityLevel security_level) {
// Devices with baked-in DRM certs cannot be reprovisioned and therefore must
// not be unprovisioned.
CryptoSession crypto_session(metrics_.GetCryptoMetrics());
scoped_ptr<CryptoSession> crypto_session(
CryptoSession::MakeCryptoSession(metrics_.GetCryptoMetrics()));
CdmClientTokenType token_type = kClientTokenUninitialized;
CdmResponseType res = crypto_session.GetProvisioningMethod(
CdmResponseType res = crypto_session->GetProvisioningMethod(
security_level == kSecurityLevelL3 ? kLevel3 : kLevelDefault,
&token_type);
if (res != NO_ERROR) {
@@ -921,10 +924,11 @@ CdmResponseType CdmEngine::Unprovision(CdmSecurityLevel security_level) {
}
CdmResponseType CdmEngine::DeleteUsageTable(CdmSecurityLevel security_level) {
CryptoSession crypto_session(metrics_.GetCryptoMetrics());
scoped_ptr<CryptoSession> crypto_session(
CryptoSession::MakeCryptoSession(metrics_.GetCryptoMetrics()));
CdmResponseType status;
M_TIME(
status = crypto_session.Open(
status = crypto_session->Open(
security_level == kSecurityLevelL3 ?
kLevel3 :
kLevelDefault),
@@ -937,7 +941,7 @@ CdmResponseType CdmEngine::DeleteUsageTable(CdmSecurityLevel security_level) {
status);
return UNPROVISION_ERROR_4;
}
status = crypto_session.DeleteAllUsageReports();
status = crypto_session->DeleteAllUsageReports();
metrics_.GetCryptoMetrics()->crypto_session_delete_all_usage_reports_
.Increment(status);
if (status != NO_ERROR) {
@@ -1004,7 +1008,7 @@ CdmResponseType CdmEngine::DeleteUsageRecord(const std::string& app_id,
// Got provider token. Remove from OEMCrypto.
scoped_ptr<CryptoSession> crypto_session(
new CryptoSession(metrics_.GetCryptoMetrics()));
CryptoSession::MakeCryptoSession(metrics_.GetCryptoMetrics()));
CdmResponseType status = crypto_session->Open(
security_level == kSecurityLevelL3 ? kLevel3 : kLevelDefault);
if (status == NO_ERROR) {
@@ -1213,7 +1217,7 @@ CdmResponseType CdmEngine::RemoveAllUsageInfo(
// Got at least one provider token. Remove from OEMCrypto.
scoped_ptr<CryptoSession> crypto_session(
new CryptoSession(metrics_.GetCryptoMetrics()));
CryptoSession::MakeCryptoSession(metrics_.GetCryptoMetrics()));
CdmResponseType status = crypto_session->Open(
security_level == kSecurityLevelL3 ? kLevel3 : kLevelDefault);
if (status == NO_ERROR) {
@@ -1367,7 +1371,7 @@ CdmResponseType CdmEngine::RemoveUsageInfo(
DeviceFiles::GetUsageInfoFileName(app_id),
provider_session_token);
scoped_ptr<CryptoSession> crypto_session(
new CryptoSession(metrics_.GetCryptoMetrics()));
CryptoSession::MakeCryptoSession(metrics_.GetCryptoMetrics()));
status = crypto_session->Open(
static_cast<CdmSecurityLevel>(j) == kSecurityLevelL3
? kLevel3 : kLevelDefault);
@@ -1805,7 +1809,7 @@ void CdmEngine::DeleteAllUsageReportsUponFactoryReset() {
if (!file_system_->Exists(device_base_path_level1) &&
!file_system_->Exists(device_base_path_level3)) {
scoped_ptr<CryptoSession> crypto_session(
new CryptoSession(metrics_.GetCryptoMetrics()));
CryptoSession::MakeCryptoSession(metrics_.GetCryptoMetrics()));
CdmResponseType status;
M_TIME(
status = crypto_session->Open(

View File

@@ -49,7 +49,7 @@ CdmSession::CdmSession(FileSystem* file_system,
mock_policy_engine_in_use_(false) {
assert(metrics_); // metrics_ must not be null.
crypto_metrics_ = metrics_->GetCryptoMetrics();
crypto_session_.reset(new CryptoSession(crypto_metrics_));
crypto_session_.reset(CryptoSession::MakeCryptoSession(crypto_metrics_));
life_span_.Start();
}
@@ -734,7 +734,7 @@ CdmResponseType CdmSession::DeleteUsageEntry(uint32_t usage_entry_number) {
// it, so close and reopen session.
CdmResponseType sts;
crypto_session_->Close();
crypto_session_.reset(new CryptoSession(crypto_metrics_));
crypto_session_.reset(CryptoSession::MakeCryptoSession(crypto_metrics_));
M_TIME(sts = crypto_session_->Open(requested_security_level_),
crypto_metrics_, crypto_session_open_, sts, requested_security_level_);
if (sts != NO_ERROR) return sts;
@@ -870,7 +870,7 @@ bool CdmSession::StoreLicense(DeviceFiles::LicenseState state) {
CdmResponseType CdmSession::RemoveKeys() {
CdmResponseType sts;
crypto_session_.reset(new CryptoSession(crypto_metrics_));
crypto_session_.reset(CryptoSession::MakeCryptoSession(crypto_metrics_));
// Ignore errors
M_TIME(
sts = crypto_session_->Open(requested_security_level_),

View File

@@ -142,7 +142,7 @@ bool CertificateProvisioning::SetSpoidParameter(
} else if (origin != EMPTY_ORIGIN) {
// Legacy behavior - Concatenate Unique ID with Origin
std::string device_unique_id;
if (!crypto_session_.GetInternalDeviceUniqueId(&device_unique_id)) {
if (!crypto_session_->GetInternalDeviceUniqueId(&device_unique_id)) {
LOGE("CertificateProvisioning::SetSpoidParameter: Failure getting "
"device unique ID");
return false;
@@ -158,7 +158,7 @@ bool CertificateProvisioning::SetSpoidParameter(
*/
SignedProvisioningMessage::ProtocolVersion
CertificateProvisioning::GetProtocolVersion() {
if (crypto_session_.GetPreProvisionTokenType() == kClientTokenOemCert)
if (crypto_session_->GetPreProvisionTokenType() == kClientTokenOemCert)
return SignedProvisioningMessage::VERSION_3;
else
return SignedProvisioningMessage::VERSION_2;
@@ -183,7 +183,7 @@ CdmResponseType CertificateProvisioning::GetProvisioningRequest(
default_url->assign(kProvisioningServerUrl);
CdmResponseType status = crypto_session_.Open(requested_security_level);
CdmResponseType status = crypto_session_->Open(requested_security_level);
if (NO_ERROR != status) {
LOGE("GetProvisioningRequest: fails to create a crypto session");
return status;
@@ -193,7 +193,7 @@ CdmResponseType CertificateProvisioning::GetProvisioningRequest(
ProvisioningRequest provisioning_request;
wvcdm::ClientIdentification id;
status = id.Init(&crypto_session_);
status = id.Init(crypto_session_.get());
if (status != NO_ERROR) return status;
video_widevine::ClientIdentification* client_id =
@@ -212,12 +212,12 @@ CdmResponseType CertificateProvisioning::GetProvisioningRequest(
// Encrypt client identification
EncryptedClientIdentification* encrypted_client_id =
provisioning_request.mutable_encrypted_client_id();
status = service_certificate_->EncryptClientId(&crypto_session_, client_id,
encrypted_client_id);
status = service_certificate_->EncryptClientId(
crypto_session_.get(), client_id, encrypted_client_id);
provisioning_request.clear_client_id();
uint32_t nonce;
if (!crypto_session_.GenerateNonce(&nonce)) {
if (!crypto_session_->GenerateNonce(&nonce)) {
LOGE("GetProvisioningRequest: fails to generate a nonce");
return CERT_PROVISIONING_NONCE_GENERATION_ERROR;
}
@@ -254,7 +254,7 @@ CdmResponseType CertificateProvisioning::GetProvisioningRequest(
// Derives signing and encryption keys and constructs signature.
std::string request_signature;
if (!crypto_session_.PrepareRequest(serialized_message, true,
if (!crypto_session_->PrepareRequest(serialized_message, true,
&request_signature)) {
LOGE("GetProvisioningRequest: fails to prepare request");
return CERT_PROVISIONING_REQUEST_ERROR_3;
@@ -351,7 +351,7 @@ CdmResponseType CertificateProvisioning::HandleProvisioningResponse(
// If Provisioning 3.0 (OEM Cert provisioned), verify that the
// message is properly signed.
if (crypto_session_.GetPreProvisionTokenType() == kClientTokenOemCert) {
if (crypto_session_->GetPreProvisionTokenType() == kClientTokenOemCert) {
if (service_certificate_->VerifySignedMessage(signed_message, signature)
!= NO_ERROR) {
// TODO(b/69562876): if the cert is bad, request a new one.
@@ -369,14 +369,14 @@ CdmResponseType CertificateProvisioning::HandleProvisioningResponse(
std::string wrapped_private_key;
if (!crypto_session_.RewrapCertificate(signed_message, signature, nonce,
if (!crypto_session_->RewrapCertificate(signed_message, signature, nonce,
new_private_key, iv, wrapping_key,
&wrapped_private_key)) {
LOGE("HandleProvisioningResponse: RewrapCertificate fails");
return CERT_PROVISIONING_RESPONSE_ERROR_6;
}
crypto_session_.Close();
crypto_session_->Close();
if (cert_type_ == kCertificateX509) {
*cert = provisioning_response.device_certificate();
@@ -391,7 +391,7 @@ CdmResponseType CertificateProvisioning::HandleProvisioningResponse(
provisioning_response.device_certificate();
DeviceFiles handle(file_system);
if (!handle.Init(crypto_session_.GetSecurityLevel())) {
if (!handle.Init(crypto_session_->GetSecurityLevel())) {
LOGE("HandleProvisioningResponse: failed to init DeviceFiles");
return CERT_PROVISIONING_RESPONSE_ERROR_7;
}

View File

@@ -2574,4 +2574,20 @@ void CryptoSession::IncrementIV(uint64_t increase_by,
(*counter_buffer) = htonll64(ntohll64(*counter_buffer) + increase_by);
}
// The factory will either be set by WvCdmTestBase, or a default factory is
// created on the first call to MakeCryptoSession.
scoped_ptr<CryptoSessionFactory> CryptoSession::factory_(NULL);
CryptoSession* CryptoSession::MakeCryptoSession(
metrics::CryptoMetrics* crypto_metrics) {
// If the factory_ has not been set, then use a default factory.
if (factory_.get() == NULL) factory_.reset(new CryptoSessionFactory());
return factory_->MakeCryptoSession(crypto_metrics);
}
CryptoSession* CryptoSessionFactory::MakeCryptoSession(
metrics::CryptoMetrics* crypto_metrics) {
return new CryptoSession(crypto_metrics);
}
} // namespace wvcdm

View File

@@ -143,6 +143,7 @@ static std::vector<CryptoKey> ExtractContentKeys(const License& license) {
key.set_key_id(license.key(i).id());
// Strip off PKCS#5 padding - since we know the key is 16 or 32 bytes,
// the padding will always be 16 bytes.
// TODO(b/111069024): remove this!
if (license.key(i).key().size() > 16) {
length = license.key(i).key().size() - 16;
} else {

View File

@@ -285,7 +285,7 @@ CdmResponseType UsageTableHeader::MoveEntry(
scoped_ptr<CryptoSession> scoped_crypto_session;
CryptoSession* crypto_session = test_crypto_session_.get();
if (crypto_session == NULL) {
scoped_crypto_session.reset((new CryptoSession(metrics)));
scoped_crypto_session.reset((CryptoSession::MakeCryptoSession(metrics)));
crypto_session = scoped_crypto_session.get();
}
@@ -479,7 +479,7 @@ CdmResponseType UsageTableHeader::Shrink(
scoped_ptr<CryptoSession> scoped_crypto_session;
CryptoSession* crypto_session = test_crypto_session_.get();
if (crypto_session == NULL) {
scoped_crypto_session.reset((new CryptoSession(metrics)));
scoped_crypto_session.reset((CryptoSession::MakeCryptoSession(metrics)));
crypto_session = scoped_crypto_session.get();
}
@@ -549,32 +549,33 @@ bool UsageTableHeader::UpgradeLicensesFromUsageTable(
if (provider_session_token.empty()) continue;
CryptoSession crypto_session(metrics);
CdmResponseType status = crypto_session.Open(requested_security_level_);
scoped_ptr<CryptoSession> crypto_session(
CryptoSession::MakeCryptoSession(metrics));
CdmResponseType status = crypto_session->Open(requested_security_level_);
if (status != NO_ERROR) continue;
// We create this entry since OEMCrypto_CopyOldUsageEntry needs the old
// usage table to be loaded in order to copy an entry.
if (!CreateDummyOldUsageEntry(&crypto_session)) continue;
if (!CreateDummyOldUsageEntry(crypto_session.get())) continue;
status = AddEntry(&crypto_session, true /* persistent license */,
status = AddEntry(crypto_session.get(), true /* persistent license */,
key_set_ids[i], kEmptyString, &usage_entry_number);
if (status != NO_ERROR) continue;
status = crypto_session.CopyOldUsageEntry(provider_session_token);
status = crypto_session->CopyOldUsageEntry(provider_session_token);
if (status != NO_ERROR) {
crypto_session.Close();
crypto_session->Close();
Shrink(metrics, 1);
continue;
}
status = UpdateEntry(&crypto_session, &usage_entry);
status = UpdateEntry(crypto_session.get(), &usage_entry);
if (status != NO_ERROR) {
crypto_session.Close();
crypto_session->Close();
Shrink(metrics, 1);
continue;
}
@@ -631,36 +632,37 @@ bool UsageTableHeader::UpgradeUsageInfoFromUsageTable(
continue;
}
CryptoSession crypto_session(metrics);
CdmResponseType status = crypto_session.Open(requested_security_level_);
scoped_ptr<CryptoSession> crypto_session(
CryptoSession::MakeCryptoSession(metrics));
CdmResponseType status = crypto_session->Open(requested_security_level_);
if (status != NO_ERROR) continue;
// We create this entry since OEMCrypto_CopyOldUsageEntry needs the old
// usage table to be loaded in order to copy an entry.
if (!CreateDummyOldUsageEntry(&crypto_session)) continue;
if (!CreateDummyOldUsageEntry(crypto_session.get())) continue;
// TODO(rfrias): We need to fill in the app id, but it is hashed
// and we have no way to extract. Use the hased filename instead?
status = AddEntry(&crypto_session, false /* usage info */,
status = AddEntry(crypto_session.get(), false /* usage info */,
usage_data[j].key_set_id, usage_info_file_names[i],
&(usage_data[j].usage_entry_number));
if (status != NO_ERROR) continue;
status = crypto_session.CopyOldUsageEntry(
status = crypto_session->CopyOldUsageEntry(
usage_data[j].provider_session_token);
if (status != NO_ERROR) {
crypto_session.Close();
crypto_session->Close();
Shrink(metrics, 1);
continue;
}
status = UpdateEntry(&crypto_session, &(usage_data[j].usage_entry));
status = UpdateEntry(crypto_session.get(), &(usage_data[j].usage_entry));
if (status != NO_ERROR) {
crypto_session.Close();
crypto_session->Close();
Shrink(metrics, 1);
continue;
}

View File

@@ -23,6 +23,7 @@
#include "properties.h"
#include "scoped_ptr.h"
#include "string_conversions.h"
#include "test_base.h"
#include "test_printers.h"
#include "url_request.h"
#include "wv_cdm_constants.h"
@@ -116,7 +117,7 @@ bool ExtractSignedMessage(const std::string& response,
} // namespace
class WvCdmEnginePreProvTest : public testing::Test {
class WvCdmEnginePreProvTest : public WvCdmTestBase {
public:
WvCdmEnginePreProvTest() : cdm_engine_(&file_system_),
session_opened_(false) {}
@@ -124,6 +125,7 @@ class WvCdmEnginePreProvTest : public testing::Test {
virtual ~WvCdmEnginePreProvTest() {}
virtual void SetUp() {
WvCdmTestBase::SetUp();
session_opened_ = false;
}
@@ -188,9 +190,6 @@ class WvCdmEnginePreProvTest : public testing::Test {
CdmCertificateType cert_type = kCertificateWidevine;
std::string cert_authority;
std::string cert, wrapped_key;
// Keep a crypto session alive so that OEMCrypto won't terminate while we
// try to provision. This is needed for testing nonce floods.
CryptoSession keep_alive(cdm_engine_.GetMetrics()->GetCryptoMetrics());
CdmResponseType result = NO_ERROR;
for(int i = 0; i < 2; ++i) { // Retry once if there is a nonce problem.
@@ -372,19 +371,9 @@ class WvCdmEngineTest : public WvCdmEnginePreProvTest {
CdmKeyRequest key_request;
CdmResponseType result = NO_ERROR;
for(int i=0; i < 2; i++) { // Retry once if there is a nonce problem.
result = cdm_engine_.GenerateKeyRequest(
session_id_, key_set_id, init_data,
kLicenseTypeStreaming, app_parameters,
&key_request);
if (result == LICENSE_REQUEST_NONCE_GENERATION_ERROR) {
LOGW("Woops. Nonce problem. Try again?");
sleep(1);
} else {
break;
}
}
CdmResponseType result = cdm_engine_.GenerateKeyRequest(
session_id_, key_set_id, init_data, kLicenseTypeStreaming,
app_parameters, &key_request);
EXPECT_EQ(KEY_MESSAGE, result);
key_msg_ = key_request.message;
@@ -393,16 +382,8 @@ class WvCdmEngineTest : public WvCdmEnginePreProvTest {
void GenerateRenewalRequest() {
CdmKeyRequest request;
CdmResponseType result = NO_ERROR;
for (int i = 0; i < 2; i++) { // Retry once if there is a nonce problem.
result = cdm_engine_.GenerateRenewalRequest(session_id_, &request);
if (result == LICENSE_RENEWAL_NONCE_GENERATION_ERROR) {
LOGW("Woops. Nonce problem. Try again?");
sleep(1);
} else {
break;
}
}
CdmResponseType result =
cdm_engine_.GenerateRenewalRequest(session_id_, &request);
EXPECT_EQ(KEY_MESSAGE, result);
key_msg_ = request.message;

View File

@@ -11,6 +11,7 @@
#include "scoped_ptr.h"
#include "service_certificate.h"
#include "string_conversions.h"
#include "test_base.h"
#include "test_printers.h"
#include "usage_table_header.h"
#include "wv_cdm_constants.h"
@@ -122,10 +123,10 @@ class MockUsageTableHeader : public UsageTableHeader {
CdmUsageEntry* usage_entry));
};
class MockCryptoSession : public CryptoSession {
class MockCryptoSession : public TestCryptoSession {
public:
MockCryptoSession(metrics::CryptoMetrics* crypto_metrics)
: CryptoSession(crypto_metrics) {
: TestCryptoSession(crypto_metrics) {
// By default, call the concrete implementation of GetUsageSupportType.
ON_CALL(*this, GetUsageSupportType(_))
.WillByDefault(
@@ -167,9 +168,10 @@ class MockCdmLicense : public CdmLicense {
} // namespace
class CdmSessionTest : public ::testing::Test {
class CdmSessionTest : public WvCdmTestBase {
protected:
virtual void SetUp() {
WvCdmTestBase::SetUp();
cdm_session_.reset(new CdmSession(NULL, &metrics_));
// Inject testing mocks.
license_parser_ = new MockCdmLicense(cdm_session_->session_id());

View File

@@ -15,6 +15,8 @@
#include "metrics.pb.h"
#include "metrics_collections.h"
#include "scoped_ptr.h"
#include "test_base.h"
#include "test_printers.h"
#include "wv_cdm_types.h"
namespace {
@@ -240,10 +242,10 @@ const uint32_t kOemCertSystemId = 7346;
namespace wvcdm {
class CryptoSessionForTest : public CryptoSession, public testing::Test {
class CryptoSessionForTest : public TestCryptoSession, public WvCdmTestBase {
public:
using CryptoSession::ExtractSystemIdFromOemCert;
CryptoSessionForTest() : CryptoSession(metrics_.GetCryptoMetrics()) {}
CryptoSessionForTest() : TestCryptoSession(metrics_.GetCryptoMetrics()) {}
void SetUp() {}
@@ -264,7 +266,7 @@ TEST(CryptoSessionTest, CanExtractSystemIdFromOemCertificate) {
ASSERT_EQ(kOemCertSystemId, system_id);
}
class CryptoSessionMetricsTest : public testing::Test {
class CryptoSessionMetricsTest : public WvCdmTestBase {
protected:
uint32_t FindKeyboxSystemID() {
OEMCryptoResult sts;
@@ -280,11 +282,12 @@ class CryptoSessionMetricsTest : public testing::Test {
TEST_F(CryptoSessionMetricsTest, OpenSessionValidMetrics) {
metrics::CryptoMetrics crypto_metrics;
CryptoSession session(&crypto_metrics);
session.Open(wvcdm::kLevelDefault);
scoped_ptr<CryptoSession> session(
CryptoSession::MakeCryptoSession(&crypto_metrics));
session->Open(wvcdm::kLevelDefault);
// Exercise a method that will touch a metric.
CdmUsageSupportType usage_type;
ASSERT_EQ(NO_ERROR, session.GetUsageSupportType(&usage_type));
ASSERT_EQ(NO_ERROR, session->GetUsageSupportType(&usage_type));
drm_metrics::WvCdmMetrics::CryptoMetrics metrics_proto;
crypto_metrics.Serialize(&metrics_proto);
@@ -305,7 +308,7 @@ TEST_F(CryptoSessionMetricsTest, OpenSessionValidMetrics) {
metrics_proto.oemcrypto_usage_table_support().int_value());
// Validate metrics that differ based on provisioning type.
CdmClientTokenType token_type = session.GetPreProvisionTokenType();
CdmClientTokenType token_type = session->GetPreProvisionTokenType();
if (token_type == kClientTokenKeybox) {
uint32_t system_id = FindKeyboxSystemID();
@@ -337,18 +340,19 @@ TEST_F(CryptoSessionMetricsTest, OpenSessionValidMetrics) {
TEST_F(CryptoSessionMetricsTest, GetProvisioningTokenValidMetrics) {
metrics::CryptoMetrics crypto_metrics;
CryptoSession session(&crypto_metrics);
scoped_ptr<CryptoSession> session(
CryptoSession::MakeCryptoSession(&crypto_metrics));
ASSERT_EQ(NO_ERROR, session.Open(wvcdm::kLevelDefault));
ASSERT_EQ(NO_ERROR, session->Open(wvcdm::kLevelDefault));
CdmClientTokenType token_type = session.GetPreProvisionTokenType();
CdmClientTokenType token_type = session->GetPreProvisionTokenType();
LOGI("token_type: %d", token_type);
// DRM Certificate provisioning method does not support a provisioning
// token. Otherwise, we should be able to fetch the token.
std::string token;
if (token_type != kClientTokenDrmCert) {
ASSERT_TRUE(session.GetProvisioningToken(&token));
ASSERT_TRUE(session->GetProvisioningToken(&token));
}
drm_metrics::WvCdmMetrics::CryptoMetrics metrics_proto;

View File

@@ -15,6 +15,8 @@
#include "properties.h"
#include "service_certificate.h"
#include "string_conversions.h"
#include "test_base.h"
#include "test_printers.h"
#include "wv_cdm_constants.h"
namespace wvcdm {
@@ -142,10 +144,10 @@ const CryptoSession::SupportedCertificateTypes kDefaultSupportedCertTypes = {
true
};
class MockCryptoSession : public CryptoSession {
class MockCryptoSession : public TestCryptoSession {
public:
MockCryptoSession(metrics::CryptoMetrics* crypto_metrics)
: CryptoSession(crypto_metrics) { }
: TestCryptoSession(crypto_metrics) { }
MOCK_METHOD0(IsOpen, bool());
MOCK_METHOD1(GenerateRequestId, bool(std::string*));
MOCK_METHOD1(UsageInformationSupport, bool(bool*));
@@ -194,11 +196,12 @@ using ::testing::Return;
using ::testing::SetArgPointee;
using ::testing::UnorderedElementsAre;
class CdmLicenseTest : public ::testing::Test {
class CdmLicenseTest : public WvCdmTestBase {
protected:
CdmLicenseTest(const std::string& pssh = (kCencInitDataHdr + kCencPssh))
: pssh_(pssh) {}
virtual void SetUp() {
WvCdmTestBase::SetUp();
clock_ = new MockClock();
crypto_session_ = new MockCryptoSession(&crypto_metrics_);
init_data_ = new MockInitializationData(CENC_INIT_DATA_FORMAT, pssh_);

View File

@@ -11,6 +11,8 @@
#include "policy_engine.h"
#include "mock_clock.h"
#include "scoped_ptr.h"
#include "test_base.h"
#include "test_printers.h"
#include "wv_cdm_event_listener.h"
#include "wv_cdm_types.h"
@@ -62,10 +64,10 @@ const OutputProtection::HDCP kHdcpV2_1 = OutputProtection::HDCP_V2_1;
// should match kHdcpCheckInterval in policy_engine.cpp
const int64_t kHdcpInterval = 10;
class HdcpOnlyMockCryptoSession : public CryptoSession {
class HdcpOnlyMockCryptoSession : public TestCryptoSession {
public:
HdcpOnlyMockCryptoSession(metrics::CryptoMetrics* crypto_metrics)
: CryptoSession(crypto_metrics) { }
: TestCryptoSession(crypto_metrics) { }
MOCK_METHOD2(GetHdcpCapabilities, bool(HdcpCapability*, HdcpCapability*));
};
@@ -82,7 +84,7 @@ class MockCdmEventListener : public WvCdmEventListener {
} // namespace
class PolicyEngineConstraintsTest : public Test {
class PolicyEngineConstraintsTest : public WvCdmTestBase {
public:
PolicyEngineConstraintsTest() :
crypto_session_(&dummy_metrics_) {
@@ -90,6 +92,7 @@ class PolicyEngineConstraintsTest : public Test {
protected:
virtual void SetUp() {
WvCdmTestBase::SetUp();
current_time_ = 0;
policy_engine_.reset(new PolicyEngine(kSessionId, &mock_event_listener_,

View File

@@ -14,6 +14,7 @@
#include "mock_clock.h"
#include "policy_engine.h"
#include "scoped_ptr.h"
#include "test_base.h"
#include "test_printers.h"
#include "wv_cdm_event_listener.h"
#include "wv_cdm_constants.h"
@@ -57,10 +58,10 @@ int64_t ParseInt(const std::string& str) {
return ret;
}
class HdcpOnlyMockCryptoSession : public CryptoSession {
class HdcpOnlyMockCryptoSession : public TestCryptoSession {
public:
HdcpOnlyMockCryptoSession(metrics::CryptoMetrics* metrics) :
CryptoSession(metrics) {}
TestCryptoSession(metrics) {}
MOCK_METHOD2(GetHdcpCapabilities, bool(HdcpCapability*, HdcpCapability*));
bool DoRealGetHdcpCapabilities(HdcpCapability* current,
@@ -99,12 +100,13 @@ using ::testing::Return;
using ::testing::StrictMock;
using ::testing::UnorderedElementsAre;
class PolicyEngineTest : public ::testing::Test {
class PolicyEngineTest : public WvCdmTestBase {
public:
PolicyEngineTest() : crypto_session_(&dummy_metrics_) {
}
protected:
virtual void SetUp() {
WvCdmTestBase::SetUp();
policy_engine_.reset(
new PolicyEngine(kSessionId, &mock_event_listener_,
&crypto_session_));

View File

@@ -0,0 +1,82 @@
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine Master
// License Agreement.
// This file adds some print methods so that when unit tests fail, the
// will print the name of an enumeration instead of the numeric value.
#include "test_base.h"
#include <getopt.h>
#include <openssl/aes.h>
#include <openssl/bio.h>
#include <openssl/cmac.h>
#include <openssl/err.h>
#include "cdm_engine.h"
#include "crypto_session.h"
#include "file_store.h"
#include "log.h"
#include "oec_device_features.h"
#include "oec_test_data.h"
#include "properties.h"
#include "test_printers.h"
#include "url_request.h"
namespace wvcdm {
TestCryptoSession::TestCryptoSession(metrics::CryptoMetrics* crypto_metrics)
: CryptoSession(crypto_metrics) {
// The first CryptoSession should have initialized OEMCrypto. This is right
// after that, so should tell oemcrypto to use a test keybox.
if (session_count() == 1) {
WvCdmTestBase::InstallTestRootOfTrust();
}
}
bool TestCryptoSession::GenerateNonce(uint32_t* nonce) {
for (int i = 0; !CryptoSession::GenerateNonce(nonce); i++) {
LOGV("Recovering from nonce flood.");
if (i > 2) return false;
sleep(1);
}
return true;
}
class TestCryptoSessionFactory : public CryptoSessionFactory {
CryptoSession* MakeCryptoSession(metrics::CryptoMetrics* crypto_metrics) {
return new TestCryptoSession(crypto_metrics);
}
};
void WvCdmTestBase::SetUp() {
::testing::Test::SetUp();
Properties::Init();
const ::testing::TestInfo* const test_info =
::testing::UnitTest::GetInstance()->current_test_info();
LOGD("Running test %s.%s", test_info->test_case_name(), test_info->name());
CryptoSession::SetCryptoSessionFactory(new TestCryptoSessionFactory());
// TODO(fredgc): Add a test version of DeviceFiles.
}
void WvCdmTestBase::InstallTestRootOfTrust() {
switch (wvoec::global_features.derive_key_method) {
case wvoec::DeviceFeatures::LOAD_TEST_KEYBOX:
ASSERT_EQ(OEMCrypto_SUCCESS,
OEMCrypto_LoadTestKeybox(
reinterpret_cast<const uint8_t*>(&wvoec::kTestKeybox),
sizeof(wvoec::kTestKeybox)));
break;
case wvoec::DeviceFeatures::LOAD_TEST_RSA_KEY:
// Rare case: used by devices with baked in DRM cert.
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_LoadTestRSAKey());
break;
case wvoec::DeviceFeatures::TEST_PROVISION_30:
// Can use oem certificate to install test rsa key.
break;
default:
FAIL() << "Cannot run test without test keybox or RSA key installed.";
}
}
} // namespace wvcdm

View File

@@ -0,0 +1,37 @@
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine Master
// License Agreement.
#ifndef WVCDM_CORE_TEST_BASE_H_
#define WVCDM_CORE_TEST_BASE_H_
#include <gtest/gtest.h>
#include "config_test_env.h"
#include "crypto_session.h"
#include "metrics_collections.h"
namespace wvcdm {
// This is the base class for Widevine CDM integration tests. It's main use is
// to configure OEMCrypto to use a test keybox.
class WvCdmTestBase : public ::testing::Test {
public:
WvCdmTestBase() {}
virtual ~WvCdmTestBase() {}
virtual void SetUp();
// Install a test keybox, if appropriate.
static void InstallTestRootOfTrust();
};
class TestCryptoSession : public CryptoSession {
public:
explicit TestCryptoSession(metrics::CryptoMetrics* crypto_metrics);
// This intercepts nonce flood errors, which is useful for tests that request
// many nonces and are not time critical.
bool GenerateNonce(uint32_t* nonce);
};
} // namespace wvcdm
#endif // WVCDM_CORE_TEST_BASE_H_

View File

@@ -11,6 +11,8 @@
#include "crypto_session.h"
#include "device_files.h"
#include "file_store.h"
#include "test_base.h"
#include "test_printers.h"
#include "wv_cdm_constants.h"
#include "wv_cdm_types.h"
@@ -225,10 +227,10 @@ class MockDeviceFiles : public DeviceFiles {
FileSystem file_system_;
};
class MockCryptoSession : public CryptoSession {
class MockCryptoSession : public TestCryptoSession {
public:
MockCryptoSession(metrics::CryptoMetrics* metrics)
: CryptoSession(metrics) {}
: TestCryptoSession(metrics) {}
MOCK_METHOD1(Open, CdmResponseType(SecurityLevel));
MOCK_METHOD1(LoadUsageTableHeader,
CdmResponseType(const CdmUsageTableHeader&));
@@ -255,7 +257,7 @@ using ::testing::StrEq;
using ::testing::UnorderedElementsAre;
using ::testing::UnorderedElementsAreArray;
class UsageTableHeaderTest : public ::testing::Test {
class UsageTableHeaderTest : public WvCdmTestBase {
public:
static void SetUpTestCase() {
InitVectorConstants();
@@ -263,6 +265,7 @@ class UsageTableHeaderTest : public ::testing::Test {
protected:
virtual void SetUp() {
WvCdmTestBase::SetUp();
// UsageTableHeader will take ownership of the pointer
device_files_ = new MockDeviceFiles();
crypto_session_ = new MockCryptoSession(&crypto_metrics_);

View File

@@ -188,6 +188,14 @@ bool StringToInt64(const std::string& input, int64_t* output) {
} // namespace
// GTest requires PrintTo to be in the same namespace as the thing it prints,
// which is std::vector in this case.
namespace std {
void PrintTo(const vector<uint8_t>& value, ostream* os) {
*os << wvcdm::b2a_hex(value);
}
} // namespace std
using ::testing::Contains;
using ::testing::Pair;
using ::testing::StrNe;

View File

@@ -48,8 +48,6 @@ namespace {
// HTTP response codes.
const int kHttpOk = 200;
const int kHttpBadRequest = 400;
const int kHttpInternalServerError = 500;
const wvcdm::CdmIdentifier kExampleIdentifier = {
wvcdm::EMPTY_SPOID,

View File

@@ -1,21 +0,0 @@
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine Master
// License Agreement.
#include <gtest/gtest.h>
namespace wvcdm {
class WvCdmTestBase : public ::testing::Test {
public:
WvCdmTestBase() {}
virtual ~WvCdmTestBase() {}
protected:
virtual void SetUp() {
const ::testing::TestInfo* const test_info =
::testing::UnitTest::GetInstance()->current_test_info();
LOGD("Running test %s.%s", test_info->test_case_name(),
test_info->name());
}
};
} // namespace wvcdm

View File

@@ -11,9 +11,11 @@ LOCAL_MODULE_TAGS := tests
LOCAL_SRC_FILES := \
$(test_src_dir)/$(test_name).cpp \
../../oemcrypto/test/oec_device_features.cpp \
../core/test/config_test_env.cpp \
../core/test/http_socket.cpp \
../core/test/license_request.cpp \
../core/test/test_base.cpp \
../core/test/test_printers.cpp \
../core/test/url_request.cpp
@@ -25,6 +27,7 @@ LOCAL_C_INCLUDES := \
vendor/widevine/libwvdrmengine/cdm/metrics/include \
vendor/widevine/libwvdrmengine/cdm/util/include \
vendor/widevine/libwvdrmengine/oemcrypto/include \
vendor/widevine/libwvdrmengine/oemcrypto/test \
LOCAL_C_INCLUDES += external/protobuf/src