diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..5c3332f7 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,101 @@ +## 3.0.4 (2015-12-14) + +Features: + - Enforce storage restrictions based on the license type and policy + - Updated to EME spec 2015-11-20 + - Updated kPersistent to kPersistentLicense + - Updated kInvalidAccess with kTypeError and kRangeError + - Updated kOutputNotAllowed to kOutputRestricted + - Added key status kReleased + - Added new session type (kPersistentUsageRecord) used for "secure stop" + - Enabled WebM-related tests for CdmEngine + +Bugfixes: + - Fixed OEMCrypto test bugs regarding nonce-enabled and nonce-or-entry flags + - Fixed build system bug to allow adding the static CDM library as a + dependency of another gyp static library target + - Fixed message type for service cert requests + - Fixed reporting of expiration for sessions which do not expire + - Fixed test bugs in which changing execution order caused test failures + - Fixed bug in OEMCrypto\_DeleteUsageTable in which the empty table was not + written to disk + - Fixed bug in CE CDM tests in which OEMCrypto usage table data was not + cleared between test runs, causing issues with duplicate PSTs + + +## 3.0.3 (2015-11-09) + +Features: + - Added x86-32 build settings + +Bugfixes: + - Fix buffer overflow in mock OEMCrypto on 32-bit systems + - Fixed OEMCrypto\_RefreshKeys return value + - Fixed OEMCrypto\_GenerateRSASignature return value + - Fixed assertions during server certificate provisioning, triggered by a + race condition + - Removed spurious error messages from CdmEngine::AddKey() + - Fixed PSS verification in iOS privacy crypto implementation + + +## 3.0.2 (2015-09-18) + +Features: + - Updated OEMCrypto docs + - Privacy crypto implementation for iOS + - Now builds with strict warnings and warnings as errors + - Added an extra method to IEventListener to allow integration with older + versions of Chromium using prefixed EME + - *NOTE: This is temporary and will be removed in a future release* + +Bugfixes: + - Fixed support for C++11 and clang + - Prevent renewal license when can\_renew is false + - Fixed variable-length key ID tests + - Fixed enforcement of secure buffer types for decrypt + - Fix type-casting issues with various versions of OpenSSL and BoringSSL + - Return kNotSupported when generateRequest called with non-Widevine initdata + + +## 3.0.1 (2015-09-11) + +Features: + - Added new methods to access app parameters available on Android + - Test suite is now IPv6-ready + - Exposed IClient inheritance for Cdm interface + - Added baked-in cert support to the mock OEMCrypto + +Bugfixes: + - Made improvements to tests for OEMCrypto and core + - Return client ID information in secure stop + - Fix multiple deletions of OEMCrypto usage table entries + - Don't delete offline licenses when a new device cert is provisioned + - Hardened BufferReader class + - Removed excess logging in PSSH parser + - Fixed iOS build issues with MD5 in DeviceFiles + - Fixed iOS build issues with protobuf\_config==target + - Fixed bugs in OEMCrypto v9 and v10 adapters + - Fixed inclusion of unit test gypis from external projects + +Broken compatibility: + - Added a cancel() method to ITimer, needed for some timer implementations + + +## 3.0.0 (2015-06-19) + +v3.0 introduced a completely new interface which is not backward compatible +with v2.x. + +Features: + - Simplified, synchronous interface which mimics EME APIs + - Support for key statuses and session expiration times + - Simplified build system with fewer build-time flags + - Simplified initialization with runtime settings for client info, + log levels, and secure output modes + - Secure output modes are explicit, and individual decrypt requests can + be done in the clear (for example, for platforms with L3 audio) + - Device certificates are now required for all platforms and must be + provisioned during initialization if not present + - Simplified storage interface with more explicit methods + - New integration guide which replaces several older documents + diff --git a/README.pdf b/README.pdf index daf5faf3..680bda11 100644 Binary files a/README.pdf and b/README.pdf differ diff --git a/cdm/cdm.gyp b/cdm/cdm.gyp index 775ac714..1d710d92 100644 --- a/cdm/cdm.gyp +++ b/cdm/cdm.gyp @@ -161,6 +161,8 @@ 'include_dirs': [ 'include', ], + }, + 'link_settings': { 'libraries': [ '-lpthread', ], diff --git a/cdm/include/cdm.h b/cdm/include/cdm.h index 15ddd8d9..64600227 100644 --- a/cdm/include/cdm.h +++ b/cdm/include/cdm.h @@ -1,7 +1,6 @@ // Copyright 2015 Google Inc. All Rights Reserved. -// Based on the EME draft spec from 2015 June 01. -// https://rawgit.com/w3c/encrypted-media/1cbedad/index.html -// TODO: Verify behavior and update to June 12 draft. +// Based on the EME draft spec from 2015 November 20. +// https://rawgit.com/w3c/encrypted-media/1dab9e5/index.html #ifndef WVCDM_CDM_CDM_H_ #define WVCDM_CDM_CDM_H_ @@ -49,7 +48,9 @@ class CDM_EXPORT Cdm : public ITimerClient { // Session types defined by EME. typedef enum { kTemporary = 0, - kPersistent = 1, + kPersistentLicense = 1, + kPersistent = kPersistentLicense, // deprecated name from June 1 draft + kPersistentUsageRecord = 2, } SessionType; // Message types defined by EME. @@ -67,11 +68,15 @@ class CDM_EXPORT Cdm : public ITimerClient { kSessionNotFound = 2, kDecryptError = 3, kNoKey = 4, + // These are analogous to the errors used by EME: - kInvalidAccess = 5, + kTypeError = 5, + kInvalidAccess = kTypeError, // deprecated name from June 1 draft kNotSupported = 6, kInvalidState = 7, kQuotaExceeded = 8, + kRangeError = 9, + // This covers errors that we do not expect (see logs for details): kUnexpectedError = 99999, } Status; @@ -87,9 +92,11 @@ class CDM_EXPORT Cdm : public ITimerClient { typedef enum { kUsable = 0, kExpired = 1, - kOutputNotAllowed = 2, + kOutputRestricted = 2, + kOutputNotAllowed = kOutputRestricted, // deprecated name from June 1 draft kStatusPending = 3, kInternalError = 4, + kReleased = 5, } KeyStatus; // These are defined by Widevine. The CDM can be configured to decrypt in @@ -316,7 +323,7 @@ class CDM_EXPORT Cdm : public ITimerClient { // license server. // If |privacy_mode| was true in create() and setServerCertificate() is not // called, the CDM will attempt to provision a server certificate through - // IEventListener::onMessage() with messageType == kIndividualizationRequest. + // IEventListener::onMessage() with messageType == kLicenseRequest. // May not be called if |privacy_mode| was false. virtual Status setServerCertificate(const std::string& certificate) = 0; @@ -453,13 +460,13 @@ class CDM_EXPORT Cdm : public ITimerClient { const std::string& value) = 0; // Gets the current value in the custom app settings. If the key is - // not present, then kInvalidAccess is returned. The |key| cannot be + // not present, then kTypeError is returned. The |key| cannot be // empty. |result| cannot be null. See setAppParameter(). virtual Status getAppParameter(const std::string& key, std::string* result) = 0; // Removes the value in the custom app settings. If the key is not - // present, then kInvalidAccess is returned. The |key| cannot be empty. + // present, then kTypeError is returned. The |key| cannot be empty. // See setAppParameter(). virtual Status removeAppParameter(const std::string& key) = 0; diff --git a/cdm/include/cdm_version.h b/cdm/include/cdm_version.h index e68bca20..666b8932 100644 --- a/cdm/include/cdm_version.h +++ b/cdm/include/cdm_version.h @@ -1,2 +1,2 @@ // Widevine CE CDM Version -#define CDM_VERSION "v3.0.3-0-g226db8b-ce" +#define CDM_VERSION "v3.0.4-0-g4dee2a8-ce" diff --git a/cdm/src/cdm.cpp b/cdm/src/cdm.cpp index d79bb042..85c349f0 100644 --- a/cdm/src/cdm.cpp +++ b/cdm/src/cdm.cpp @@ -2,6 +2,7 @@ #include "cdm.h" #include +#include // LLONG_MAX #include // memcpy #include @@ -11,6 +12,7 @@ #include "cdm_engine.h" #include "clock.h" #include "crypto_session.h" +#include "device_files.h" #include "file_store.h" #include "license.h" #include "log.h" @@ -171,9 +173,18 @@ class CdmImpl : public Cdm, PropertySet property_set_; CdmAppParameterMap app_parameters_; - std::map new_session_types_; - std::map session_expirations_; - std::map session_key_statuses_; + struct SessionMetadata { + bool callable; // EME terminology: request generated or session loaded + SessionType type; + int64_t expiration; + KeyStatusMap key_statuses; + + SessionMetadata() + : callable(false), + type((SessionType)-1), + expiration(0) {} + }; + std::map sessions_; }; CdmImpl::CdmImpl(IEventListener* listener, @@ -195,12 +206,12 @@ Cdm::Status CdmImpl::setServerCertificate(const std::string& certificate) { if (certificate.empty()) { LOGE("An empty server certificate is invalid."); - return kInvalidAccess; + return kTypeError; } if (CdmLicense::VerifySignedServiceCertificate(certificate) != NO_ERROR) { LOGE("Invalid server certificate!"); - return kInvalidAccess; + return kTypeError; } property_set_.set_service_certificate(certificate); @@ -211,7 +222,7 @@ Cdm::Status CdmImpl::createSession(SessionType session_type, std::string* session_id) { if (!session_id) { LOGE("Missing session ID pointer."); - return kInvalidAccess; + return kTypeError; } // Important! The caller may pass a pre-filled string, which must be cleared // before being given to CdmEngine. @@ -219,7 +230,8 @@ Cdm::Status CdmImpl::createSession(SessionType session_type, switch (session_type) { case kTemporary: - case kPersistent: + case kPersistentLicense: + case kPersistentUsageRecord: break; default: LOGE("Unsupported session type: %d", session_type); @@ -232,7 +244,7 @@ Cdm::Status CdmImpl::createSession(SessionType session_type, NULL, session_id); switch (result) { case NO_ERROR: - new_session_types_[*session_id] = session_type; + sessions_[*session_id].type = session_type; return kSuccess; case NEED_PROVISIONING: LOGE("A device certificate is needed."); @@ -251,22 +263,23 @@ Cdm::Status CdmImpl::generateRequest(const std::string& session_id, return kSessionNotFound; } - std::map::iterator it = - new_session_types_.find(session_id); - if (it == new_session_types_.end()) { + if (sessions_[session_id].callable) { LOGE("Request already generated: %s", session_id.c_str()); return kInvalidState; } - SessionType session_type = it->second; + SessionType session_type = sessions_[session_id].type; CdmLicenseType license_type; switch (session_type) { case kTemporary: - license_type = kLicenseTypeStreaming; + license_type = kLicenseTypeTemporary; break; - case kPersistent: + case kPersistentLicense: license_type = kLicenseTypeOffline; break; + case kPersistentUsageRecord: + license_type = kLicenseTypeStreaming; + break; default: LOGE("Unexpected session type: %d", session_type); return kUnexpectedError; @@ -285,12 +298,12 @@ Cdm::Status CdmImpl::generateRequest(const std::string& session_id, break; default: LOGE("Invalid init data type: %d", init_data_type); - return kInvalidAccess; + return kTypeError; } if (init_data.empty()) { LOGE("Empty init data is not valid."); - return kInvalidAccess; + return kTypeError; } InitializationData init_data_obj(init_data_type_name, init_data); @@ -316,26 +329,27 @@ Cdm::Status CdmImpl::generateRequest(const std::string& session_id, return kUnexpectedError; } - new_session_types_.erase(it); + sessions_[session_id].callable = true; assert(key_request_type == kKeyRequestTypeInitial); - MessageType message_type = kLicenseRequest; if (property_set_.use_privacy_mode() && property_set_.service_certificate().empty()) { // We can deduce that this is a server cert request, even though CdmEgine // cannot currently inform us of this. - message_type = kIndividualizationRequest; + // Previously, we used message type kIndividiualizationRequest for this. + // The EME editor has clarified that this was a misinterpretation of the + // spec, and that this should also be kLicenseRequest. LOGI("A server certificate request has been generated."); } else { LOGI("A license request has been generated."); } - listener_->onMessage(session_id, message_type, key_request); + listener_->onMessage(session_id, kLicenseRequest, key_request); return kSuccess; } Cdm::Status CdmImpl::load(const std::string& session_id) { if (session_id.empty()) { LOGE("Empty session ID."); - return kInvalidAccess; + return kTypeError; } if (cdm_engine_.IsOpenSession(session_id)) { @@ -359,11 +373,37 @@ Cdm::Status CdmImpl::load(const std::string& session_id) { return kUnexpectedError; } + DeviceFiles f; + if (!f.Init(kSecurityLevelUnknown)) { + LOGE("Unexpected error, failed to init DeviceFiles"); + return kUnexpectedError; + } + + if (!f.LicenseExists(session_id)) { + // This might be a usage record session which needs to be loaded. + CdmKeyMessage release_message; + result = cdm_engine_.LoadUsageSession(session_id, &release_message); + if (result == LOAD_USAGE_INFO_MISSING) { + LOGE("Unable to load license: %s", session_id.c_str()); + cdm_engine_.CloseSession(session_id); + return kSessionNotFound; + } else if (result != KEY_MESSAGE) { + LOGE("Unexpected error %d", result); + cdm_engine_.CloseSession(session_id); + return kUnexpectedError; + } + + LOGI("A usage record release has been generated."); + MessageType message_type = kLicenseRelease; + listener_->onMessage(session_id, message_type, release_message); + + sessions_[session_id].type = kPersistentUsageRecord; + sessions_[session_id].callable = true; + return kSuccess; + } + result = cdm_engine_.RestoreKey(session_id, session_id); - if (result == GET_LICENSE_ERROR) { - LOGE("Unable to load license: %s", session_id.c_str()); - return kSessionNotFound; - } else if (result == GET_RELEASED_LICENSE_ERROR) { + if (result == GET_RELEASED_LICENSE_ERROR) { // This was partially removed already. // The EME spec states that we should send a release message right away. InitializationData empty_initialization_data; @@ -388,6 +428,8 @@ Cdm::Status CdmImpl::load(const std::string& session_id) { return kUnexpectedError; } + sessions_[session_id].type = kPersistentLicense; + sessions_[session_id].callable = true; return kSuccess; } @@ -398,14 +440,14 @@ Cdm::Status CdmImpl::update(const std::string& session_id, return kSessionNotFound; } - if (new_session_types_.find(session_id) != new_session_types_.end()) { + if (!sessions_[session_id].callable) { LOGE("Request not yet generated: %s", session_id.c_str()); return kInvalidState; } if (response.empty()) { LOGE("Empty response."); - return kInvalidAccess; + return kTypeError; } // NOTE: If the CdmSession object recognizes that this is not the first @@ -444,6 +486,12 @@ Cdm::Status CdmImpl::update(const std::string& session_id, MessageType message_type = kLicenseRequest; listener_->onMessage(session_id, message_type, key_request); return kSuccess; + } else if (result == OFFLINE_LICENSE_PROHIBITED) { + LOGE("A temporary session cannot be used for a persistent license."); + return kRangeError; + } else if (result == STORAGE_PROHIBITED) { + LOGE("A temporary session cannot be used for a persistent usage records."); + return kRangeError; } else if (result != KEY_ADDED) { LOGE("Unexpected error %d", result); return kUnexpectedError; @@ -457,6 +505,7 @@ Cdm::Status CdmImpl::update(const std::string& session_id, } if (cdm_engine_.IsReleaseSession(session_id)) { + sessions_.erase(session_id); cdm_engine_.CloseSession(session_id); listener_->onRemoveComplete(session_id); } @@ -470,7 +519,7 @@ Cdm::Status CdmImpl::getExpiration(const std::string& session_id, return kSessionNotFound; } - *expiration = session_expirations_[session_id]; + *expiration = sessions_[session_id].expiration; return kSuccess; } @@ -481,14 +530,14 @@ Cdm::Status CdmImpl::getKeyStatuses(const std::string& session_id, return kSessionNotFound; } - *key_statuses = session_key_statuses_[session_id]; + *key_statuses = sessions_[session_id].key_statuses; return kSuccess; } Cdm::Status CdmImpl::setAppParameter(const std::string& key, const std::string& value) { if (key.empty()) { - return kInvalidAccess; + return kTypeError; } app_parameters_[key] = value; return kSuccess; @@ -498,7 +547,7 @@ Cdm::Status CdmImpl::getAppParameter(const std::string& key, std::string* result) { if (NULL == result || key.empty() || app_parameters_.find(key) == app_parameters_.end()) { - return kInvalidAccess; + return kTypeError; } *result = app_parameters_[key]; return kSuccess; @@ -506,11 +555,11 @@ Cdm::Status CdmImpl::getAppParameter(const std::string& key, Cdm::Status CdmImpl::removeAppParameter(const std::string& key) { if (key.empty()) { - return kInvalidAccess; + return kTypeError; } CdmAppParameterMap::iterator it = app_parameters_.find(key); if (it == app_parameters_.end()) { - return kInvalidAccess; + return kTypeError; } app_parameters_.erase(it); return kSuccess; @@ -532,6 +581,7 @@ Cdm::Status CdmImpl::close(const std::string& session_id) { LOGE("Unexpected error %d", result); return kUnexpectedError; } + sessions_.erase(session_id); return kSuccess; } @@ -541,20 +591,28 @@ Cdm::Status CdmImpl::remove(const std::string& session_id) { return kSessionNotFound; } - if (new_session_types_.find(session_id) != new_session_types_.end()) { + if (!sessions_[session_id].callable) { LOGE("Request not yet generated: %s", session_id.c_str()); return kInvalidState; } - if (!cdm_engine_.IsOfflineSession(session_id)) { + if (sessions_[session_id].type == kTemporary) { LOGE("Not a persistent session: %s", session_id.c_str()); - return kInvalidAccess; + return kRangeError; } InitializationData empty_initialization_data; CdmKeyMessage key_request; std::string ignored_server_url; + // Mark all keys as released ahead of generating the release request. + // When released, cdm_engine_ will mark all keys as expired, which we will + // ignore in this interface. + KeyStatusMap& map = sessions_[session_id].key_statuses; + for (KeyStatusMap::iterator it = map.begin(); it != map.end(); ++it) { + it->second = kReleased; + } + CdmResponseType result = cdm_engine_.GenerateKeyRequest( session_id, session_id, empty_initialization_data, kLicenseTypeRelease, app_parameters_, &key_request, NULL, @@ -575,7 +633,7 @@ Cdm::Status CdmImpl::decrypt(const InputBuffer& input, const OutputBuffer& output) { if (input.is_encrypted && input.iv_length != 16) { LOGE("The IV must be 16 bytes long."); - return kInvalidAccess; + return kTypeError; } if (PropertiesCE::GetSecureOutputType() == kNoSecureOutput && output.is_secure) { @@ -651,7 +709,7 @@ void CdmImpl::OnSessionRenewalNeeded(const CdmSessionId& session_id) { void CdmImpl::OnSessionKeysChange(const CdmSessionId& session_id, const CdmKeyStatusMap& keys_status, bool has_new_usable_key) { - KeyStatusMap& map = session_key_statuses_[session_id]; + KeyStatusMap& map = sessions_[session_id].key_statuses; CdmKeyStatusMap::const_iterator it; for (it = keys_status.begin(); it != keys_status.end(); ++it) { @@ -659,11 +717,18 @@ void CdmImpl::OnSessionKeysChange(const CdmSessionId& session_id, case kKeyStatusUsable: map[it->first] = kUsable; break; - case kKeyStatusExpired: - map[it->first] = kExpired; + case kKeyStatusExpired: { + KeyStatusMap::const_iterator it_old = map.find(it->first); + if (it_old != map.end() && it_old->second == kReleased) { + // This key has already been marked as "released". + // Ignore the internal "expired" status. + } else { + map[it->first] = kExpired; + } break; + } case kKeyStatusOutputNotAllowed: - map[it->first] = kOutputNotAllowed; + map[it->first] = kOutputRestricted; break; case kKeyStatusPending: map[it->first] = kStatusPending; @@ -683,7 +748,12 @@ void CdmImpl::OnSessionKeysChange(const CdmSessionId& session_id, void CdmImpl::OnExpirationUpdate(const CdmSessionId& session_id, int64_t new_expiry_time_seconds) { - session_expirations_[session_id] = new_expiry_time_seconds * 1000; + // "Never expires" in core is LLONG_MAX. In the CDM API, it's -1. + if (new_expiry_time_seconds == LLONG_MAX) { + sessions_[session_id].expiration = -1; + } else { + sessions_[session_id].expiration = new_expiry_time_seconds * 1000; + } } @@ -728,24 +798,24 @@ Cdm::Status Cdm::initialize( break; default: LOGE("Invalid output type!"); - return kInvalidAccess; + return kTypeError; } if (client_info.product_name.empty() || client_info.company_name.empty() || client_info.model_name.empty()) { LOGE("Client info requires product_name, company_name, model_name!"); - return kInvalidAccess; + return kTypeError; } if (!storage || !clock || !timer) { LOGE("All interfaces are required!"); - return kInvalidAccess; + return kTypeError; } if (!device_certificate_request) { LOGE("Device certificate request pointer is required!"); - return kInvalidAccess; + return kTypeError; } // Our enum values match those in core/include/log.h @@ -811,7 +881,7 @@ Cdm::Status Cdm::DeviceCertificateRequest::acceptReply( const std::string& reply) { if (!host.provisioning_engine) { LOGE("Provisioning reply received while not in a provisioning state!"); - return kInvalidAccess; + return kTypeError; } std::string empty_origin; @@ -901,7 +971,11 @@ void File::Close() { } bool File::Exists(const std::string& file_path) { - return host.storage->exists(file_path); + // An empty path is the "base directory" for CE CDM's file storage. + // Therefore, it should always be seen as existing. + // If it ever does not exist, CdmEngine detects this as a "factory reset" + // and wipes out all usage table data. + return file_path.empty() || host.storage->exists(file_path); } bool File::Remove(const std::string& file_path) { diff --git a/cdm/test/cdm_test.cpp b/cdm/test/cdm_test.cpp index a667ad6d..b1103ed1 100644 --- a/cdm/test/cdm_test.cpp +++ b/cdm/test/cdm_test.cpp @@ -142,6 +142,11 @@ class CdmTest : public Test, // Clear anything stored, load default device cert. g_host->Reset(); + // Clear anything stored by OEMCrypto. + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_Initialize()); + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_DeleteUsageTable()); + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_Terminate()); + // Reinit the library. Cdm::DeviceCertificateRequest cert_request; Cdm::Status status = Cdm::initialize( @@ -230,61 +235,40 @@ class CdmTest : public Test, if (ok) ASSERT_EQ(expected_status_code, status_code); } - void CreateTemporarySessionAndGenerateRequest(std::string* session_id, - std::string* message) { - Cdm::Status status = cdm_->createSession(Cdm::kTemporary, session_id); + void CreateSessionAndGenerateRequest(Cdm::SessionType session_type, + std::string* session_id, + std::string* message) { + Cdm::Status status = cdm_->createSession(session_type, session_id); ASSERT_EQ(Cdm::kSuccess, status); + std::string init_data; + if (session_type == Cdm::kTemporary) { + init_data = kCencInitData; + } else { + init_data = kCencPersistentInitData; + } + EXPECT_CALL(*this, onMessage(*session_id, Cdm::kLicenseRequest, _)). WillOnce(SaveArg<2>(message)); - status = cdm_->generateRequest(*session_id, Cdm::kCenc, kCencInitData); + status = cdm_->generateRequest(*session_id, Cdm::kCenc, init_data); ASSERT_EQ(Cdm::kSuccess, status); Mock::VerifyAndClear(this); } - void CreatePersistentSessionAndGenerateRequest(std::string* session_id, - std::string* message) { - Cdm::Status status = cdm_->createSession(Cdm::kPersistent, session_id); - ASSERT_EQ(Cdm::kSuccess, status); - - EXPECT_CALL(*this, onMessage(*session_id, Cdm::kLicenseRequest, _)). - WillOnce(SaveArg<2>(message)); - status = cdm_->generateRequest(*session_id, Cdm::kCenc, - kCencPersistentInitData); - ASSERT_EQ(Cdm::kSuccess, status); - Mock::VerifyAndClear(this); - } - - void CreateTemporarySessionAndFetchLicense(std::string* session_id, - std::string* response) { + void CreateSessionAndFetchLicense(Cdm::SessionType session_type, + std::string* session_id, + std::string* response) { std::string message; - ASSERT_NO_FATAL_FAILURE(CreateTemporarySessionAndGenerateRequest( - session_id, &message)); + ASSERT_NO_FATAL_FAILURE(CreateSessionAndGenerateRequest( + session_type, session_id, &message)); FetchLicense(message, response); } - void CreatePersistentSessionAndFetchLicense(std::string* session_id, - std::string* response) { - std::string message; - ASSERT_NO_FATAL_FAILURE(CreatePersistentSessionAndGenerateRequest( - session_id, &message)); - FetchLicense(message, response); - } - - void CreateTemporarySessionAndUpdate(std::string* session_id) { + void CreateSessionAndUpdate(Cdm::SessionType session_type, + std::string* session_id) { std::string response; - ASSERT_NO_FATAL_FAILURE(CreateTemporarySessionAndFetchLicense( - session_id, &response)); - EXPECT_CALL(*this, onKeyStatusesChange(*session_id)); - Cdm::Status status = cdm_->update(*session_id, response); - ASSERT_EQ(Cdm::kSuccess, status); - Mock::VerifyAndClear(this); - } - - void CreatePersistentSessionAndUpdate(std::string* session_id) { - std::string response; - ASSERT_NO_FATAL_FAILURE(CreatePersistentSessionAndFetchLicense( - session_id, &response)); + ASSERT_NO_FATAL_FAILURE(CreateSessionAndFetchLicense( + session_type, session_id, &response)); EXPECT_CALL(*this, onKeyStatusesChange(*session_id)); Cdm::Status status = cdm_->update(*session_id, response); ASSERT_EQ(Cdm::kSuccess, status); @@ -352,7 +336,7 @@ TEST_F(CdmTest, Initialize) { static_cast(-1), PropertiesCE::GetClientInfo(), g_host, g_host, g_host, &cert_request, static_cast(g_cutoff)); - EXPECT_EQ(Cdm::kInvalidAccess, status); + EXPECT_EQ(Cdm::kTypeError, status); // Try with various client info properties missing. Cdm::ClientInfo working_client_info = PropertiesCE::GetClientInfo(); @@ -364,7 +348,7 @@ TEST_F(CdmTest, Initialize) { Cdm::kNoSecureOutput, broken_client_info, g_host, g_host, g_host, &cert_request, static_cast(g_cutoff)); - EXPECT_EQ(Cdm::kInvalidAccess, status); + EXPECT_EQ(Cdm::kTypeError, status); broken_client_info = working_client_info; broken_client_info.company_name.clear(); @@ -372,7 +356,7 @@ TEST_F(CdmTest, Initialize) { Cdm::kNoSecureOutput, broken_client_info, g_host, g_host, g_host, &cert_request, static_cast(g_cutoff)); - EXPECT_EQ(Cdm::kInvalidAccess, status); + EXPECT_EQ(Cdm::kTypeError, status); broken_client_info = working_client_info; broken_client_info.device_name.clear(); // Not required @@ -388,7 +372,7 @@ TEST_F(CdmTest, Initialize) { Cdm::kNoSecureOutput, broken_client_info, g_host, g_host, g_host, &cert_request, static_cast(g_cutoff)); - EXPECT_EQ(Cdm::kInvalidAccess, status); + EXPECT_EQ(Cdm::kTypeError, status); broken_client_info = working_client_info; broken_client_info.arch_name.clear(); // Not required @@ -411,25 +395,25 @@ TEST_F(CdmTest, Initialize) { Cdm::kNoSecureOutput, working_client_info, NULL, g_host, g_host, &cert_request, static_cast(g_cutoff)); - EXPECT_EQ(Cdm::kInvalidAccess, status); + EXPECT_EQ(Cdm::kTypeError, status); status = Cdm::initialize( Cdm::kNoSecureOutput, working_client_info, g_host, NULL, g_host, &cert_request, static_cast(g_cutoff)); - EXPECT_EQ(Cdm::kInvalidAccess, status); + EXPECT_EQ(Cdm::kTypeError, status); status = Cdm::initialize( Cdm::kNoSecureOutput, working_client_info, g_host, g_host, NULL, &cert_request, static_cast(g_cutoff)); - EXPECT_EQ(Cdm::kInvalidAccess, status); + EXPECT_EQ(Cdm::kTypeError, status); status = Cdm::initialize( Cdm::kNoSecureOutput, working_client_info, g_host, g_host, g_host, NULL, static_cast(g_cutoff)); - EXPECT_EQ(Cdm::kInvalidAccess, status); + EXPECT_EQ(Cdm::kTypeError, status); // One last init with everything correct and working. status = Cdm::initialize( @@ -502,11 +486,11 @@ TEST_F(CdmTest, SetServerCertificate) { // It is invalid to set an empty cert. status = cdm_->setServerCertificate(""); - EXPECT_EQ(Cdm::kInvalidAccess, status); + EXPECT_EQ(Cdm::kTypeError, status); // It is invalid to set a malformed cert. status = cdm_->setServerCertificate("asdf"); - EXPECT_EQ(Cdm::kInvalidAccess, status); + EXPECT_EQ(Cdm::kTypeError, status); } TEST_F(CdmTest, CreateSession) { @@ -524,12 +508,12 @@ TEST_F(CdmTest, CreateSession) { EXPECT_NE(original_session_id, session_id); // Create a persistent session. - status = cdm_->createSession(Cdm::kPersistent, &session_id); + status = cdm_->createSession(Cdm::kPersistentLicense, &session_id); EXPECT_EQ(Cdm::kSuccess, status); // Try a NULL pointer for session ID. status = cdm_->createSession(Cdm::kTemporary, NULL); - EXPECT_EQ(Cdm::kInvalidAccess, status); + EXPECT_EQ(Cdm::kTypeError, status); // Try a bogus session type. status = cdm_->createSession(kBogusSessionType, &session_id); @@ -574,7 +558,7 @@ TEST_F(CdmTest, GenerateRequest) { ASSERT_EQ(Cdm::kSuccess, status); EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)).Times(0); status = cdm_->generateRequest(session_id, kBogusInitDataType, "asdf"); - EXPECT_EQ(Cdm::kInvalidAccess, status); + EXPECT_EQ(Cdm::kTypeError, status); Mock::VerifyAndClear(this); // This same session should still be usable with a supported init data type @@ -589,7 +573,7 @@ TEST_F(CdmTest, GenerateRequest) { ASSERT_EQ(Cdm::kSuccess, status); EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)).Times(0); status = cdm_->generateRequest(session_id, Cdm::kCenc, ""); - EXPECT_EQ(Cdm::kInvalidAccess, status); + EXPECT_EQ(Cdm::kTypeError, status); Mock::VerifyAndClear(this); // Try to pass invalid CENC init data. @@ -619,8 +603,8 @@ TEST_F(CdmTest, GenerateRequest) { TEST_F(CdmTest, Update) { std::string session_id; std::string message; - ASSERT_NO_FATAL_FAILURE(CreateTemporarySessionAndGenerateRequest( - &session_id, &message)); + ASSERT_NO_FATAL_FAILURE(CreateSessionAndGenerateRequest( + Cdm::kTemporary, &session_id, &message)); // Acquire a license. std::string response; @@ -638,7 +622,7 @@ TEST_F(CdmTest, Update) { // Try updating with an empty response. status = cdm_->update(session_id, ""); - EXPECT_EQ(Cdm::kInvalidAccess, status); + EXPECT_EQ(Cdm::kTypeError, status); // Create a new session and try updating before generating a request. status = cdm_->createSession(Cdm::kTemporary, &session_id); @@ -675,8 +659,8 @@ TEST_F(CdmTest, Close) { TEST_F(CdmTest, LoadTemporary) { std::string session_id; std::string response; - ASSERT_NO_FATAL_FAILURE(CreateTemporarySessionAndFetchLicense( - &session_id, &response)); + ASSERT_NO_FATAL_FAILURE(CreateSessionAndFetchLicense( + Cdm::kTemporary, &session_id, &response)); // Update the temporary session. EXPECT_CALL(*this, onKeyStatusesChange(session_id)); @@ -696,8 +680,8 @@ TEST_F(CdmTest, LoadTemporary) { TEST_F(CdmTest, LoadPersistent) { std::string session_id; std::string response; - ASSERT_NO_FATAL_FAILURE(CreatePersistentSessionAndFetchLicense( - &session_id, &response)); + ASSERT_NO_FATAL_FAILURE(CreateSessionAndFetchLicense( + Cdm::kPersistentLicense, &session_id, &response)); // Update the persistent session. EXPECT_CALL(*this, onKeyStatusesChange(session_id)); @@ -731,6 +715,49 @@ TEST_F(CdmTest, LoadPersistent) { Mock::VerifyAndClear(this); } +TEST_F(CdmTest, LoadUsageRecord) { + std::string session_id; + std::string response; + ASSERT_NO_FATAL_FAILURE(CreateSessionAndFetchLicense( + Cdm::kPersistentUsageRecord, &session_id, &response)); + + // Update the session. + EXPECT_CALL(*this, onKeyStatusesChange(session_id)); + Cdm::Status status = cdm_->update(session_id, response); + ASSERT_EQ(Cdm::kSuccess, status); + Mock::VerifyAndClear(this); + + // Should be able to load the session again after closing it. + status = cdm_->close(session_id); + ASSERT_EQ(Cdm::kSuccess, status); + + // There should be no usable keys after loading this session. + EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(0); + EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRelease, _)); + status = cdm_->load(session_id); + EXPECT_EQ(Cdm::kSuccess, status); + Mock::VerifyAndClear(this); + + // Should be able to load the session again after recreating the CDM. + ASSERT_NO_FATAL_FAILURE(RecreateCdm(true /* privacy_mode */)); + ASSERT_NO_FATAL_FAILURE(SetDefaultServerCertificate()); + EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(0); + EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRelease, _)); + status = cdm_->load(session_id); + EXPECT_EQ(Cdm::kSuccess, status); + Mock::VerifyAndClear(this); + + // Should not be able to load the session again clearing storage. + status = cdm_->close(session_id); + ASSERT_EQ(Cdm::kSuccess, status); + g_host->Reset(); + EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(0); + EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRelease, _)).Times(0); + status = cdm_->load(session_id); + EXPECT_EQ(Cdm::kSessionNotFound, status); + Mock::VerifyAndClear(this); +} + TEST_F(CdmTest, LoadBogus) { EXPECT_CALL(*this, onKeyStatusesChange(_)).Times(0); Cdm::Status status = cdm_->load(kBogusSessionId); @@ -739,7 +766,7 @@ TEST_F(CdmTest, LoadBogus) { TEST_F(CdmTest, GetKeyStatuses) { std::string session_id; - ASSERT_NO_FATAL_FAILURE(CreateTemporarySessionAndUpdate(&session_id)); + ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate(Cdm::kTemporary, &session_id)); // We should be able to query status and see a usable key. Cdm::KeyStatusMap map; @@ -773,7 +800,7 @@ TEST_F(CdmTest, GetKeyStatuses) { TEST_F(CdmTest, GetExpiration) { std::string session_id; - ASSERT_NO_FATAL_FAILURE(CreateTemporarySessionAndUpdate(&session_id)); + ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate(Cdm::kTemporary, &session_id)); // We should be able to query expiration and get a value in the future. int64_t expiration; @@ -805,7 +832,8 @@ TEST_F(CdmTest, GetExpiration) { TEST_F(CdmTest, Remove) { std::string session_id; - ASSERT_NO_FATAL_FAILURE(CreatePersistentSessionAndUpdate(&session_id)); + ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate( + Cdm::kPersistentLicense, &session_id)); // Remove the session. This causes a release message to be generated. std::string message; @@ -820,7 +848,7 @@ TEST_F(CdmTest, Remove) { Cdm::KeyStatusMap map; status = cdm_->getKeyStatuses(session_id, &map); ASSERT_EQ(Cdm::kSuccess, status); - EXPECT_EQ(Cdm::kExpired, map.begin()->second); + EXPECT_EQ(Cdm::kReleased, map.begin()->second); // Post the release message to the license server. std::string response; @@ -843,20 +871,56 @@ TEST_F(CdmTest, Remove) { EXPECT_EQ(Cdm::kSessionNotFound, status); // Try a new session. - status = cdm_->createSession(Cdm::kPersistent, &session_id); + status = cdm_->createSession(Cdm::kPersistentLicense, &session_id); ASSERT_EQ(Cdm::kSuccess, status); status = cdm_->remove(session_id); EXPECT_EQ(Cdm::kInvalidState, status); // Try a temporary session. - ASSERT_NO_FATAL_FAILURE(CreateTemporarySessionAndUpdate(&session_id)); + ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate(Cdm::kTemporary, &session_id)); status = cdm_->remove(session_id); - EXPECT_EQ(Cdm::kInvalidAccess, status); + EXPECT_EQ(Cdm::kRangeError, status); +} + +TEST_F(CdmTest, RemoveUsageRecord) { + std::string session_id; + ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate( + Cdm::kPersistentUsageRecord, &session_id)); + + // Remove the session. This causes a release message to be generated. + std::string message; + EXPECT_CALL(*this, onKeyStatusesChange(session_id)); + EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRelease, _)).WillOnce( + SaveArg<2>(&message)); + Cdm::Status status = cdm_->remove(session_id); + ASSERT_EQ(Cdm::kSuccess, status); + Mock::VerifyAndClear(this); + + // The keys should already be unusable. + Cdm::KeyStatusMap map; + status = cdm_->getKeyStatuses(session_id, &map); + ASSERT_EQ(Cdm::kSuccess, status); + EXPECT_EQ(Cdm::kReleased, map.begin()->second); + + // Post the release message to the license server. + std::string response; + ASSERT_NO_FATAL_FAILURE(FetchLicense(message, &response)); + + // Update the session. + EXPECT_CALL(*this, onRemoveComplete(session_id)); + status = cdm_->update(session_id, response); + ASSERT_EQ(Cdm::kSuccess, status); + Mock::VerifyAndClear(this); + + // The session is now completely gone. + status = cdm_->close(session_id); + ASSERT_EQ(Cdm::kSessionNotFound, status); } TEST_F(CdmTest, RemoveIncomplete) { std::string session_id; - ASSERT_NO_FATAL_FAILURE(CreatePersistentSessionAndUpdate(&session_id)); + ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate( + Cdm::kPersistentLicense, &session_id)); // Remove the session. This causes a release message to be generated. std::string message; @@ -872,7 +936,64 @@ TEST_F(CdmTest, RemoveIncomplete) { status = cdm_->getKeyStatuses(session_id, &map); ASSERT_EQ(Cdm::kSuccess, status); ASSERT_FALSE(map.empty()); - EXPECT_EQ(Cdm::kExpired, map.begin()->second); + EXPECT_EQ(Cdm::kReleased, map.begin()->second); + + // Recreate the CDM. + ASSERT_NO_FATAL_FAILURE(RecreateCdm(true /* privacy_mode */)); + ASSERT_NO_FATAL_FAILURE(SetDefaultServerCertificate()); + + // Load the partially removed session, which will immediately generate a + // release message. + message.clear(); + EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(0); + EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRelease, _)).WillOnce( + SaveArg<2>(&message)); + status = cdm_->load(session_id); + ASSERT_EQ(Cdm::kSuccess, status); + ASSERT_FALSE(message.empty()); + Mock::VerifyAndClear(this); + + // This session has no keys. + status = cdm_->getKeyStatuses(session_id, &map); + ASSERT_EQ(Cdm::kSuccess, status); + EXPECT_TRUE(map.empty()); + + // Post the release message to the license server. + std::string response; + ASSERT_NO_FATAL_FAILURE(FetchLicense(message, &response)); + + // Update the session. + EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(0); + EXPECT_CALL(*this, onRemoveComplete(session_id)); + status = cdm_->update(session_id, response); + ASSERT_EQ(Cdm::kSuccess, status); + Mock::VerifyAndClear(this); + + // The session is now completely gone. + status = cdm_->load(session_id); + ASSERT_EQ(Cdm::kSessionNotFound, status); +} + +TEST_F(CdmTest, RemoveUsageRecordIncomplete) { + std::string session_id; + ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate( + Cdm::kPersistentUsageRecord, &session_id)); + + // Remove the session. This causes a release message to be generated. + std::string message; + EXPECT_CALL(*this, onKeyStatusesChange(session_id)); + EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRelease, _)).WillOnce( + SaveArg<2>(&message)); + Cdm::Status status = cdm_->remove(session_id); + ASSERT_EQ(Cdm::kSuccess, status); + Mock::VerifyAndClear(this); + + // The keys should already be unusable, but they should still exist. + Cdm::KeyStatusMap map; + status = cdm_->getKeyStatuses(session_id, &map); + ASSERT_EQ(Cdm::kSuccess, status); + ASSERT_FALSE(map.empty()); + EXPECT_EQ(Cdm::kReleased, map.begin()->second); // Recreate the CDM. ASSERT_NO_FATAL_FAILURE(RecreateCdm(true /* privacy_mode */)); @@ -913,7 +1034,8 @@ TEST_F(CdmTest, RemoveIncomplete) { TEST_F(CdmTest, RemoveNotLoaded) { // Create a persistent session and then close it. std::string session_id; - ASSERT_NO_FATAL_FAILURE(CreatePersistentSessionAndUpdate(&session_id)); + ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate( + Cdm::kPersistentLicense, &session_id)); Cdm::Status status = cdm_->close(session_id); ASSERT_EQ(Cdm::kSuccess, status); @@ -945,7 +1067,7 @@ TEST_F(CdmTest, DecryptClear) { // Create a session with the right keys. std::string session_id; - ASSERT_NO_FATAL_FAILURE(CreateTemporarySessionAndUpdate(&session_id)); + ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate(Cdm::kTemporary, &session_id)); // Decrypt should now succeed. status = cdm_->decrypt(input, output); @@ -958,7 +1080,8 @@ TEST_F(CdmTest, RequestPersistentLicenseWithWrongInitData) { // Generate a request for a persistent license without using the correct // persistent content init data. std::string session_id; - Cdm::Status status = cdm_->createSession(Cdm::kPersistent, &session_id); + Cdm::Status status = cdm_->createSession(Cdm::kPersistentLicense, + &session_id); ASSERT_EQ(Cdm::kSuccess, status); std::string message; @@ -990,24 +1113,16 @@ TEST_F(CdmTest, RequestTemporaryLicenseWithWrongInitData) { std::string response; ASSERT_NO_FATAL_FAILURE(FetchLicense(message, &response)); - // This license should be accepted. + // This license should not be accepted. EXPECT_CALL(*this, onKeyStatusesChange(session_id)); status = cdm_->update(session_id, response); - EXPECT_EQ(Cdm::kSuccess, status); - Mock::VerifyAndClear(this); - - // Should not be able to load the session again after closing it. - status = cdm_->close(session_id); - ASSERT_EQ(Cdm::kSuccess, status); - EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(0); - status = cdm_->load(session_id); - EXPECT_EQ(Cdm::kSessionNotFound, status); + EXPECT_EQ(Cdm::kRangeError, status); Mock::VerifyAndClear(this); } TEST_F(CdmTest, Renewal) { std::string session_id; - ASSERT_NO_FATAL_FAILURE(CreateTemporarySessionAndUpdate(&session_id)); + ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate(Cdm::kTemporary, &session_id)); // We should have a timer. EXPECT_NE(0, g_host->NumTimers()); @@ -1044,9 +1159,10 @@ TEST_F(CdmTest, ServerCertificateProvisioning) { Cdm::Status status = cdm_->createSession(Cdm::kTemporary, &session_id); ASSERT_EQ(Cdm::kSuccess, status); - // Expect an individualization request. + // Expect a license request type message, but this is actually a server cert + // provisioning request. std::string message; - EXPECT_CALL(*this, onMessage(session_id, Cdm::kIndividualizationRequest, _)). + EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)). WillOnce(SaveArg<2>(&message)); status = cdm_->generateRequest(session_id, Cdm::kCenc, kCencInitData); ASSERT_EQ(Cdm::kSuccess, status); @@ -1058,7 +1174,7 @@ TEST_F(CdmTest, ServerCertificateProvisioning) { // No keys will change, since this wasn't a license. EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(0); - // We should get an actual license request generated during update. + // We should get another license request generated during update. message.clear(); EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)).WillOnce( SaveArg<2>(&message)); @@ -1076,7 +1192,7 @@ TEST_F(CdmTest, ServerCertificateProvisioning) { ASSERT_NO_FATAL_FAILURE(FetchLicense(message, &response)); // Update the session. The keys will change now. - EXPECT_CALL(*this, onKeyStatusesChange(session_id)); + EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(1); status = cdm_->update(session_id, response); ASSERT_EQ(Cdm::kSuccess, status); Mock::VerifyAndClear(this); @@ -1087,17 +1203,30 @@ TEST_F(CdmTest, ServerCertificateProvisioning) { ASSERT_FALSE(map.empty()); EXPECT_EQ(Cdm::kUsable, map.begin()->second); + // Create another session. This one should not require server certificate // provisioning. status = cdm_->createSession(Cdm::kTemporary, &session_id); ASSERT_EQ(Cdm::kSuccess, status); - // Expect a license request, not an individualization request. - EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)); + // Expect a license request. + message.clear(); + EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)).WillOnce( + SaveArg<2>(&message)); status = cdm_->generateRequest(session_id, Cdm::kCenc, kCencInitData); ASSERT_EQ(Cdm::kSuccess, status); Mock::VerifyAndClear(this); + // Relay it to the server. + ASSERT_NO_FATAL_FAILURE(FetchLicense(message, &response)); + + // Keys will change, since this was an actual license. + EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(1); + status = cdm_->update(session_id, response); + ASSERT_EQ(Cdm::kSuccess, status); + Mock::VerifyAndClear(this); + + // Create a second CDM instance. scoped_ptr cdm2; CreateAdditionalCdm(true /* privacy_mode */, &cdm2); @@ -1107,20 +1236,45 @@ TEST_F(CdmTest, ServerCertificateProvisioning) { // instances. status = cdm2->createSession(Cdm::kTemporary, &session_id); ASSERT_EQ(Cdm::kSuccess, status); - EXPECT_CALL(*this, onMessage(session_id, Cdm::kIndividualizationRequest, _)); + message.clear(); + EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)).WillOnce( + SaveArg<2>(&message)); status = cdm2->generateRequest(session_id, Cdm::kCenc, kCencInitData); ASSERT_EQ(Cdm::kSuccess, status); Mock::VerifyAndClear(this); + // Relay it to the server. + ASSERT_NO_FATAL_FAILURE(FetchLicense(message, &response)); + + // No keys will change, since this wasn't a license. + EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(0); + // We should get another license request generated during update. + EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)); + status = cdm2->update(session_id, response); + ASSERT_EQ(Cdm::kSuccess, status); + Mock::VerifyAndClear(this); + + // Create another session on the first CDM. This one should not require // server certificate provisioning. This proves that the creation of the // second CDM instance did not affect the state of the first. status = cdm_->createSession(Cdm::kTemporary, &session_id); ASSERT_EQ(Cdm::kSuccess, status); - EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)); + message.clear(); + EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)).WillOnce( + SaveArg<2>(&message)); status = cdm_->generateRequest(session_id, Cdm::kCenc, kCencInitData); ASSERT_EQ(Cdm::kSuccess, status); Mock::VerifyAndClear(this); + + // Relay it to the server. + ASSERT_NO_FATAL_FAILURE(FetchLicense(message, &response)); + + // Keys will change, since this was an actual license. + EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(1); + status = cdm_->update(session_id, response); + ASSERT_EQ(Cdm::kSuccess, status); + Mock::VerifyAndClear(this); } TEST_F(CdmTest, SetAppParameters) { @@ -1140,19 +1294,19 @@ TEST_F(CdmTest, SetAppParameters) { // Try to get using a null result. status = cdm_->getAppParameter(kParamName, NULL); - ASSERT_EQ(Cdm::kInvalidAccess, status); + ASSERT_EQ(Cdm::kTypeError, status); // Try to get using an empty key. status = cdm_->getAppParameter("", &result); - ASSERT_EQ(Cdm::kInvalidAccess, status); + ASSERT_EQ(Cdm::kTypeError, status); // Try to set using an empty key. status = cdm_->setAppParameter("", kValue); - ASSERT_EQ(Cdm::kInvalidAccess, status); + ASSERT_EQ(Cdm::kTypeError, status); // Try to remove using an empty key. status = cdm_->removeAppParameter(""); - ASSERT_EQ(Cdm::kInvalidAccess, status); + ASSERT_EQ(Cdm::kTypeError, status); // Change an existing app parameter. status = cdm_->setAppParameter(kParamName, kNewValue); @@ -1165,11 +1319,11 @@ TEST_F(CdmTest, SetAppParameters) { status = cdm_->removeAppParameter(kParamName); ASSERT_EQ(Cdm::kSuccess, status); status = cdm_->getAppParameter(kParamName, &result); - ASSERT_EQ(Cdm::kInvalidAccess, status); + ASSERT_EQ(Cdm::kTypeError, status); // Try to remove an absent value. status = cdm_->removeAppParameter(kParamName2); - ASSERT_EQ(Cdm::kInvalidAccess, status); + ASSERT_EQ(Cdm::kTypeError, status); // Set some values to check for. status = cdm_->setAppParameter(kParamName, kValue); @@ -1198,9 +1352,9 @@ TEST_F(CdmTest, SetAppParameters) { status = cdm_->clearAppParameters(); ASSERT_EQ(Cdm::kSuccess, status); status = cdm_->getAppParameter(kParamName, &result); - ASSERT_EQ(Cdm::kInvalidAccess, status); + ASSERT_EQ(Cdm::kTypeError, status); status = cdm_->getAppParameter(kParamName2, &result); - ASSERT_EQ(Cdm::kInvalidAccess, status); + ASSERT_EQ(Cdm::kTypeError, status); } } // namespace widevine diff --git a/cdm/test/cdm_test_printers.cpp b/cdm/test/cdm_test_printers.cpp index 4795ad49..23565990 100644 --- a/cdm/test/cdm_test_printers.cpp +++ b/cdm/test/cdm_test_printers.cpp @@ -32,7 +32,7 @@ void PrintTo(const Cdm::Status& value, ::std::ostream* os) { break; case Cdm::kNoKey: *os << "Cdm::kNoKey"; break; - case Cdm::kInvalidAccess: *os << "Cdm::kInvalidAccess"; + case Cdm::kTypeError: *os << "Cdm::kTypeError"; break; case Cdm::kNotSupported: *os << "Cdm::kNotSupported"; break; @@ -40,6 +40,8 @@ void PrintTo(const Cdm::Status& value, ::std::ostream* os) { break; case Cdm::kQuotaExceeded: *os << "Cdm::kQuotaExceeded"; break; + case Cdm::kRangeError: *os << "Cdm::kRangeError"; + break; case Cdm::kUnexpectedError: *os << "Cdm::kUnexpectedError"; break; default: *os << "Unknown Cdm::Status value " << value; @@ -53,7 +55,7 @@ void PrintTo(const Cdm::KeyStatus& value, ::std::ostream* os) { break; case Cdm::kExpired: *os << "Cdm::kExpired"; break; - case Cdm::kOutputNotAllowed: *os << "Cdm::kOutputNotAllowed"; + case Cdm::kOutputRestricted: *os << "Cdm::kOutputRestricted"; break; case Cdm::kStatusPending: *os << "Cdm::kStatusPending"; break; diff --git a/core/include/cdm_engine.h b/core/include/cdm_engine.h index db8cf5bb..3b77b098 100644 --- a/core/include/cdm_engine.h +++ b/core/include/cdm_engine.h @@ -146,6 +146,8 @@ class CdmEngine { virtual CdmResponseType ReleaseAllUsageInfo(const std::string& app_id); virtual CdmResponseType ReleaseUsageInfo( const CdmUsageInfoReleaseMessage& message); + virtual CdmResponseType LoadUsageSession(const CdmKeySetId& key_set_id, + CdmKeyMessage* release_message); // Decryption and key related methods // Accept encrypted buffer and return decrypted data. @@ -173,6 +175,7 @@ class CdmEngine { private: // private methods + void DeleteAllUsageReportsUponFactoryReset(); bool ValidateKeySystem(const CdmKeySystem& key_system); CdmResponseType GetUsageInfo(const std::string& app_id, SecurityLevel requested_security_level, diff --git a/core/include/cdm_session.h b/core/include/cdm_session.h index 24d61319..d473d16b 100644 --- a/core/include/cdm_session.h +++ b/core/include/cdm_session.h @@ -104,6 +104,8 @@ class CdmSession { virtual bool is_release() { return is_release_; } virtual bool is_offline() { return is_offline_; } + virtual bool is_temporary() { return is_temporary_; } + virtual bool license_received() { return license_received_; } // ReleaseCrypto() - Closes the underlying crypto session but leaves this // object alive. It is invalid to call any method that requires a crypto @@ -141,6 +143,7 @@ class CdmSession { bool license_received_; bool is_offline_; bool is_release_; + bool is_temporary_; CdmSecurityLevel security_level_; SecurityLevel requested_security_level_; CdmAppParameterMap app_parameters_; diff --git a/core/include/device_files.h b/core/include/device_files.h index f7cc0636..a3e5f09d 100644 --- a/core/include/device_files.h +++ b/core/include/device_files.h @@ -67,11 +67,13 @@ class DeviceFiles { virtual bool DeleteAllLicenses(); virtual bool LicenseExists(const std::string& key_set_id); virtual bool ReserveLicenseId(const std::string& key_set_id); + virtual bool UnreserveLicenseId(const std::string& key_set_id); virtual bool StoreUsageInfo(const std::string& provider_session_token, const CdmKeyMessage& key_request, const CdmKeyResponse& key_response, - const std::string& app_id); + const std::string& app_id, + const std::string& key_set_id); virtual bool DeleteUsageInfo(const std::string& app_id, const std::string& provider_session_token); // Delete usage information from the file system. Puts a list of all the @@ -90,6 +92,12 @@ class DeviceFiles { const std::string& provider_session_token, CdmKeyMessage* license_request, CdmKeyResponse* license_response); + // Retrieve the usage info entry specified by |key_set_id|. + // Returns false if the entry could not be found. + virtual bool RetrieveUsageInfoByKeySetId(const std::string& app_id, + const std::string& key_set_id, + CdmKeyMessage* license_request, + CdmKeyResponse* license_response); private: // Helpers that wrap the File interface and automatically handle hashing, as diff --git a/core/include/license.h b/core/include/license.h index 7e7912e6..a6a9a82a 100644 --- a/core/include/license.h +++ b/core/include/license.h @@ -56,6 +56,10 @@ class CdmLicense { return provider_session_token_; } + virtual bool is_offline() { + return is_offline_; + } + static CdmResponseType VerifySignedServiceCertificate( const std::string& signed_service_certificate); @@ -89,6 +93,7 @@ class CdmLicense { std::set loaded_keys_; std::string provider_session_token_; bool renew_with_client_id_; + bool is_offline_; // Used for certificate based licensing CdmKeyMessage key_request_; diff --git a/core/include/wv_cdm_types.h b/core/include/wv_cdm_types.h index 4de2d9c1..06180447 100644 --- a/core/include/wv_cdm_types.h +++ b/core/include/wv_cdm_types.h @@ -207,6 +207,12 @@ enum CdmResponseType { DUPLICATE_SESSION_ID_SPECIFIED, LICENSE_RENEWAL_PROHIBITED, EMPTY_PROVISIONING_CERTIFICATE_2, + OFFLINE_LICENSE_PROHIBITED, + STORAGE_PROHIBITED, + EMPTY_KEYSET_ID_ENG_5, + SESSION_NOT_FOUND_11, + LOAD_USAGE_INFO_FILE_ERROR, + LOAD_USAGE_INFO_MISSING, }; enum CdmKeyStatus { @@ -229,6 +235,9 @@ enum CdmLicenseType { // If the original request was saved to make a service certificate request, // use Deferred for the license type in the subsequent request. kLicenseTypeDeferred, + // Like Streaming, but stricter. Does not permit storage of any kind. + // Named after the 'temporary' session type in EME, which has this behavior. + kLicenseTypeTemporary, }; enum SecurityLevel { diff --git a/core/src/cdm_engine.cpp b/core/src/cdm_engine.cpp index 620c7faf..18bcdeb4 100644 --- a/core/src/cdm_engine.cpp +++ b/core/src/cdm_engine.cpp @@ -2,6 +2,7 @@ #include "cdm_engine.h" +#include #include #include @@ -10,6 +11,7 @@ #include "cdm_session.h" #include "clock.h" #include "device_files.h" +#include "file_store.h" #include "license_protocol.pb.h" #include "log.h" #include "properties.h" @@ -227,7 +229,8 @@ CdmResponseType CdmEngine::GenerateKeyRequest( key_request->clear(); - if (license_type == kLicenseTypeRelease) { + if (license_type == kLicenseTypeRelease && + !iter->second->license_received()) { sts = iter->second->RestoreOfflineSession(key_set_id, kLicenseTypeRelease); if (sts != KEY_ADDED) { LOGE("CdmEngine::GenerateKeyRequest: key release restoration failed," @@ -338,7 +341,7 @@ CdmResponseType CdmEngine::RestoreKey(const CdmSessionId& session_id, if (sts != KEY_ADDED && sts != GET_RELEASED_LICENSE_ERROR) { LOGE("CdmEngine::RestoreKey: restore offline session failed = %d", sts); } - return sts; // TODO ewew + return sts; } CdmResponseType CdmEngine::RemoveKeys(const CdmSessionId& session_id) { @@ -602,6 +605,9 @@ CdmResponseType CdmEngine::GetProvisioningRequest( LOGE("CdmEngine::GetProvisioningRequest: invalid output parameters"); return INVALID_PROVISIONING_REQUEST_PARAM_2; } + + DeleteAllUsageReportsUponFactoryReset(); + if (NULL == cert_provisioning_.get()) { cert_provisioning_.reset(new CertificateProvisioning()); } @@ -663,6 +669,7 @@ CdmResponseType CdmEngine::HandleProvisioningResponse( } return NO_ERROR; } + CdmResponseType ret = cert_provisioning_->HandleProvisioningResponse( origin, response, cert, wrapped_key); // Release resources only on success. It is possible that a provisioning @@ -928,6 +935,66 @@ CdmResponseType CdmEngine::ReleaseUsageInfo( return status; } +CdmResponseType CdmEngine::LoadUsageSession(const CdmKeySetId& key_set_id, + CdmKeyMessage* release_message) { + LOGI("CdmEngine::LoadUsageSession"); + // This method is currently only used by the CE CDM, in which all session IDs + // are key set IDs. + assert(Properties::AlwaysUseKeySetIds()); + + if (key_set_id.empty()) { + LOGE("CdmEngine::LoadUsageSession: invalid key set id"); + return EMPTY_KEYSET_ID_ENG_5; + } + + CdmSessionMap::iterator iter = sessions_.find(key_set_id); + if (iter == sessions_.end()) { + LOGE("CdmEngine::LoadUsageSession: session_id not found = %s ", + key_set_id.c_str()); + return SESSION_NOT_FOUND_11; + } + + DeviceFiles handle; + if (!handle.Init(iter->second->GetSecurityLevel())) { + LOGE("CdmEngine::LoadUsageSession: unable to initialize device files"); + return LOAD_USAGE_INFO_FILE_ERROR; + } + + std::string app_id; + iter->second->GetApplicationId(&app_id); + + CdmKeyMessage key_message; + CdmKeyResponse key_response; + if (!handle.RetrieveUsageInfoByKeySetId(app_id, key_set_id, &key_message, + &key_response)) { + LOGE("CdmEngine::LoadUsageSession: unable to find usage information"); + return LOAD_USAGE_INFO_MISSING; + } + + CdmResponseType status = + iter->second->RestoreUsageSession(key_message, key_response); + if (KEY_ADDED != status) { + LOGE("CdmEngine::LoadUsageSession: usage session error %ld", status); + return status; + } + + std::string server_url; + status = iter->second->GenerateReleaseRequest(release_message, &server_url); + + switch (status) { + case KEY_MESSAGE: + break; + case KEY_CANCELED: // usage information not present in + iter->second->DeleteLicense(); // OEMCrypto, delete and try again + break; + default: + LOGE("CdmEngine::LoadUsageSession: generate release request error: %d", + status); + break; + } + return status; +} + CdmResponseType CdmEngine::Decrypt(const CdmSessionId& session_id, const CdmDecryptionParameters& parameters) { if (parameters.key_id == NULL) { @@ -1097,4 +1164,34 @@ std::string CdmEngine::MapHdcpVersion( return ""; } +void CdmEngine::DeleteAllUsageReportsUponFactoryReset() { + std::string device_base_path_level1 = ""; + std::string device_base_path_level3 = ""; + Properties::GetDeviceFilesBasePath(kSecurityLevelL1, + &device_base_path_level1); + Properties::GetDeviceFilesBasePath(kSecurityLevelL3, + &device_base_path_level3); + + File file; + if (!file.Exists(device_base_path_level1) && + !file.Exists(device_base_path_level3)) { + scoped_ptr crypto_session(new CryptoSession()); + CdmResponseType status = crypto_session->Open( + cert_provisioning_requested_security_level_); + if (NO_ERROR == status) { + status = crypto_session->DeleteAllUsageReports(); + if (NO_ERROR != status) { + LOGW( + "CdmEngine::GetProvisioningRequest: " + "Fails to delete usage reports: %d", status); + } + } else { + LOGW( + "CdmEngine::GetProvisioningRequest: " + "Fails to open crypto session: error=%d.\n" + "Usage reports are not removed after factory reset.", status); + } + } +} + } // namespace wvcdm diff --git a/core/src/cdm_session.cpp b/core/src/cdm_session.cpp index 050cfdba..46c0767b 100644 --- a/core/src/cdm_session.cpp +++ b/core/src/cdm_session.cpp @@ -37,6 +37,7 @@ CdmSession::CdmSession(CdmClientPropertySet* cdm_client_property_set, license_received_(false), is_offline_(false), is_release_(false), + is_temporary_(false), security_level_(kSecurityLevelUninitialized), requested_security_level_(kLevelDefault), is_initial_decryption_(true), @@ -66,7 +67,13 @@ CdmSession::CdmSession(CdmClientPropertySet* cdm_client_property_set, } } -CdmSession::~CdmSession() { Properties::RemoveSessionPropertySet(session_id_); } +CdmSession::~CdmSession() { + if (!key_set_id_.empty()) { + // Unreserve the license ID. + file_handle_->UnreserveLicenseId(key_set_id_); + } + Properties::RemoveSessionPropertySet(session_id_); +} CdmResponseType CdmSession::Init() { if (session_id_.empty()) { @@ -184,6 +191,9 @@ CdmResponseType CdmSession::GenerateKeyRequest( } switch (license_type) { + case kLicenseTypeTemporary: + is_temporary_ = true; + break; case kLicenseTypeStreaming: is_offline_ = false; break; @@ -207,6 +217,8 @@ CdmResponseType CdmSession::GenerateKeyRequest( license_type = kLicenseTypeRelease; } else if (is_offline_) { license_type = kLicenseTypeOffline; + } else if (is_temporary_) { + license_type = kLicenseTypeTemporary; } else { license_type = kLicenseTypeStreaming; } @@ -484,12 +496,22 @@ bool CdmSession::GenerateKeySetId(CdmKeySetId* key_set_id) { } CdmResponseType CdmSession::StoreLicense() { + if (is_temporary_) { + LOGE("CdmSession::StoreLicense: Session type prohibits storage."); + return STORAGE_PROHIBITED; + } + if (is_offline_) { if (key_set_id_.empty()) { LOGE("CdmSession::StoreLicense: No key set ID"); return EMPTY_KEYSET_ID; } + if (!license_parser_->is_offline()) { + LOGE("CdmSession::StoreLicense: License policy prohibits storage."); + return OFFLINE_LICENSE_PROHIBITED; + } + if (!StoreLicense(DeviceFiles::kLicenseStateActive)) { LOGE("CdmSession::StoreLicense: Unable to store license"); CdmResponseType sts = Init(); @@ -502,7 +524,7 @@ CdmResponseType CdmSession::StoreLicense() { return STORE_LICENSE_ERROR_1; } return NO_ERROR; - } + } // if (is_offline_) std::string provider_session_token = license_parser_->provider_session_token(); @@ -519,7 +541,7 @@ CdmResponseType CdmSession::StoreLicense() { std::string app_id; GetApplicationId(&app_id); if (!file_handle_->StoreUsageInfo(provider_session_token, key_request_, - key_response_, app_id)) { + key_response_, app_id, key_set_id_)) { LOGE("CdmSession::StoreLicense: Unable to store usage info"); return STORE_USAGE_INFO_ERROR; } diff --git a/core/src/device_files.cpp b/core/src/device_files.cpp index 4eda0f46..a17b948e 100644 --- a/core/src/device_files.cpp +++ b/core/src/device_files.cpp @@ -343,10 +343,20 @@ bool DeviceFiles::ReserveLicenseId(const std::string& key_set_id) { return true; } +bool DeviceFiles::UnreserveLicenseId(const std::string& key_set_id) { + if (!initialized_) { + LOGW("DeviceFiles::UnreserveLicenseId: not initialized"); + return false; + } + reserved_license_ids_.erase(key_set_id); + return true; +} + bool DeviceFiles::StoreUsageInfo(const std::string& provider_session_token, const CdmKeyMessage& key_request, const CdmKeyResponse& key_response, - const std::string& app_id) { + const std::string& app_id, + const std::string& key_set_id) { if (!initialized_) { LOGW("DeviceFiles::StoreUsageInfo: not initialized"); return false; @@ -372,6 +382,7 @@ bool DeviceFiles::StoreUsageInfo(const std::string& provider_session_token, provider_session_token.size()); provider_session->set_license_request(key_request.data(), key_request.size()); provider_session->set_license(key_response.data(), key_response.size()); + provider_session->set_key_set_id(key_set_id.data(), key_set_id.size()); file.SerializeToString(&serialized_file); return StoreFileWithHash(file_name, serialized_file); @@ -513,22 +524,48 @@ bool DeviceFiles::RetrieveUsageInfo(const std::string& app_id, LOGW("DeviceFiles::RetrieveUsageInfo: Unable to parse file"); return false; } + int index = 0; - bool found = false; for (; index < file.usage_info().sessions_size(); ++index) { if (file.usage_info().sessions(index).token() == provider_session_token) { - found = true; - break; + *license_request = file.usage_info().sessions(index).license_request(); + *license_response = file.usage_info().sessions(index).license(); + return true; } } - if (!found) { + return false; +} + +bool DeviceFiles::RetrieveUsageInfoByKeySetId( + const std::string& app_id, + const std::string& key_set_id, + CdmKeyMessage* license_request, + CdmKeyResponse* license_response) { + if (!initialized_) { + LOGW("DeviceFiles::RetrieveUsageInfoByKeySetId: not initialized"); + return false; + } + std::string serialized_file; + std::string file_name = GetUsageInfoFileName(app_id); + if (!RetrieveHashedFile(file_name, &serialized_file)) return false; + + video_widevine_client::sdk::File file; + if (!file.ParseFromString(serialized_file)) { + LOGW("DeviceFiles::RetrieveUsageInfoByKeySetId: Unable to parse file"); return false; } - *license_request = file.usage_info().sessions(index).license_request(); - *license_response = file.usage_info().sessions(index).license(); - return true; + int index = 0; + for (; index < file.usage_info().sessions_size(); ++index) { + if (file.usage_info().sessions(index).key_set_id() == key_set_id) { + *license_request = file.usage_info().sessions(index).license_request(); + *license_response = file.usage_info().sessions(index).license(); + return true; + } + } + + return false; } bool DeviceFiles::StoreFileWithHash(const std::string& name, diff --git a/core/src/device_files.proto b/core/src/device_files.proto index 468b4252..e9b793fd 100644 --- a/core/src/device_files.proto +++ b/core/src/device_files.proto @@ -46,6 +46,7 @@ message UsageInfo { optional bytes token = 1; optional bytes license_request = 2; optional bytes license = 3; + optional bytes key_set_id = 4; } repeated ProviderSession sessions = 1; diff --git a/core/src/license.cpp b/core/src/license.cpp index a2a5d297..72af4db7 100644 --- a/core/src/license.cpp +++ b/core/src/license.cpp @@ -132,6 +132,7 @@ CdmLicense::CdmLicense(const CdmSessionId& session_id) session_id_(session_id), initialized_(false), renew_with_client_id_(false), + is_offline_(false), clock_(new Clock()) {} CdmLicense::CdmLicense(const CdmSessionId& session_id, Clock* clock) @@ -139,7 +140,8 @@ CdmLicense::CdmLicense(const CdmSessionId& session_id, Clock* clock) policy_engine_(NULL), session_id_(session_id), initialized_(false), - renew_with_client_id_(false) { + renew_with_client_id_(false), + is_offline_(false) { clock_.reset(clock); } @@ -522,6 +524,10 @@ CdmResponseType CdmLicense::HandleKeyResponse( return NO_CONTENT_KEY; } + if (license.id().type() == video_widevine_server::sdk::OFFLINE && + license.policy().can_persist()) + is_offline_ = true; + if (license.id().has_provider_session_token()) provider_session_token_ = license.id().provider_session_token(); @@ -1102,6 +1108,7 @@ bool CdmLicense::PrepareContentId(const CdmLicenseType license_type, content_id->set_license_type(video_widevine_server::sdk::OFFLINE); break; case kLicenseTypeStreaming: + case kLicenseTypeTemporary: content_id->set_license_type(video_widevine_server::sdk::STREAMING); break; default: diff --git a/core/test/cdm_engine_test.cpp b/core/test/cdm_engine_test.cpp index 0418728a..84e6e71b 100644 --- a/core/test/cdm_engine_test.cpp +++ b/core/test/cdm_engine_test.cpp @@ -200,7 +200,7 @@ TEST_F(WvCdmEngineTest, BaseIsoBmffMessageTest) { } // TODO(juce): Set up with correct test data. -TEST_F(WvCdmEngineTest, DISABLED_BaseWebmMessageTest) { +TEST_F(WvCdmEngineTest, BaseWebmMessageTest) { GenerateKeyRequest(g_key_id_unwrapped, kWebmMimeType); GetKeyRequestResponse(g_license_server, g_client_auth); } @@ -220,7 +220,7 @@ TEST_F(WvCdmEngineTest, NormalDecryptionIsoBmff) { } // TODO(juce): Set up with correct test data. -TEST_F(WvCdmEngineTest, DISABLED_NormalDecryptionWebm) { +TEST_F(WvCdmEngineTest, NormalDecryptionWebm) { GenerateKeyRequest(g_key_id_unwrapped, kWebmMimeType); VerifyNewKeyResponse(g_license_server, g_client_auth); } diff --git a/core/test/device_files_unittest.cpp b/core/test/device_files_unittest.cpp index 4c757932..340ed059 100644 --- a/core/test/device_files_unittest.cpp +++ b/core/test/device_files_unittest.cpp @@ -21,6 +21,7 @@ const uint32_t kProtobufEstimatedOverhead = 75; const uint32_t kLicenseRequestLen = 300; const uint32_t kLicenseLen = 500; const uint32_t kProviderSessionTokenLen = 128; +const uint32_t kKeySetIdLen = 20; // Structurally valid test certificate. // The data elements in this module are used to test the storage and @@ -1536,6 +1537,16 @@ MATCHER_P4(Contains, str1, str2, str3, size, "") { data.find(str2) != std::string::npos && data.find(str3) != std::string::npos); } +MATCHER_P5(Contains, str1, str2, str3, str4, size, "") { + // Estimating the length of data. We can have gmock provide length + // as well as pointer to data but that will introduce a dependency on tr1 + std::string data(arg, size + str1.size() + str2.size() + str3.size() + + str4.size() + kProtobufEstimatedOverhead); + return (data.find(str1) != std::string::npos && + data.find(str2) != std::string::npos && + data.find(str3) != std::string::npos && + data.find(str4) != std::string::npos); +} MATCHER_P6(Contains, str1, str2, str3, str4, str5, str6, "") { // Estimating the length of data. We can have gmock provide length // as well as pointer to data but that will introduce a dependency on tr1 @@ -1549,7 +1560,6 @@ MATCHER_P6(Contains, str1, str2, str3, str4, str5, str6, "") { data.find(str5) != std::string::npos && data.find(str6) != std::string::npos); } - MATCHER_P7(Contains, str1, str2, str3, str4, str5, str6, map7, "") { // Estimating the length of data. We can have gmock provide length // as well as pointer to data but that will introduce a dependency on tr1 @@ -2140,6 +2150,8 @@ TEST_F(DeviceFilesTest, ReserveLicenseIdsDoesNotUseFileSystem) { EXPECT_TRUE(device_files.ReserveLicenseId(license_test_data[i].key_set_id)); // Validate that the license IDs are actually reserved. EXPECT_TRUE(device_files.LicenseExists(license_test_data[i].key_set_id)); + // Unreserve these IDs to avoid polluting other tests. + EXPECT_TRUE(device_files.UnreserveLicenseId(license_test_data[i].key_set_id)); } } @@ -2205,6 +2217,7 @@ TEST_P(DeviceFilesUsageInfoTest, Store) { std::string pst(GenerateRandomData(kProviderSessionTokenLen)); std::string license_request(GenerateRandomData(kLicenseRequestLen)); std::string license(GenerateRandomData(kLicenseLen)); + std::string key_set_id(GenerateRandomData(kKeySetIdLen)); std::string path = device_base_path_ + DeviceFiles::GetUsageInfoFileName(app_id); @@ -2234,8 +2247,10 @@ TEST_P(DeviceFilesUsageInfoTest, Store) { } EXPECT_CALL(file, - Write(Contains(pst, license_request, license, data.size()), - Gt(pst.size() + license_request.size() + license.size()))) + Write(Contains(pst, license_request, license, key_set_id, + data.size()), + Gt(pst.size() + license_request.size() + license.size() + + key_set_id.size()))) .WillOnce(ReturnArg<1>()); DeviceFiles device_files; @@ -2243,7 +2258,8 @@ TEST_P(DeviceFilesUsageInfoTest, Store) { device_files.SetTestFile(&file); ASSERT_TRUE( - device_files.StoreUsageInfo(pst, license_request, license, app_id)); + device_files.StoreUsageInfo(pst, license_request, license, app_id, + key_set_id)); } TEST_P(DeviceFilesUsageInfoTest, Delete) { diff --git a/oemcrypto/test/oemcrypto_test.cpp b/oemcrypto/test/oemcrypto_test.cpp index e70e7398..6f53fdc8 100644 --- a/oemcrypto/test/oemcrypto_test.cpp +++ b/oemcrypto/test/oemcrypto_test.cpp @@ -5285,9 +5285,7 @@ TEST_P(UsageTableTestWithMAC, OfflineBadNonce) { Session s; s.open(); s.GenerateTestSessionKeys(); - s.FillSimpleMessage( - 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceOrEntry, - 42, pst); + s.FillSimpleMessage(0, wvoec_mock::kControlNonceOrEntry, 42, pst); s.EncryptAndSign(); uint8_t* pst_ptr = s.encrypted_license().pst; OEMCryptoResult sts = OEMCrypto_LoadKeys( @@ -5305,9 +5303,7 @@ TEST_P(UsageTableTestWithMAC, OfflineEmptyPST) { Session s; s.open(); s.GenerateTestSessionKeys(); - s.FillSimpleMessage( - 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceOrEntry, - s.get_nonce()); + s.FillSimpleMessage(0, wvoec_mock::kControlNonceOrEntry, s.get_nonce()); s.EncryptAndSign(); OEMCryptoResult sts = OEMCrypto_LoadKeys( s.session_id(), s.message_ptr(), sizeof(MessageData), &s.signature()[0], @@ -5367,8 +5363,7 @@ TEST_P(UsageTableTestWithMAC, BadRange) { Session s; s.open(); s.GenerateTestSessionKeys(); - s.FillSimpleMessage(0, wvoec_mock::kControlNonceOrEntry, - s.get_nonce(), pst); + s.FillSimpleMessage(0, wvoec_mock::kControlNonceOrEntry,s.get_nonce(), pst); s.EncryptAndSign(); uint8_t* pst_ptr = s.license().pst; // Bad: not in encrypted_license. ASSERT_NE(