diff --git a/libwvdrmengine/cdm/Android.bp b/libwvdrmengine/cdm/Android.bp index c7ea68af..a03c47ec 100644 --- a/libwvdrmengine/cdm/Android.bp +++ b/libwvdrmengine/cdm/Android.bp @@ -54,6 +54,8 @@ cc_library_static { CORE_SRC_DIR + "/license.cpp", CORE_SRC_DIR + "/license_key_status.cpp", CORE_SRC_DIR + "/oemcrypto_adapter_dynamic.cpp", + CORE_SRC_DIR + "/okp_fallback_policy.cpp", + CORE_SRC_DIR + "/okp_info.cpp", CORE_SRC_DIR + "/ota_keybox_provisioner.cpp", CORE_SRC_DIR + "/policy_engine.cpp", CORE_SRC_DIR + "/policy_timers.cpp", diff --git a/libwvdrmengine/cdm/core/include/cdm_engine.h b/libwvdrmengine/cdm/core/include/cdm_engine.h index 734cad36..e85cc9ba 100644 --- a/libwvdrmengine/cdm/core/include/cdm_engine.h +++ b/libwvdrmengine/cdm/core/include/cdm_engine.h @@ -24,11 +24,11 @@ #include "wv_cdm_types.h" namespace wvcdm { - class CdmClientPropertySet; class CdmEngineFactory; class CdmSession; class CryptoEngine; +class OtaKeyboxProvisioner; class UsagePropertySet; class WvCdmEventListener; @@ -379,6 +379,13 @@ class CdmEngine { void CloseExpiredReleaseSessions(); + // Returns "true" if |okp_provisioner_| should be checked. + bool OkpCheck(); + // Returns "true" if CdmEngine should always fallback to L3. + bool OkpIsInFallbackMode(); + void OkpTriggerFallback(); + void OkpCleanUp(); + // instance variables /* @@ -423,6 +430,23 @@ class CdmEngine { // occur that may subsequently call back into CdmEngine. std::recursive_mutex session_map_lock_; + // OTA Keybox Provisioning (OKP) + // Engine should check for the OKP status of the device before opening + // sessions or generating DRM cert provisioning requests. + bool okp_initialized_ = false; + // If OKP is required, then the engine should create an instance + // of |okp_provisioner_|. If the instance exists, it should be used + // for GetProvisionRequest, ProvideProvisionRequest, and + // OpenSession when requested with default security level. + std::unique_ptr okp_provisioner_; + // Should the engine need to fallback, this flag should be set to + // true and |okp_provisioner_| should be cleared. All follow-up + // requests from the app with security level default should use L3. + bool okp_fallback_ = false; + // To prevent race conditions around the engine's OKP state, this mutex + // should be locked before the use of any of the |okp_*| variables. + std::mutex okp_mutex_; + CORE_DISALLOW_COPY_AND_ASSIGN(CdmEngine); }; diff --git a/libwvdrmengine/cdm/core/include/cdm_session.h b/libwvdrmengine/cdm/core/include/cdm_session.h index 37a30055..226f6c80 100644 --- a/libwvdrmengine/cdm/core/include/cdm_session.h +++ b/libwvdrmengine/cdm/core/include/cdm_session.h @@ -60,9 +60,13 @@ class CdmSession { // // |event_listener| is caller owned, may be null, but must be in scope as long // as the session is in scope. + // + // |forced_level3|_is used to specify whether the "default" security level + // should always use L3 even if L1 is available. virtual CdmResponseType Init(CdmClientPropertySet* cdm_client_property_set, const CdmSessionId* forced_session_id, - WvCdmEventListener* event_listener); + WvCdmEventListener* event_listener, + bool forced_level3); // Restores an offline session identified by the |key_set_id| and // |license_type|. The |error_detail| will be filled with an internal error @@ -280,6 +284,9 @@ class CdmSession { bool is_temporary_; CdmSecurityLevel security_level_; SecurityLevel requested_security_level_; + // If |forced_level3_|, |security_level_| and |requested_security_level_| + // MUST be set to kSecurityLevelL3 and kLevel3, respectively. + bool forced_level3_ = false; CdmAppParameterMap app_parameters_; bool atsc_mode_enabled_ = false; std::string drm_certificate_; diff --git a/libwvdrmengine/cdm/core/include/certificate_provisioning.h b/libwvdrmengine/cdm/core/include/certificate_provisioning.h index 7baac324..972925cc 100644 --- a/libwvdrmengine/cdm/core/include/certificate_provisioning.h +++ b/libwvdrmengine/cdm/core/include/certificate_provisioning.h @@ -64,7 +64,7 @@ class CertificateProvisioning { // Removes json wrapping if applicable to extract the // SignedProvisioningMessage - static bool ExtractAndDecodeSignedMessageForTesting( + static bool ExtractAndDecodeSignedMessage( const std::string& provisioning_response, std::string* result); // Retrieve the provisioning server URL used for certificate diff --git a/libwvdrmengine/cdm/core/include/crypto_session.h b/libwvdrmengine/cdm/core/include/crypto_session.h index a052feed..6d04ba4a 100644 --- a/libwvdrmengine/cdm/core/include/crypto_session.h +++ b/libwvdrmengine/cdm/core/include/crypto_session.h @@ -23,12 +23,15 @@ #include "wv_cdm_types.h" namespace wvcdm { - class CryptoKey; class CryptoSessionFactory; class OtaKeyboxProvisioner; class UsageTableHeader; +namespace okp { +class SystemFallbackPolicy; +} // namespace okp + using CryptoKeyMap = std::map; // Crypto session utility functions used by KeySession implementations. @@ -42,7 +45,6 @@ OEMCrypto_Substring GetSubstring(const std::string& message = "", bool set_zero = false); OEMCryptoCipherMode ToOEMCryptoCipherMode(CdmCipherMode cipher_mode); - class CryptoSession { public: using HdcpCapability = OEMCrypto_HDCP_Capability; @@ -293,11 +295,11 @@ class CryptoSession { bool needs_keybox_provisioning() const { return needs_keybox_provisioning_; } - // Returns a system-wide singleton instance of OtaKeyboxProvisioner - // to be used for OTA provisioning requests/responses across apps. - // Returns a null pointer if OTA provisioning is NOT supported, or - // if the device has already been provisioned. - virtual OtaKeyboxProvisioner* GetOtaKeyboxProvisioner(); + // Returns a system-wide singleton instance of SystemFallbackPolicy + // to be used for communicating OTA keybox provisioning state between + // apps. Returns a null pointer if OTA provisioning is not supported, + // or if the device has already been provisioned. + static okp::SystemFallbackPolicy* GetOkpFallbackPolicy(); // Generates an OTA provisioning request. // This should only be called by an instance of OtaKeyboxProvisioner. @@ -513,10 +515,10 @@ class CryptoSession { static std::mutex factory_mutex_; static std::unique_ptr factory_; - // A singleton instance of OtaKeyboxProvisioner. Only one will + // A singleton instance of SystemFallbackPolicy. Only one will // be created for the system if OTA keybox provisioning is both // required and supported by L1. - static std::unique_ptr ota_keybox_provisioner_l1_; + static std::unique_ptr okp_fallback_policy_l1_; CORE_DISALLOW_COPY_AND_ASSIGN(CryptoSession); }; // class CryptoSession diff --git a/libwvdrmengine/cdm/core/include/device_files.h b/libwvdrmengine/cdm/core/include/device_files.h index c62001b5..9516471b 100644 --- a/libwvdrmengine/cdm/core/include/device_files.h +++ b/libwvdrmengine/cdm/core/include/device_files.h @@ -12,6 +12,7 @@ #include "crypto_wrapped_key.h" #include "device_files.pb.h" #include "disallow_copy_and_assign.h" +#include "okp_info.h" #include "platform.h" #include "wv_cdm_types.h" @@ -271,6 +272,11 @@ class DeviceFiles { virtual bool DeleteUsageTableInfo(); + // OTA Keybox Provisioning (OKP) information. + virtual bool StoreOkpInfo(const okp::SystemFallbackInfo& info); + virtual bool RetrieveOkpInfo(okp::SystemFallbackInfo* info); + virtual bool DeleteOkpInfo(); + private: // This method will retrieve the certificate and perform expiry validation // appropriate for a given certificate type @@ -299,6 +305,7 @@ class DeviceFiles { static std::string GetHlsAttributesFileNameExtension(); static std::string GetLicenseFileNameExtension(); static std::string GetUsageTableFileName(); + static std::string GetOkpInfoFileName(); static std::string GetFileNameSafeHash(const std::string& input); #if defined(UNIT_TEST) @@ -327,6 +334,9 @@ class DeviceFiles { FRIEND_TEST(DeviceFilesTest, StoreCertificateInvalidParams); FRIEND_TEST(DeviceFilesTest, StoreLicenses); FRIEND_TEST(DeviceFilesTest, UpdateLicenseState); + FRIEND_TEST(DeviceFilesTest, OkpInfo_FileDoesNotExist); + FRIEND_TEST(DeviceFilesTest, OkpInfo_DeleteFile); + FRIEND_TEST(DeviceFilesTest, OkpInfo_StoreAndRetrieve); FRIEND_TEST(DeviceFilesUsageInfoTest, Delete); FRIEND_TEST(DeviceFilesUsageInfoTest, DeleteAll); FRIEND_TEST(DeviceFilesUsageInfoTest, Read); diff --git a/libwvdrmengine/cdm/core/include/okp_fallback_policy.h b/libwvdrmengine/cdm/core/include/okp_fallback_policy.h new file mode 100644 index 00000000..36129a63 --- /dev/null +++ b/libwvdrmengine/cdm/core/include/okp_fallback_policy.h @@ -0,0 +1,113 @@ +// Copyright 2021 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +#ifndef WVCDM_CORE_OKP_FALLBACK_POLICY_H_ +#define WVCDM_CORE_OKP_FALLBACK_POLICY_H_ +#include + +#include +#include + +#include "clock.h" +#include "disallow_copy_and_assign.h" +#include "okp_info.h" + +namespace wvcdm { +class DeviceFiles; +class FileSystem; +// OTA Keybox Provisioning (OKP) +namespace okp { +static constexpr int64_t kSecondsPerHour = 60 * 60; +static constexpr int64_t kSecondsPerDay = kSecondsPerHour * 24; +// Initial backoff duration. Subsequent backoff durations for the +// same engine will double its previous duration. +static constexpr int64_t kAverageInitialBackoffDuration = kSecondsPerDay; +static constexpr int64_t kInitalBackoffDurationDelta = kSecondsPerHour * 12; +// Minimum backoff duration which an device will be required to +// backoff the first time. +static constexpr int64_t kMinInitialBackoffDuration = + kAverageInitialBackoffDuration - kInitalBackoffDurationDelta; +static constexpr int64_t kMaxInitialBackoffDuration = + kAverageInitialBackoffDuration + kInitalBackoffDurationDelta; + +// SystemFallbackPolicy is a centralized OKP state manager which allows +// multiple CDM engines to communicate between each other. In a production +// build, there should only be at most one SystemFallbackPolicy instance. +class SystemFallbackPolicy { + public: + // Creates a new instance of SystemFallbackPolicy. If there exists + // OKP information for the device in storage, it will be loaded and + // the system policy will resume from its previous state. If no + // OKP information exists, then the policy begins new. + // Caller should immediately mark the fallback policy as requiring + // provisioning. + static std::unique_ptr Create(); + // Creates a new instance of SystemFallbackPolicy for testing. + // The testing instance of SystemFallbackPolicy behaves similar to a + // production instance, except that it will not use device storage. + // Optionally, a fake clock may be used for timestamp operations + // and/or fake data may be used to initialize the policy. + // Params: + // - |info| (optional) + // Fake device OKP info to use as a resume point. If not + // specified, then policy begins the same as if no OKP + // device info exists. + // - |clock| (optional) + // Fake/mock clock to be used instead of the CDM's default + // Clock. + static std::unique_ptr CreateForTesting( + Clock* clock = nullptr); + static std::unique_ptr CreateForTesting( + const SystemFallbackInfo& info, Clock* clock = nullptr); + + // == System Info == + const SystemFallbackInfo& info() const { return info_; } + SystemState state() const { return info_.state(); } + void MarkNeedsProvisioning(); + void TriggerFallback(); + void MarkProvisioned(); + + bool IsProvisioned(); + bool IsInFallbackMode(); + + ~SystemFallbackPolicy(); + + private: + SystemFallbackPolicy(); + + // Checks the device's file system for OKP info and loads it. + // If the info does not exist, policy begins fresh. + void TryRestore(); + + void StoreInfo(); + + int64_t GetSecondsSinceBackoffStart() const; + void EndBackoffPeriod(); + + void SetClockForTesting(Clock* clock) { + clock_ref_ = (clock == nullptr) ? &clock_ : clock; + } + int64_t GetCurrentTime() const { return clock_ref_->GetCurrentTime(); } + + bool IsTestMode() const; + + SystemFallbackInfo info_; + + // Handle for the DeviceFiles instance used to store the OKP + // information. + // Not set for test instances. + std::unique_ptr fs_; + std::unique_ptr device_files_; + + Clock clock_; // System clock + Clock* clock_ref_ = nullptr; // Pointer to clock to be used. + + // All public methods must lock to protect from simultaneous + // engine access. + mutable std::mutex mutex_; + + CORE_DISALLOW_COPY_AND_ASSIGN(SystemFallbackPolicy); +}; // class SystemFallbackPolicy +} // namespace okp +} // namespace wvcdm +#endif // WVCDM_CORE_OKP_FALLBACK_POLICY_H_ diff --git a/libwvdrmengine/cdm/core/include/okp_info.h b/libwvdrmengine/cdm/core/include/okp_info.h new file mode 100644 index 00000000..3bd91424 --- /dev/null +++ b/libwvdrmengine/cdm/core/include/okp_info.h @@ -0,0 +1,78 @@ +// Copyright 2021 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +#ifndef WVCDM_CORE_OKP_SYSTEM_INFO_H_ +#define WVCDM_CORE_OKP_SYSTEM_INFO_H_ + +#include + +namespace wvcdm { +// OTA Keybox Provisioning (OKP) +namespace okp { +enum class SystemState { + kUnknown = 0, + kNeedsProvisioning = 1, + kFallbackMode = 2, // Fallback indicates provisioning is needed. + kProvisioned = 3 + // Note: "Not needed" is represented by an absence of info. +}; +// Converts a SystemState value to a human readable string. Intended +// to be used for debug logging. +const char* SystemStateToString(SystemState state); + +// Container for all the device information related to OKP. +class SystemFallbackInfo { + public: + SystemState state() const { return state_; } + void SetState(SystemState state) { state_ = state; } + + bool HasFirstCheckedTime() const { return first_checked_time_ != 0; } + int64_t first_checked_time() const { return first_checked_time_; } + void SetFirstCheckedTime(int64_t time) { + first_checked_time_ = (time > 0 ? time : 0); + } + + bool HasBackoffStartTime() const { return backoff_start_time_ > 0; } + int64_t backoff_start_time() const { return backoff_start_time_; } + void SetBackoffStartTime(int64_t time) { + backoff_start_time_ = (time > 0 ? time : 0); + } + void ClearBackoffStartTime() { backoff_start_time_ = 0; } + + bool HasBackoffDuration() const { return backoff_duration_ > 0; } + int64_t backoff_duration() const { return backoff_duration_; } + void SetBackoffDuration(int64_t duration) { + backoff_duration_ = (duration > 0 ? duration : 0); + } + void DoubleBackoffDuration() { backoff_duration_ *= 2; } + + bool HasProvisioningTime() const { return provisioning_time_ != 0; } + int64_t provisioning_time() const { return provisioning_time_; } + void SetProvisioningTime(int64_t time) { + provisioning_time_ = (time > 0 ? time : 0); + } + void ClearProvisioningTime() { provisioning_time_ = 0; } + + void Clear() { + state_ = SystemState::kUnknown; + first_checked_time_ = 0; + backoff_start_time_ = 0; + backoff_duration_ = 0; + provisioning_time_ = 0; + } + + bool operator==(const SystemFallbackInfo& other) const; + bool operator!=(const SystemFallbackInfo& other) const { + return !(*this == other); + } + + private: + SystemState state_ = SystemState::kUnknown; + int64_t first_checked_time_ = 0; + int64_t backoff_start_time_ = 0; + int64_t backoff_duration_ = 0; + int64_t provisioning_time_ = 0; +}; // class SystemFallbackInfo +} // namespace okp +} // namespace wvcdm +#endif // WVCDM_CORE_OKP_SYSTEM_INFO_H_ diff --git a/libwvdrmengine/cdm/core/include/ota_keybox_provisioner.h b/libwvdrmengine/cdm/core/include/ota_keybox_provisioner.h index 297f16b8..0bb41dd5 100644 --- a/libwvdrmengine/cdm/core/include/ota_keybox_provisioner.h +++ b/libwvdrmengine/cdm/core/include/ota_keybox_provisioner.h @@ -5,62 +5,79 @@ #define WVCDM_CORE_OTA_KEYBOX_PROVISIONER_H_ #include -#include #include +#include "client_identification.h" #include "disallow_copy_and_assign.h" +#include "metrics_collections.h" #include "wv_cdm_types.h" namespace wvcdm { class CryptoSession; +namespace okp { +class SystemFallbackPolicy; +} // namespace okp -// Wrapper around an OEMCrypto system-wide OTA keybox provisioning -// workflow. +// A CdmEngine-specific OTA keybox provisioning context. class OtaKeyboxProvisioner { public: - // Creates a new OTA keybox provisioner. This should only be - // created once and object ownership belongs to the CryptoSession - // module. - static std::unique_ptr Create(); + // Creates a new OtaKeyboxProvisioner. + // Checks for the system fallback policy and if the device + // requires provisioning. + // |crypto_metrics| - CryptoMetrics instance that is used in the + // the calling EngineMetrics. + static std::unique_ptr Create( + metrics::CryptoMetrics* crypto_metrics); + static std::unique_ptr CreateForTesting( + std::unique_ptr&& crypto_session, + okp::SystemFallbackPolicy* fallback_policy); ~OtaKeyboxProvisioner(); + // Returns true if the underlying SystemFallbackPolicy is + // provisioned. + // Note: This may change without a call to HandleProvisioningResponse() + // on this instance as provisioning is a system-wide responsibility. + bool IsProvisioned() const; + bool IsInFallbackMode() const; + + // Indicates that a request has been successfully generated. + uint32_t request_generated() const { return request_generated_; } + // Indicates that a response has been successfully received by + // this provisioner. + bool response_received() const { return response_received_; } + // === Request/response API === - // Returns true if a provisioning response has been provided - // and accepted by OEMCrytpo. - bool IsProvisioned() const { return is_provisioned_; } - - uint32_t request_count() const { return request_count_; } - uint32_t response_count() const { return response_count_; } - - // Generates an OTA provisioning request. - // Generating a request will succeed so long as OTA provisioning - // is supported and no valid response has been provided. - CdmResponseType GenerateProvisioningRequest(CryptoSession* crypto_session, - std::string* request); - - // Accepts a provisioning response from the OTA provisioning - // server. The first response which is successfully loaded is - // is used. Any subsequent response after the first successful - // response is silently discarded. - CdmResponseType HandleProvisioningResponse(CryptoSession* crypto_session, - const std::string& response); + // Generates and prepares a OTA Keybox Provisioning request, packing + // it into a SignedProvisioningMessage. + // |default_url| will be populated with the URL of the provisioning + // server used for OTA keybox provisioning. + CdmResponseType GetProvisioningRequest(std::string* request, + std::string* default_url); + // Receives, unwraps and loads the OTA Keybox Provisioning response. + // |response| must be a SignedProvisioningMessage containing an + // OTA keybox provisioning response. + CdmResponseType HandleProvisioningResponse(const std::string& response); private: - OtaKeyboxProvisioner(); + OtaKeyboxProvisioner(std::unique_ptr&& crypto_session, + okp::SystemFallbackPolicy* fallback_policy); - bool is_provisioned_ = false; + bool Init(); - // These counters are for debugging purposes. - // Number of requests generated. - uint32_t request_count_ = 0; - // Number of responses provided. - uint32_t response_count_ = 0; + void CleanUp(); - // It is expected that multiple CDM engines may interact with the - // OtaKeyboxProvisioner instance simultaneously. - mutable std::mutex mutex_; + std::unique_ptr crypto_session_; + ClientIdentification client_id_; + + // Pointer to the system-wide okp::SystemFallbackPolicy. This class + // does not take ownership of this pointer. + okp::SystemFallbackPolicy* fallback_policy_ = nullptr; + + // These flags are for debugging purposes. + bool request_generated_ = false; + bool response_received_ = false; CORE_DISALLOW_COPY_AND_ASSIGN(OtaKeyboxProvisioner); }; // class OtaKeyboxProvisioner diff --git a/libwvdrmengine/cdm/core/include/wv_cdm_types.h b/libwvdrmengine/cdm/core/include/wv_cdm_types.h index 382f54de..e176997c 100644 --- a/libwvdrmengine/cdm/core/include/wv_cdm_types.h +++ b/libwvdrmengine/cdm/core/include/wv_cdm_types.h @@ -421,6 +421,8 @@ enum CdmResponseType : int32_t { CERT_PROVISIONING_RESPONSE_ERROR_10 = 366, CLIENT_TOKEN_NOT_SET = 367, USAGE_ENTRY_ALREADY_LOADED = 368, + PARSE_OKP_RESPONSE_ERROR = 369, + OKP_ALREADY_PROVISIONED = 370, // Don't forget to add new values to // * core/test/test_printers.cpp. // * android/include/mapErrors-inl.h diff --git a/libwvdrmengine/cdm/core/src/cdm_engine.cpp b/libwvdrmengine/cdm/core/src/cdm_engine.cpp index aa96dd12..dee205f4 100644 --- a/libwvdrmengine/cdm/core/src/cdm_engine.cpp +++ b/libwvdrmengine/cdm/core/src/cdm_engine.cpp @@ -21,6 +21,7 @@ #include "device_files.h" #include "file_store.h" #include "log.h" +#include "ota_keybox_provisioner.h" #include "properties.h" #include "string_conversions.h" #include "wv_cdm_constants.h" @@ -119,16 +120,56 @@ CdmResponseType CdmEngine::OpenSession(const CdmKeySystem& key_system, } LOGD("forced_session_id = %s", IdPtrToString(forced_session_id)); } + bool forced_level3 = false; + if (OkpCheck()) { + bool okp_provisioned = false; + bool fallback = false; + { + std::unique_lock lock(okp_mutex_); + if (!okp_provisioner_) { + // Very rare race condition. Possible if two calls to OpenSession + // occur the same time. Cleanup would have been performed. + if (okp_fallback_) { + fallback = true; + } else { + okp_provisioned = true; + } + } else if (okp_provisioner_->IsProvisioned()) { + okp_provisioned = true; + } else if (okp_provisioner_->IsInFallbackMode()) { + fallback = true; + } + } + if (okp_provisioned) { + // OKP not required, engine may assume normal operations. + OkpCleanUp(); + } else if (fallback) { + LOGD("Engine is falling back to L3"); + OkpTriggerFallback(); + forced_level3 = true; + } else { + // OKP is required. + return NEED_PROVISIONING; + } + } else { + std::unique_lock lock(okp_mutex_); + // |okp_fallback_| would have been set previously if required. + if (okp_fallback_) forced_level3 = true; + } CloseExpiredReleaseSessions(); std::unique_ptr new_session( new CdmSession(file_system_, metrics_->AddSession())); - const CdmResponseType sts = - new_session->Init(property_set, forced_session_id, event_listener); - if (sts == NEED_PROVISIONING) { - // Reserve a session ID so the CDM can return success. - if (session_id) *session_id = new_session->GenerateSessionId(); + const CdmResponseType sts = new_session->Init(property_set, forced_session_id, + event_listener, forced_level3); + if (sts != NO_ERROR) { + if (sts == NEED_PROVISIONING) { + // Reserve a session ID so the CDM can return success. + if (session_id) *session_id = new_session->GenerateSessionId(); + } else { + LOGE("Bad session init: status = %d", static_cast(sts)); + } return sts; } if (sts != NO_ERROR) { @@ -499,6 +540,14 @@ CdmResponseType CdmEngine::QueryStatus(SecurityLevel security_level, std::unique_ptr crypto_session( CryptoSession::MakeCryptoSession(metrics_->GetCryptoMetrics())); + // Force OKP check on CryptoSession. Only concerned if engine + // has fallen back to L3. + if (security_level == kLevelDefault && OkpIsInFallbackMode()) { + LOGD("Engine is falling back to L3 for query: token = %s", + query_token.c_str()); + security_level = kLevel3; + } + // Add queries here, that can be answered before a session is opened if (query_token == QUERY_KEY_SECURITY_LEVEL) { const CdmSecurityLevel found_security_level = @@ -877,6 +926,7 @@ CdmResponseType CdmEngine::QueryOemCryptoSessionId( return session->QueryOemCryptoSessionId(query_response); } +// static bool CdmEngine::IsSecurityLevelSupported(CdmSecurityLevel level) { LOGI("level = %s", CdmSecurityLevelToString(level)); metrics::CryptoMetrics alternate_crypto_metrics; @@ -914,6 +964,42 @@ CdmResponseType CdmEngine::GetProvisioningRequest( return INVALID_PROVISIONING_REQUEST_PARAM_2; } + if (requested_security_level == kLevelDefault) { + if (OkpCheck()) { + if (okp_provisioner_->IsProvisioned()) { + // OKP not required, engine may assume normal operations. + OkpCleanUp(); + } else if (okp_provisioner_->IsInFallbackMode()) { + LOGD("Engine is falling back to L3"); + OkpTriggerFallback(); + requested_security_level = kLevel3; + } else { + // OKP is required. + const CdmResponseType status = + okp_provisioner_->GetProvisioningRequest(request, default_url); + if (status == NO_ERROR) return NO_ERROR; + if (status == NOT_IMPLEMENTED_ERROR) { + LOGW("OKP not supoprted, falling back to L3"); + OkpTriggerFallback(); + requested_security_level = kLevel3; + } else if (status == OKP_ALREADY_PROVISIONED) { + LOGD("OKP already completed, continuing in normal operation"); + OkpCleanUp(); + // Continue with normal provisioning request. + } else { + LOGE("Failed to generate OKP request: status = %d", + static_cast(status)); + return status; + } + } + } else { + std::unique_lock lock(okp_mutex_); + if (okp_fallback_) { + requested_security_level = kLevel3; + } + } + } + // TODO(b/141705730): Remove usage entries on provisioning. if (!cert_provisioning_) { cert_provisioning_.reset( @@ -959,6 +1045,32 @@ CdmResponseType CdmEngine::HandleProvisioningResponse( cert_provisioning_.reset(); return INVALID_PROVISIONING_PARAMETERS_2; } + + if (requested_security_level == kLevelDefault) { + bool use_okp = false; + CdmResponseType okp_res = UNKNOWN_ERROR; + { + std::unique_lock lock(okp_mutex_); + if (okp_provisioner_) { + use_okp = true; + // If the engine initiated OKP previously, it must complete it + // regardless of whether the device has fallen back to L3. + okp_res = okp_provisioner_->HandleProvisioningResponse(response); + } else if (okp_fallback_) { + requested_security_level = kLevel3; + } + } + if (use_okp) { + // Cannot hold lock when calling OkpCleanUp() or OkpTriggerFallback(). + if (okp_res == NO_ERROR) { + OkpCleanUp(); + } else { + OkpTriggerFallback(); + } + return okp_res; + } + } + if (!cert_provisioning_) { // Certificate provisioning object has been released. Check if a concurrent // provisioning attempt has succeeded before declaring failure. @@ -991,6 +1103,11 @@ CdmResponseType CdmEngine::HandleProvisioningResponse( } bool CdmEngine::IsProvisioned(CdmSecurityLevel security_level) { + LOGI("security_level = %d", static_cast(security_level)); + if (security_level == kSecurityLevelL1 && OkpIsInFallbackMode()) { + LOGD("OKP fallback to L3"); + security_level = kSecurityLevelL3; + } // To validate whether the given security level is provisioned, we attempt to // initialize a CdmSession. This verifies the existence of a certificate and // attempts to load it. If this fails, initialization will return an error. @@ -1008,6 +1125,10 @@ bool CdmEngine::IsProvisioned(CdmSecurityLevel security_level) { CdmResponseType CdmEngine::Unprovision(CdmSecurityLevel security_level) { LOGI("security_level = %s", CdmSecurityLevelToString(security_level)); + if (security_level == kSecurityLevelL1 && OkpIsInFallbackMode()) { + LOGD("OKP fallback to L3"); + security_level = kSecurityLevelL3; + } // Devices with baked-in DRM certs cannot be reprovisioned and therefore must // not be unprovisioned. std::unique_ptr crypto_session( @@ -1046,11 +1167,15 @@ CdmResponseType CdmEngine::Unprovision(CdmSecurityLevel security_level) { CdmResponseType CdmEngine::ListStoredLicenses( CdmSecurityLevel security_level, std::vector* key_set_ids) { - DeviceFiles handle(file_system_); if (!key_set_ids) { LOGE("Output |key_set_ids| is null"); return INVALID_PARAMETERS_ENG_22; } + if (security_level == kSecurityLevelL1 && OkpIsInFallbackMode()) { + LOGD("OKP fallback to L3"); + security_level = kSecurityLevelL3; + } + DeviceFiles handle(file_system_); if (!handle.Init(security_level)) { LOGE("Unable to initialize device files"); return LIST_LICENSE_ERROR_1; @@ -1066,11 +1191,15 @@ CdmResponseType CdmEngine::ListUsageIds( const std::string& app_id, CdmSecurityLevel security_level, std::vector* ksids, std::vector* provider_session_tokens) { - DeviceFiles handle(file_system_); if (!ksids && !provider_session_tokens) { LOGE("Outputs |ksids| and |provider_session_tokens| are null"); return INVALID_PARAMETERS_ENG_23; } + if (security_level == kSecurityLevelL1 && OkpIsInFallbackMode()) { + LOGD("OKP fallback to L3"); + security_level = kSecurityLevelL3; + } + DeviceFiles handle(file_system_); if (!handle.Init(security_level)) { LOGE("Unable to initialize device files"); return LIST_USAGE_ERROR_1; @@ -1088,6 +1217,10 @@ CdmResponseType CdmEngine::DeleteUsageRecord(const std::string& app_id, const std::string& key_set_id) { LOGI("app_id = %s, key_set_id = %s", IdToString(app_id), IdToString(key_set_id)); + if (security_level == kSecurityLevelL1 && OkpIsInFallbackMode()) { + LOGD("OKP fallback to L3"); + security_level = kSecurityLevelL3; + } DeviceFiles handle(file_system_); if (!handle.Init(security_level)) { LOGE("Unable to initialize device files"); @@ -1105,12 +1238,15 @@ CdmResponseType CdmEngine::DeleteUsageRecord(const std::string& app_id, CdmResponseType CdmEngine::GetOfflineLicenseState( const CdmKeySetId& key_set_id, CdmSecurityLevel security_level, CdmOfflineLicenseState* license_state) { + if (security_level == kSecurityLevelL1 && OkpIsInFallbackMode()) { + LOGD("OKP fallback to L3"); + security_level = kSecurityLevelL3; + } DeviceFiles handle(file_system_); if (!handle.Init(security_level)) { LOGE("Cannot initialize device files"); return GET_OFFLINE_LICENSE_STATE_ERROR_1; } - DeviceFiles::CdmLicenseData license_data; DeviceFiles::ResponseType sub_error_code = DeviceFiles::kNoError; if (!handle.RetrieveLicense(key_set_id, &license_data, &sub_error_code)) { @@ -1124,6 +1260,10 @@ CdmResponseType CdmEngine::GetOfflineLicenseState( CdmResponseType CdmEngine::RemoveOfflineLicense( const CdmKeySetId& key_set_id, CdmSecurityLevel security_level) { + if (security_level == kSecurityLevelL1 && OkpIsInFallbackMode()) { + LOGD("OKP fallback to L3"); + security_level = kSecurityLevelL3; + } UsagePropertySet property_set; property_set.set_security_level( security_level == kSecurityLevelL3 ? kLevel3 : kLevelDefault); @@ -1289,7 +1429,10 @@ CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id, LOGE("Output |usage_info| is null"); return PARAMETER_NULL; } - + if (requested_security_level == kLevelDefault && OkpIsInFallbackMode()) { + LOGD("OKP fallback to L3"); + requested_security_level = kLevel3; + } if (!usage_property_set_) { usage_property_set_.reset(new UsagePropertySet()); } @@ -1939,4 +2082,76 @@ void CdmEngine::CloseExpiredReleaseSessions() { CloseSession(*iter); } } + +bool CdmEngine::OkpCheck() { + std::unique_lock lock(okp_mutex_); + if (okp_initialized_) { + return static_cast(okp_provisioner_); + } + okp_initialized_ = true; + // Creating a CryptoSession will initialize OEMCrypto and flag the need + // for OKP. + std::unique_ptr crypto_session( + CryptoSession::MakeCryptoSession(metrics_->GetCryptoMetrics())); + if (!crypto_session->needs_keybox_provisioning()) { + // System does not require OKP provisioning. + return false; + } + okp_provisioner_ = OtaKeyboxProvisioner::Create(metrics_->GetCryptoMetrics()); + if (!okp_provisioner_) { + LOGE("Failed to create engine OKP handler, falling back to L3"); + okp_fallback_ = true; + return false; + } + if (okp_provisioner_->IsProvisioned()) { + // This should have been caught by call to needs_keybox_provisioning(), + // but possible with simultaneous apps. + okp_provisioner_.reset(); + return false; + } + if (okp_provisioner_->IsInFallbackMode()) { + LOGD("Engine is in OKP fallback mode"); + okp_fallback_ = true; + okp_provisioner_.reset(); + return false; + } + return true; +} + +bool CdmEngine::OkpIsInFallbackMode() { + const bool check = OkpCheck(); + std::unique_lock lock(okp_mutex_); + if (!check || !okp_provisioner_ || okp_fallback_) { + return okp_fallback_; + } + if (!okp_provisioner_->IsInFallbackMode()) { + return false; + } + // Trigger fallback. + LOGD("Engine is entering OKP fallback mode"); + okp_provisioner_.reset(); + okp_fallback_ = true; + return true; +} + +void CdmEngine::OkpTriggerFallback() { + std::unique_lock lock(okp_mutex_); + if (!okp_initialized_) { + LOGD("Call to OKP fallback before OKP setup"); + return; + } + if (okp_fallback_) return; + LOGD("Engine is entering OKP fallback mode"); + okp_provisioner_.reset(); + okp_fallback_ = true; +} + +void CdmEngine::OkpCleanUp() { + std::unique_lock lock(okp_mutex_); + if (!okp_initialized_) { + LOGD("Call to OKP fallback before OKP setup"); + return; + } + okp_provisioner_.reset(); +} } // namespace wvcdm diff --git a/libwvdrmengine/cdm/core/src/cdm_session.cpp b/libwvdrmengine/cdm/core/src/cdm_session.cpp index 315b1164..e9a07aef 100644 --- a/libwvdrmengine/cdm/core/src/cdm_session.cpp +++ b/libwvdrmengine/cdm/core/src/cdm_session.cpp @@ -111,19 +111,21 @@ CdmSession::~CdmSession() { CdmResponseType CdmSession::Init( CdmClientPropertySet* cdm_client_property_set) { - return Init(cdm_client_property_set, nullptr, nullptr); + return Init(cdm_client_property_set, nullptr, nullptr, false); } CdmResponseType CdmSession::Init(CdmClientPropertySet* cdm_client_property_set, const CdmSessionId* forced_session_id, - WvCdmEventListener* event_listener) { + WvCdmEventListener* event_listener, + bool forced_level3) { if (initialized_) { LOGE("Failed due to previous initialization"); return REINIT_ERROR; } - if (cdm_client_property_set && cdm_client_property_set->security_level() == - QUERY_VALUE_SECURITY_LEVEL_L3) { + if ((cdm_client_property_set && cdm_client_property_set->security_level() == + QUERY_VALUE_SECURITY_LEVEL_L3) || + forced_level3) { requested_security_level_ = kLevel3; security_level_ = kSecurityLevelL3; } diff --git a/libwvdrmengine/cdm/core/src/certificate_provisioning.cpp b/libwvdrmengine/cdm/core/src/certificate_provisioning.cpp index f7fcf296..424c105a 100644 --- a/libwvdrmengine/cdm/core/src/certificate_provisioning.cpp +++ b/libwvdrmengine/cdm/core/src/certificate_provisioning.cpp @@ -51,59 +51,6 @@ const std::string kCpProductionServiceCertificate = wvcdm::a2bs_hex( "26e0c050f3fd3ebe68cef9903ef6405b25fc6e31f93559fcff05657662b3653a" "8598ed5751b38694419242a875d9e00d5a5832933024b934859ec8be78adccbb" "1ec7127ae9afeef9c5cd2e15bd3048e8ce652f7d8c5d595a0323238c598a28"); - -/* - * Provisioning response is a base64-encoded protobuf, optionally within a - * JSON wrapper. If the JSON wrapper is present, extract the embedded response - * message. Then perform the base64 decode and return the result. - * - * If an error occurs during the parse or the decode, return an empty string. - */ -bool ExtractAndDecodeSignedMessage(const std::string& provisioning_response, - std::string* result) { - const std::string json_start_substr("\"signedResponse\": \""); - const std::string json_end_substr("\""); - std::string message_string; - - if (result == nullptr) { - LOGE("Output parameter |result| is not provided"); - return false; - } - - size_t start = provisioning_response.find(json_start_substr); - - if (start == provisioning_response.npos) { - // Message is not properly wrapped - reject it. - LOGE("Cannot locate start substring"); - result->clear(); - return false; - } - - // Appears to be JSON-wrapped protobuf - find end of protobuf portion. - const size_t end = provisioning_response.find( - json_end_substr, start + json_start_substr.length()); - if (end == provisioning_response.npos) { - LOGE("Cannot locate end substring"); - result->clear(); - return false; - } - - size_t b64_string_size = end - start - json_start_substr.length(); - message_string.assign(provisioning_response, - start + json_start_substr.length(), b64_string_size); - - if (message_string.empty()) { - LOGE("CDM provisioning response is empty"); - result->clear(); - return false; - } - - // Decode the base64-encoded message. - const std::vector decoded_message = - wvcdm::Base64SafeDecode(message_string); - result->assign(decoded_message.begin(), decoded_message.end()); - return true; -} } // namespace // Protobuf generated classes. using video_widevine::ClientIdentification_ClientCapabilities; @@ -497,10 +444,56 @@ CdmResponseType CertificateProvisioning::HandleProvisioningResponse( return NO_ERROR; } -// Static -bool CertificateProvisioning::ExtractAndDecodeSignedMessageForTesting( +// Provisioning response is a base64-encoded protobuf, optionally within a +// JSON wrapper. If the JSON wrapper is present, extract the embedded response +// message. Then perform the base64 decode and return the result. +// +// If an error occurs during the parse or the decode, return an empty string. +// static +bool CertificateProvisioning::ExtractAndDecodeSignedMessage( const std::string& provisioning_response, std::string* result) { - return ExtractAndDecodeSignedMessage(provisioning_response, result); + const std::string json_start_substr("\"signedResponse\": \""); + const std::string json_end_substr("\""); + std::string message_string; + + if (result == nullptr) { + LOGE("Output parameter |result| is not provided"); + return false; + } + + size_t start = provisioning_response.find(json_start_substr); + + if (start == provisioning_response.npos) { + // Message is not properly wrapped - reject it. + LOGE("Cannot locate start substring"); + result->clear(); + return false; + } + + // Appears to be JSON-wrapped protobuf - find end of protobuf portion. + const size_t end = provisioning_response.find( + json_end_substr, start + json_start_substr.length()); + if (end == provisioning_response.npos) { + LOGE("Cannot locate end substring"); + result->clear(); + return false; + } + + size_t b64_string_size = end - start - json_start_substr.length(); + message_string.assign(provisioning_response, + start + json_start_substr.length(), b64_string_size); + + if (message_string.empty()) { + LOGE("CDM provisioning response is empty"); + result->clear(); + return false; + } + + // Decode the base64-encoded message. + const std::vector decoded_message = + wvcdm::Base64SafeDecode(message_string); + result->assign(decoded_message.begin(), decoded_message.end()); + return true; } bool CertificateProvisioning::ExtractDeviceInfo( diff --git a/libwvdrmengine/cdm/core/src/crypto_session.cpp b/libwvdrmengine/cdm/core/src/crypto_session.cpp index 7249f9dd..c6b1b16c 100644 --- a/libwvdrmengine/cdm/core/src/crypto_session.cpp +++ b/libwvdrmengine/cdm/core/src/crypto_session.cpp @@ -20,7 +20,7 @@ #include "crypto_key.h" #include "entitlement_key_session.h" #include "log.h" -#include "ota_keybox_provisioner.h" +#include "okp_fallback_policy.h" #include "platform.h" #include "privacy_crypto.h" #include "properties.h" @@ -179,7 +179,8 @@ std::unique_ptr CryptoSession::usage_table_header_l1_; std::unique_ptr CryptoSession::usage_table_header_l3_; std::recursive_mutex CryptoSession::usage_table_mutex_; std::atomic CryptoSession::request_id_index_source_(0); -std::unique_ptr CryptoSession::ota_keybox_provisioner_l1_; +std::unique_ptr + CryptoSession::okp_fallback_policy_l1_; size_t GetOffset(std::string message, std::string field) { size_t pos = message.find(field); @@ -362,13 +363,15 @@ void CryptoSession::Init() { LOGD("OEMCrypto version (L3 security level): %s.%s", api_version.c_str(), api_minor_version.c_str()); if (needs_keybox_provisioning_) { - WithStaticFieldWriteLock("OtaKeyboxProvisioner", [&] { - if (!ota_keybox_provisioner_l1_) { + WithStaticFieldWriteLock("SystemFallbackPolicy", [&] { + if (!okp_fallback_policy_l1_) { LOGD("OEMCrypto needs keybox provisioning"); // Only create once. Possible that OEMCrypto is initialized // and terminated many times over the life cycle of the OTA // keybox provisioning process. - ota_keybox_provisioner_l1_ = OtaKeyboxProvisioner::Create(); + okp_fallback_policy_l1_ = okp::SystemFallbackPolicy::Create(); + if (okp_fallback_policy_l1_) + okp_fallback_policy_l1_->MarkNeedsProvisioning(); } }); } @@ -3023,16 +3026,16 @@ OEMCryptoResult CryptoSession::LegacyDecryptInChunks( return sts; } -OtaKeyboxProvisioner* CryptoSession::GetOtaKeyboxProvisioner() { - const auto getter = [&]() -> OtaKeyboxProvisioner* { +okp::SystemFallbackPolicy* CryptoSession::GetOkpFallbackPolicy() { + const auto getter = [&]() -> okp::SystemFallbackPolicy* { // If not set, then OTA keybox provisioning is not supported or // not needed. - if (!ota_keybox_provisioner_l1_) return nullptr; + if (!okp_fallback_policy_l1_) return nullptr; // May have already been initialized. - if (ota_keybox_provisioner_l1_->IsProvisioned()) return nullptr; - return ota_keybox_provisioner_l1_.get(); + if (okp_fallback_policy_l1_->IsProvisioned()) return nullptr; + return okp_fallback_policy_l1_.get(); }; - return WithStaticFieldReadLock("GetOtaKeyboxProvisioner", getter); + return WithStaticFieldReadLock("GetOkpFallbackPolicy", getter); } CdmResponseType CryptoSession::PrepareOtaProvisioningRequest( diff --git a/libwvdrmengine/cdm/core/src/device_files.cpp b/libwvdrmengine/cdm/core/src/device_files.cpp index 3871af80..979d287d 100644 --- a/libwvdrmengine/cdm/core/src/device_files.cpp +++ b/libwvdrmengine/cdm/core/src/device_files.cpp @@ -95,6 +95,7 @@ const char kTrue[] = "true"; const char kUsageInfoFileNameExt[] = ".bin"; const char kUsageInfoFileNamePrefix[] = "usage"; const char kUsageTableFileName[] = "usgtable.bin"; +const char kOkpInfoFileName[] = "okp.bin"; const char kWildcard[] = "*"; // TODO(b/192430982): Renable expiration of legacy DRM certificates // constexpr int64_t kFourMonthsInSeconds = (2 * 30 + 2 * 31) * 24 * 60 * 60; @@ -1627,6 +1628,164 @@ bool DeviceFiles::HasCertificate(CertificateType certificate_type) { return FileExists(certificate_file_name); } +bool DeviceFiles::StoreOkpInfo(const okp::SystemFallbackInfo& info) { + using StoredOkpInfo = video_widevine_client::sdk::OtaKeyboxProvisioningInfo; + using okp::SystemState; + RETURN_FALSE_IF_UNINITIALIZED(); + if (security_level_ != kSecurityLevelL1) { + LOGE("OKP info is only supported by L1: level = %d", + static_cast(security_level_)); + return false; + } + video_widevine_client::sdk::File file; + file.set_type(video_widevine_client::sdk::File::OKP_INFO); + file.set_version(video_widevine_client::sdk::File::VERSION_1); + StoredOkpInfo* stored_info = file.mutable_okp_info(); + switch (info.state()) { + case SystemState::kNeedsProvisioning: + stored_info->set_state(StoredOkpInfo::OKP_NEEDS_PROVISIONING); + break; + case SystemState::kFallbackMode: + stored_info->set_state(StoredOkpInfo::OKP_FALLBACK_MODE); + break; + case SystemState::kProvisioned: + stored_info->set_state(StoredOkpInfo::OKP_PROVISIONED); + break; + case SystemState::kUnknown: + default: + LOGE("Unexpected OKP state: state = %d", static_cast(info.state())); + return false; + } + if (info.first_checked_time() <= 0) { + LOGE("OKP first checked time is missing"); + return false; + } + stored_info->set_first_checked_time(info.first_checked_time()); + + if (info.state() == SystemState::kProvisioned) { + if (!info.HasProvisioningTime()) { + LOGE("OKP set as provisioned, but missing provisioning time"); + return false; + } + stored_info->set_provisioning_time(info.provisioning_time()); + } else if (info.state() == SystemState::kFallbackMode) { + if (!info.HasBackoffStartTime() || !info.HasBackoffDuration()) { + LOGE("OKP fallback information is missing "); + return false; + } + stored_info->set_backoff_start_time(info.backoff_start_time()); + stored_info->set_backoff_duration(info.backoff_duration()); + } else { + if (info.HasBackoffDuration()) { + // Store backoff duration from before. + stored_info->set_backoff_duration(info.backoff_duration()); + } + } + + std::string serialized_file; + file.SerializeToString(&serialized_file); + return StoreFileWithHash(GetOkpInfoFileName(), serialized_file) == kNoError; +} + +bool DeviceFiles::RetrieveOkpInfo(okp::SystemFallbackInfo* info) { + using StoredOkpInfo = video_widevine_client::sdk::OtaKeyboxProvisioningInfo; + using okp::SystemState; + RETURN_FALSE_IF_UNINITIALIZED(); + RETURN_FALSE_IF_NULL(info); + info->Clear(); + if (security_level_ != kSecurityLevelL1) { + LOGE("OKP info is only supported by L1: level = %d", + static_cast(security_level_)); + return false; + } + // File meta-data validation. + video_widevine_client::sdk::File file; + if (RetrieveHashedFile(GetOkpInfoFileName(), &file) != kNoError) { + LOGE("Unable to retrieve OKP info file"); + return false; + } + if (file.type() != video_widevine_client::sdk::File::OKP_INFO) { + LOGE("Incorrect file type: type = %d, expected_type = %d", + static_cast(file.type()), + static_cast(video_widevine_client::sdk::File::OKP_INFO)); + return false; + } + if (file.version() != video_widevine_client::sdk::File::VERSION_1) { + LOGE("Incorrect file version: version = %d, expected_version = %d", + static_cast(file.version()), + static_cast(video_widevine_client::sdk::File::VERSION_1)); + return false; + } + if (!file.has_okp_info()) { + // OKP info is only stored if at least 1 field is non-empty. This + // must be an error. + LOGD("OKP info is not present in file"); + return false; + } + + const StoredOkpInfo& stored_info = file.okp_info(); + switch (stored_info.state()) { + case StoredOkpInfo::OKP_NEEDS_PROVISIONING: + info->SetState(SystemState::kNeedsProvisioning); + break; + case StoredOkpInfo::OKP_FALLBACK_MODE: + info->SetState(SystemState::kFallbackMode); + break; + case StoredOkpInfo::OKP_PROVISIONED: + info->SetState(SystemState::kProvisioned); + break; + case StoredOkpInfo::OKP_UNKNOWN: + default: + LOGE("Unexpected OKP state: stored_state = %d", + static_cast(stored_info.state())); + return false; + } + + if (stored_info.first_checked_time() <= 0) { + LOGE("OKP first check time not present"); + info->Clear(); + return false; + } + info->SetFirstCheckedTime(stored_info.first_checked_time()); + + if (info->state() == SystemState::kProvisioned) { + if (stored_info.provisioning_time() <= 0) { + LOGE("OKP set as provisioned, but missing provisioning time"); + info->Clear(); + return false; + } + info->SetProvisioningTime(stored_info.provisioning_time()); + return true; + } + + if (info->state() == SystemState::kFallbackMode) { + if (stored_info.backoff_start_time() <= 0 || + stored_info.backoff_duration() <= 0) { + LOGE("OKP backoff information is missing"); + info->Clear(); + return false; + } + info->SetBackoffStartTime(stored_info.backoff_start_time()); + info->SetBackoffDuration(stored_info.backoff_duration()); + return true; + } + // Provisioned. + if (stored_info.backoff_duration() > 0) { + info->SetBackoffDuration(stored_info.backoff_duration()); + } + return true; +} + +bool DeviceFiles::DeleteOkpInfo() { + RETURN_FALSE_IF_UNINITIALIZED(); + if (security_level_ != kSecurityLevelL1) { + LOGE("OKP info is only supported by L1: level = %d", + static_cast(security_level_)); + return false; + } + return RemoveFile(GetOkpInfoFileName()); +} + DeviceFiles::ResponseType DeviceFiles::StoreFileWithHash( const std::string& name, const std::string& serialized_file) { std::string hash = Sha256Hash(serialized_file); @@ -1845,6 +2004,8 @@ std::string DeviceFiles::GetUsageInfoFileName(const std::string& app_id) { return kUsageInfoFileNamePrefix + hash + kUsageInfoFileNameExt; } +std::string DeviceFiles::GetOkpInfoFileName() { return kOkpInfoFileName; } + std::string DeviceFiles::GetFileNameSafeHash(const std::string& input) { return Base64SafeEncode(Md5Hash(input)); } diff --git a/libwvdrmengine/cdm/core/src/device_files.proto b/libwvdrmengine/cdm/core/src/device_files.proto index 98f15701..76266813 100644 --- a/libwvdrmengine/cdm/core/src/device_files.proto +++ b/libwvdrmengine/cdm/core/src/device_files.proto @@ -130,42 +130,6 @@ message UsageTableInfo { // OKP should create this file. Otherwise, this information is not // needed. message OtaKeyboxProvisioningInfo { - // Engine-specific information about OKP. - message OkpEngineInfo { - // Engine identifier. - optional bytes app_id = 1; - optional bytes origin = 2; - reserved 3 to 5; // Reserved for future engine composite keys. - // Counters for engine-specific OKP events. - // These counters are reset after a certain amount of time - // (OKP period) since the last event. - // Number of calls to openSession() where it is recommended - // to the app to try keybox provisioning. - optional uint32 try_okp_counter = 6; - // Number of calls to getProvisionRequest(). - optional uint32 generate_request_counter = 7; - // Number of failed calls to provideProvisionRequest(). - optional uint32 failed_response_counter = 8; - - // The value of |last_event_time| and |backoff_start_time| are set - // using the system's wall-clock in epoch seconds. A value of - // zero indicates it's not set. - - // Time of the last engine OKP event (change of the above counters; - // the beginning of the current OKP period). - // Zero indicates no event has yet occurred. - optional int64 last_event_time = 9; - // Beginning of an app/origin backoff period. - // Zero indicates that engine is not in a backoff state. - optional int64 backoff_start_time = 10; - // Intended length of “backoff period”. This will be assigned a - // random duration initially, then double each time an engine - // enters a backoff state. This is base on Google's recommended - // exponential backoff rules. - // Value of 0 indicates that backoff has not yet occurred. - optional int64 backoff_duration = 11; - } - enum OkpDeviceState { // Not yet checked for provisioning state. This should be a // transitory state only. Device which do not need OTA Keybox @@ -175,19 +139,30 @@ message OtaKeyboxProvisioningInfo { // that the device supports OKP. Device may or may not be in the // process of performing provisioning. OKP_NEEDS_PROVISIONING = 1; + // Device still needs provisioning, but has reached a condition + // where it should backoff from attempting OKP for a period of + // time. + OKP_FALLBACK_MODE = 2; // The device has successfully provisioned its keybox. - OKP_PROVISIONED = 2; + OKP_PROVISIONED = 3; } // Device-wide OKP state. optional OkpDeviceState state = 1; // Time when the CDM service first discovers that it needs to // provision the L1 keybox. optional int64 first_checked_time = 2; + // Beginning of a backoff period. + // Zero indicates that engine is not in a backoff state. + optional int64 backoff_start_time = 3; + // Intended length of “backoff period”. This will be assigned a + // random duration initially, then double each time an engine enters + // a backoff state. This is based on Google's recommended exponential + // backoff rules. + // Value of 0 indicates that backoff has not yet occurred. + optional int64 backoff_duration = 4; // System time of when a successful provisioning request has been // received. Only relevant if |state| is OKP_PROVISIONED. - optional int64 provisioning_time = 3; - // A list of all records for each identifiable engine. - repeated OkpEngineInfo engine_infos = 4; + optional int64 provisioning_time = 5; } message File { diff --git a/libwvdrmengine/cdm/core/src/okp_fallback_policy.cpp b/libwvdrmengine/cdm/core/src/okp_fallback_policy.cpp new file mode 100644 index 00000000..4e6505e6 --- /dev/null +++ b/libwvdrmengine/cdm/core/src/okp_fallback_policy.cpp @@ -0,0 +1,208 @@ +// Copyright 2021 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +#include "okp_fallback_policy.h" + +#include + +#include "cdm_random.h" +#include "device_files.h" +#include "file_store.h" +#include "log.h" +#include "wv_cdm_types.h" + +namespace wvcdm { +namespace okp { +using UniqueLock = std::unique_lock; +namespace { +constexpr int64_t kErrorTime = -1; + +int64_t GenerateInitialBackoffDuration() { + return static_cast(CdmRandom::RandomInRange( + kMinInitialBackoffDuration, kMaxInitialBackoffDuration)); +} +} // namespace + +// static +std::unique_ptr SystemFallbackPolicy::Create() { + std::unique_ptr fs(new FileSystem()); + std::unique_ptr device_files(new DeviceFiles(fs.get())); + if (!device_files->Init(kSecurityLevelL1)) { + LOGE("Failed to initialize device files"); + return nullptr; + } + std::unique_ptr policy(new SystemFallbackPolicy()); + policy->fs_ = std::move(fs); + policy->device_files_ = std::move(device_files); + policy->TryRestore(); + return policy; +} + +// static +std::unique_ptr SystemFallbackPolicy::CreateForTesting( + Clock* clock) { + std::unique_ptr policy(new SystemFallbackPolicy()); + if (clock != nullptr) { + policy->SetClockForTesting(clock); + } + // Device files are not supported for test instances. + return policy; +} + +// static +std::unique_ptr SystemFallbackPolicy::CreateForTesting( + const SystemFallbackInfo& info, Clock* clock) { + std::unique_ptr policy(new SystemFallbackPolicy()); + if (clock != nullptr) { + policy->SetClockForTesting(clock); + } + policy->info_ = info; + // Device files are not supported for test instances. + return policy; +} + +SystemFallbackPolicy::SystemFallbackPolicy() : clock_(), clock_ref_(&clock_) {} + +SystemFallbackPolicy::~SystemFallbackPolicy() { StoreInfo(); } + +void SystemFallbackPolicy::TryRestore() { + if (!device_files_->RetrieveOkpInfo(&info_)) { + info_.Clear(); + return; + } + LOGI("Restored OKP info: state = %s", + okp::SystemStateToString(info_.state())); + // Calling will end fallback mode if the backoff period has elapsed. + IsInFallbackMode(); +} + +void SystemFallbackPolicy::StoreInfo() { + if (IsTestMode()) { + // Testing instances may not set |device_files_|. + LOGV("Test instance, not storing"); + return; + } + device_files_->StoreOkpInfo(info_); +} + +// Can enter kNeedsProvisioning state from any other state other than +// kFallbackMode. +void SystemFallbackPolicy::MarkNeedsProvisioning() { + UniqueLock lock(mutex_); + switch (state()) { + case SystemState::kNeedsProvisioning: + case SystemState::kFallbackMode: + return; // Nothing to do. + case SystemState::kProvisioned: + case SystemState::kUnknown: + break; + default: + // Should not happen, but if it does continue anyways. + LOGW("Unknown state: %d", static_cast(state())); + } + info_.Clear(); + info_.SetFirstCheckedTime(GetCurrentTime()); + info_.SetState(SystemState::kNeedsProvisioning); + StoreInfo(); +} + +// Can only enter kFallbackMode if in state kNeedsProvisioning +void SystemFallbackPolicy::TriggerFallback() { + UniqueLock lock(mutex_); + switch (state()) { + case SystemState::kNeedsProvisioning: + case SystemState::kUnknown: // Should not happen. + break; // OK to fallback + case SystemState::kFallbackMode: + // Already in fallback mode. Expected if there are multiple + // engines are attempting OKP and fail for the same reason. + return; + case SystemState::kProvisioned: { + LOGW("Cannot fallback, already provisioned"); + return; + } + default: + LOGE("Unexpected state: %d", static_cast(state())); + return; + } + info_.SetState(SystemState::kFallbackMode); + const int64_t current_time = GetCurrentTime(); + if (!info_.HasFirstCheckedTime()) { + info_.SetFirstCheckedTime(current_time); + } + info_.SetBackoffStartTime(GetCurrentTime()); + if (info_.HasBackoffDuration()) { + // Doubling backoff duration for exponential backoff. + info_.DoubleBackoffDuration(); + } else { + // Use a random backoff period to avoid server spam across all devices. + info_.SetBackoffDuration(GenerateInitialBackoffDuration()); + } + StoreInfo(); +} + +void SystemFallbackPolicy::MarkProvisioned() { + UniqueLock lock(mutex_); + if (state() == SystemState::kProvisioned) return; + info_.SetState(SystemState::kProvisioned); + const int64_t current_time = GetCurrentTime(); + if (!info_.HasFirstCheckedTime()) { + info_.SetFirstCheckedTime(current_time); + } + info_.SetProvisioningTime(current_time); + info_.ClearBackoffStartTime(); + StoreInfo(); +} + +bool SystemFallbackPolicy::IsProvisioned() { + UniqueLock lock(mutex_); + return state() == SystemState::kProvisioned; +} + +bool SystemFallbackPolicy::IsInFallbackMode() { + UniqueLock lock(mutex_); + if (state() != SystemState::kFallbackMode) return false; + // Check if fallback period has ended. + const int64_t backoff_duration = info_.backoff_duration(); + const int64_t current_backoff_length = GetSecondsSinceBackoffStart(); + if (backoff_duration == 0 || current_backoff_length == kErrorTime) { + // Possible error condition if device files and/or system clock is + // modified. + LOGE( + "Unexpected backoff state, ending backoff: backoff_duration = " + "%" PRId64 ", current_backoff_length = %" PRId64, + backoff_duration, current_backoff_length); + EndBackoffPeriod(); + } else if (current_backoff_length < backoff_duration) { + // Still in backoff period, resume fallback mode. + return true; + } else { + // Backoff period has ended, may attempt OKP again. + LOGD("Backoff period has ended"); + EndBackoffPeriod(); + } + StoreInfo(); + return false; // Only stored if previously in fallback and has ended. +} + +int64_t SystemFallbackPolicy::GetSecondsSinceBackoffStart() const { + if (!info_.HasBackoffStartTime()) return 0; + const int64_t backoff_start_time = info_.backoff_start_time(); + const int64_t current_time = GetCurrentTime(); + if (current_time < backoff_start_time) { + LOGE("Current time is less than start of backoff"); + return kErrorTime; + } + return current_time - backoff_start_time; +} + +void SystemFallbackPolicy::EndBackoffPeriod() { + info_.SetState(SystemState::kNeedsProvisioning); + info_.ClearBackoffStartTime(); +} + +bool SystemFallbackPolicy::IsTestMode() const { + return !static_cast(device_files_); +} +} // namespace okp +} // namespace wvcdm diff --git a/libwvdrmengine/cdm/core/src/okp_info.cpp b/libwvdrmengine/cdm/core/src/okp_info.cpp new file mode 100644 index 00000000..30edfb4e --- /dev/null +++ b/libwvdrmengine/cdm/core/src/okp_info.cpp @@ -0,0 +1,32 @@ +// Copyright 2021 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +#include "okp_info.h" + +namespace wvcdm { +namespace okp { +const char* SystemStateToString(SystemState state) { + switch (state) { + case SystemState::kNeedsProvisioning: + return "NeedsProvisioning"; + case SystemState::kFallbackMode: + return "FallbackMode"; + case SystemState::kProvisioned: + return "Provisioned"; + case SystemState::kUnknown: + default: + return "Unknown"; + } +} + +bool SystemFallbackInfo::operator==(const SystemFallbackInfo& other) const { + if (this == &other) return true; + if (state_ != other.state_) return false; + if (first_checked_time_ != other.first_checked_time_) return false; + if (backoff_start_time_ != other.backoff_start_time_) return false; + if (backoff_duration_ != other.backoff_duration_) return false; + if (provisioning_time_ != other.provisioning_time_) return false; + return true; +} +} // namespace okp +} // namespace wvcdm diff --git a/libwvdrmengine/cdm/core/src/ota_keybox_provisioner.cpp b/libwvdrmengine/cdm/core/src/ota_keybox_provisioner.cpp index e88109a8..180ab424 100644 --- a/libwvdrmengine/cdm/core/src/ota_keybox_provisioner.cpp +++ b/libwvdrmengine/cdm/core/src/ota_keybox_provisioner.cpp @@ -3,81 +3,272 @@ // Agreement. #include "ota_keybox_provisioner.h" +#include + #include +#include "certificate_provisioning.h" #include "crypto_session.h" +#include "license_protocol.pb.h" #include "log.h" +#include "okp_fallback_policy.h" +#include "properties.h" #include "string_conversions.h" namespace wvcdm { -using UniqueLock = std::unique_lock; +using video_widevine::ProvisioningRequest; +using video_widevine::ProvisioningResponse; +using video_widevine::SignedProvisioningMessage; +using OtaRequest = ProvisioningRequest::AndroidAttestationOtaKeyboxRequest; +using OtaResponse = ProvisioningResponse::AndroidAttestationOtaKeyboxResponse; namespace { // Indicates not to use the test keybox for OTA provisioning. constexpr bool kProductionKeybox = false; +const CdmAppParameterMap kEmptyAppParameters; +const std::string kEmptyString; } // namespace // static -std::unique_ptr OtaKeyboxProvisioner::Create() { - return std::unique_ptr(new OtaKeyboxProvisioner()); +std::unique_ptr OtaKeyboxProvisioner::Create( + metrics::CryptoMetrics* crypto_metrics) { + if (crypto_metrics == nullptr) { + LOGE("Input |crypto_metrics| is null"); + return nullptr; + } + // Get system fallback policy. + okp::SystemFallbackPolicy* fallback_policy = + CryptoSession::GetOkpFallbackPolicy(); + if (fallback_policy == nullptr) { + LOGE("No system fallback policy"); + return nullptr; + } + // Setup crypto session. + std::unique_ptr crypto_session( + CryptoSession::MakeCryptoSession(crypto_metrics)); + crypto_session->Open(kLevelDefault); + if (!crypto_session->IsOpen()) { + LOGE("Failed to open crypto session for OKP provisioner"); + return nullptr; + } + const CdmSecurityLevel security_level = crypto_session->GetSecurityLevel(); + if (security_level != kSecurityLevelL1) { + LOGE("Failed to open L1 crypto session: security_level = %d", + static_cast(security_level)); + crypto_session.reset(); + return nullptr; + } + std::unique_ptr engine_provisioner( + new OtaKeyboxProvisioner(std::move(crypto_session), fallback_policy)); + if (!engine_provisioner->Init()) { + LOGE("Failed to initialize OKP provisioner"); + return nullptr; + } + return engine_provisioner; } -OtaKeyboxProvisioner::OtaKeyboxProvisioner() : mutex_() {} - -OtaKeyboxProvisioner::~OtaKeyboxProvisioner() {} - -CdmResponseType OtaKeyboxProvisioner::GenerateProvisioningRequest( - CryptoSession* crypto_session, std::string* request) { - if (crypto_session == nullptr) { - LOGE("Input |crypto_session| is null"); - return PARAMETER_NULL; +// static +std::unique_ptr OtaKeyboxProvisioner::CreateForTesting( + std::unique_ptr&& crypto_session, + okp::SystemFallbackPolicy* fallback_policy) { + if (!crypto_session) { + LOGE("Input |crypto_metrics| is null"); + return nullptr; } + if (fallback_policy == nullptr) { + LOGE("Input |fallback_policy| is null"); + return nullptr; + } + std::unique_ptr engine_provisioner( + new OtaKeyboxProvisioner(std::move(crypto_session), fallback_policy)); + if (!engine_provisioner->Init()) { + LOGE("Failed to initialize OKP provisioner"); + return nullptr; + } + return engine_provisioner; +} + +OtaKeyboxProvisioner::OtaKeyboxProvisioner( + std::unique_ptr&& crypto_session, + okp::SystemFallbackPolicy* fallback_policy) + : crypto_session_(std::move(crypto_session)), + fallback_policy_(fallback_policy) { + assert(static_cast(crypto_session_)); + assert(fallback_policy != nullptr); +} + +OtaKeyboxProvisioner::~OtaKeyboxProvisioner() { + crypto_session_.reset(); + fallback_policy_ = nullptr; +} + +bool OtaKeyboxProvisioner::Init() { + const CdmResponseType result = + client_id_.InitForOtaKeyboxProvisioning(crypto_session_.get()); + if (result != NO_ERROR) { + LOGE("Failed to initialize OKP client ID"); + return false; + } + return true; +} + +bool OtaKeyboxProvisioner::IsProvisioned() const { + return fallback_policy_->IsProvisioned(); +} + +bool OtaKeyboxProvisioner::IsInFallbackMode() const { + return fallback_policy_->IsInFallbackMode(); +} + +CdmResponseType OtaKeyboxProvisioner::GetProvisioningRequest( + std::string* request, std::string* default_url) { if (request == nullptr) { LOGE("Output |request| is null"); return PARAMETER_NULL; } - UniqueLock lock(mutex_); - // Do not generate new requests if already provisioned. + if (default_url == nullptr) { + LOGE("Output |default_url| is null"); + return PARAMETER_NULL; + } if (IsProvisioned()) { LOGW("Already provisioned"); - // TODO(sigquit): Use a response code that will indicate to the - // caller that the system is already provisioned. + CleanUp(); + return OKP_ALREADY_PROVISIONED; + } + if (!crypto_session_) { + LOGE("Crypto session has been released, OKP unavailable"); + // Caller should not reuse provisioner after failure. return UNKNOWN_ERROR; } - const CdmResponseType result = - crypto_session->PrepareOtaProvisioningRequest(kProductionKeybox, request); - if (result != NO_ERROR) { + // Step 1: Generate raw request from OEMCrypto. + std::string ota_request_data; + CdmResponseType result = crypto_session_->PrepareOtaProvisioningRequest( + kProductionKeybox, &ota_request_data); + if (result == NOT_IMPLEMENTED_ERROR) { + LOGW("OKP is not supported by OEMCrypto"); + fallback_policy_->TriggerFallback(); + CleanUp(); + return result; + } else if (result != NO_ERROR) { + LOGE("Failed to generate OKP request: status = %d", + static_cast(result)); return result; } - LOGV("OTA request generated: request = %s", b2a_hex(*request).c_str()); - request_count_++; + // Step 2: Wrap in ProvisioningRequest. + ProvisioningRequest prov_request; + auto* client_id = prov_request.mutable_client_id(); + result = client_id_.Prepare(kEmptyAppParameters, kEmptyString, client_id); + if (result != NO_ERROR) { + LOGW("Failed to prepare client ID, continuing without: result = %d", + static_cast(result)); + client_id->Clear(); + } + OtaRequest* ota_request = prov_request.mutable_android_ota_keybox_request(); + ota_request->set_ota_request(ota_request_data); + + // Step 3: Wrap in SignedProvisioningMessage. + SignedProvisioningMessage signed_request; + std::string message; + prov_request.SerializeToString(&message); + signed_request.set_message(message); + signed_request.set_provisioning_type( + SignedProvisioningMessage::ANDROID_ATTESTATION_KEYBOX_OTA); + signed_request.SerializeToString(request); + + if (!wvcdm::Properties::provisioning_messages_are_binary()) { + *request = Base64SafeEncodeNoPad( + std::vector(request->begin(), request->end())); + } + + request_generated_ = true; + CertificateProvisioning::GetProvisioningServerUrl(default_url); return NO_ERROR; } CdmResponseType OtaKeyboxProvisioner::HandleProvisioningResponse( - CryptoSession* crypto_session, const std::string& response) { - if (crypto_session == nullptr) { - LOGE("Input |crypto_session| is null"); - return PARAMETER_NULL; - } + const std::string& response) { if (response.empty()) { - LOGE("OTA provisioning response is empty"); + LOGE("Signed provisioning message is empty"); return EMPTY_PROVISIONING_RESPONSE; } - UniqueLock lock(mutex_); if (IsProvisioned()) { - // Response already received, silently dropping. - response_count_++; + LOGD("Already provisioned"); + response_received_ = true; + CleanUp(); return NO_ERROR; } - const CdmResponseType result = - crypto_session->LoadOtaProvisioning(kProductionKeybox, response); - if (result != NO_ERROR) { - return result; + if (!request_generated_) { + LOGE("Received response without generating request"); + return UNKNOWN_ERROR; + } + if (!crypto_session_) { + LOGE("Crypto session has been released, OKP unavailable"); + // Caller should not reuse provisioner after failure. + return UNKNOWN_ERROR; + } + std::string decoded_response; + if (!wvcdm::Properties::provisioning_messages_are_binary()) { + if (!CertificateProvisioning::ExtractAndDecodeSignedMessage( + response, &decoded_response)) { + LOGE("Failed to extract OKP provisioning response"); + return PARSE_OKP_RESPONSE_ERROR; + } + } else { + decoded_response = response; + } + // Step 1: Unwrap from SignedProvisioningMessage; + SignedProvisioningMessage signed_response; + if (!signed_response.ParseFromString(decoded_response)) { + LOGE("Failed to parse SignedProvisioningMessage"); + return PARSE_OKP_RESPONSE_ERROR; + } + if (!signed_response.has_message()) { + LOGE("Signed response is missing message"); + return PARSE_OKP_RESPONSE_ERROR; + } + if (signed_response.provisioning_type() != + SignedProvisioningMessage::ANDROID_ATTESTATION_KEYBOX_OTA) { + LOGE("Unexpected protocol type/version: protocol_type = %d", + static_cast(signed_response.provisioning_type())); + return PARSE_OKP_RESPONSE_ERROR; + } + // Step 2: Unwrap from ProvisioningResponse. + ProvisioningResponse prov_response; + if (!prov_response.ParseFromString(signed_response.message())) { + LOGE("Failed to parse ProvisioningResponse"); + return PARSE_OKP_RESPONSE_ERROR; + } + if (!prov_response.has_android_ota_keybox_response()) { + LOGE("Missing OTA keybox response message"); + return PARSE_OKP_RESPONSE_ERROR; + } + const OtaResponse& ota_response = prov_response.android_ota_keybox_response(); + if (!ota_response.has_ota_response()) { + LOGE("Missing OTA keybox response data"); + return PARSE_OKP_RESPONSE_ERROR; + } + // Step 3: Load response. + const std::string ota_response_data = ota_response.ota_response(); + if (ota_response_data.empty()) { + LOGE("Raw OTA response is empty"); + return PARSE_OKP_RESPONSE_ERROR; + } + const CdmResponseType result = crypto_session_->LoadOtaProvisioning( + kProductionKeybox, ota_response_data); + if (result == NO_ERROR) { + LOGV("OTA response successfully processed: ota_response_data = %s", + b2a_hex(ota_response_data).c_str()); + fallback_policy_->MarkProvisioned(); + response_received_ = true; + } else { + fallback_policy_->TriggerFallback(); + } + CleanUp(); + return result; +} + +void OtaKeyboxProvisioner::CleanUp() { + if (crypto_session_) { + crypto_session_.reset(); } - LOGD("OTA response successfully processed: response = %s", - b2a_hex(response).c_str()); - is_provisioned_ = true; - response_count_ = 1; - return NO_ERROR; } } // namespace wvcdm diff --git a/libwvdrmengine/cdm/core/test/device_files_unittest.cpp b/libwvdrmengine/cdm/core/test/device_files_unittest.cpp index 2aab53f0..2f735aa7 100644 --- a/libwvdrmengine/cdm/core/test/device_files_unittest.cpp +++ b/libwvdrmengine/cdm/core/test/device_files_unittest.cpp @@ -15,6 +15,7 @@ #include "cdm_random.h" #include "crypto_wrapped_key.h" #include "file_store.h" +#include "okp_info.h" #include "properties.h" #include "string_conversions.h" #include "wv_cdm_constants.h" @@ -3785,6 +3786,7 @@ using ::testing::Expectation; using ::testing::Gt; using ::testing::HasSubstr; using ::testing::InSequence; +using ::testing::Invoke; using ::testing::InvokeWithoutArgs; using ::testing::NotNull; using ::testing::Return; @@ -4882,6 +4884,110 @@ TEST_F(DeviceFilesTest, ReserveLicenseIdsDoesNotUseFileSystem) { } } +// OKP info can only be stored on L1 device files. +TEST_F(DeviceFilesTest, OkpInfo_L1Only) { + MockFileSystem file_system; + DeviceFiles device_files(&file_system); + EXPECT_TRUE(device_files.Init(kSecurityLevelL3)); + + okp::SystemFallbackInfo info; + info.SetState(okp::SystemState::kNeedsProvisioning); + info.SetFirstCheckedTime(1234); + + const std::string kErrorMessage = "OKP should not be available on L3"; + EXPECT_FALSE(device_files.StoreOkpInfo(info)) << kErrorMessage; + EXPECT_FALSE(device_files.RetrieveOkpInfo(&info)) << kErrorMessage; + EXPECT_FALSE(device_files.DeleteOkpInfo()) << kErrorMessage; +} + +// Uninitialized info cannot be stored. +TEST_F(DeviceFilesTest, OkpInfo_UninitializedInfo) { + MockFileSystem file_system; + DeviceFiles device_files(&file_system); + EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); + okp::SystemFallbackInfo info; // Uninitialized. + EXPECT_FALSE(device_files.StoreOkpInfo(info)); +} + +TEST_F(DeviceFilesTest, OkpInfo_FileDoesNotExist) { + MockFileSystem file_system; + DeviceFiles device_files(&file_system); + EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); + + const std::string kOkpInfoPath = + device_base_path_ + DeviceFiles::GetOkpInfoFileName(); + EXPECT_CALL(file_system, Exists(kOkpInfoPath)).WillOnce(Return(false)); + + okp::SystemFallbackInfo info; + EXPECT_FALSE(device_files.RetrieveOkpInfo(&info)); +} + +TEST_F(DeviceFilesTest, OkpInfo_RetrieveWithNull) { + MockFileSystem file_system; + DeviceFiles device_files(&file_system); + EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); + EXPECT_FALSE(device_files.RetrieveOkpInfo(nullptr)); +} + +TEST_F(DeviceFilesTest, OkpInfo_DeleteFile) { + MockFileSystem file_system; + DeviceFiles device_files(&file_system); + + // L1 - Should succeed. + EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); + const std::string kOkpInfoPath = + device_base_path_ + DeviceFiles::GetOkpInfoFileName(); + EXPECT_CALL(file_system, Remove(kOkpInfoPath)).WillOnce(Return(true)); + EXPECT_TRUE(device_files.DeleteOkpInfo()); + + // L3 - Should fail. + EXPECT_TRUE(device_files.Init(kSecurityLevelL3)); + EXPECT_FALSE(device_files.DeleteOkpInfo()); +} + +TEST_F(DeviceFilesTest, OkpInfo_StoreAndRetrieve) { + MockFileSystem file_system; + DeviceFiles device_files(&file_system); + EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); + + // Prepare data. + okp::SystemFallbackInfo info; + info.SetState(okp::SystemState::kFallbackMode); + info.SetFirstCheckedTime(1234); + info.SetBackoffStartTime(2345); + info.SetBackoffDuration(1111); + + // Set store expectations. + const std::string kOkpInfoPath = + device_base_path_ + DeviceFiles::GetOkpInfoFileName(); + MockFile* file = new MockFile(); + EXPECT_CALL(file_system, DoOpen(kOkpInfoPath, _)).WillOnce(Return(file)); + std::string serialized; + EXPECT_CALL(*file, Write(NotNull(), _)) + .WillOnce(DoAll(Invoke([&](const char* buf, size_t len) { + serialized.assign(buf, len); + }), + ReturnArg<1>())); + + EXPECT_TRUE(device_files.StoreOkpInfo(info)); + ASSERT_FALSE(serialized.empty()) << "OKP info was not serialized"; + + // Set retrieve expectations. + file = new MockFile(); + EXPECT_CALL(file_system, Exists(kOkpInfoPath)).WillOnce(Return(true)); + EXPECT_CALL(file_system, FileSize(kOkpInfoPath)) + .WillOnce(Return(serialized.size())); + EXPECT_CALL(file_system, DoOpen(kOkpInfoPath, _)).WillOnce(Return(file)); + EXPECT_CALL(*file, Read(NotNull(), _)) + .WillOnce(DoAll(SetArrayArgument<0>(serialized.begin(), serialized.end()), + Return(serialized.size()))); + + okp::SystemFallbackInfo retrieved_info; + EXPECT_TRUE(device_files.RetrieveOkpInfo(&retrieved_info)); + + EXPECT_EQ(retrieved_info, info); +} + // From a usage info file containing 3 provider sessions, 2 will be // deleted using the |key_set_id| associated with them. // It is expected that once the provider sessions are deleted, the diff --git a/libwvdrmengine/cdm/core/test/okp_fallback_policy_test.cpp b/libwvdrmengine/cdm/core/test/okp_fallback_policy_test.cpp new file mode 100644 index 00000000..ea57a42d --- /dev/null +++ b/libwvdrmengine/cdm/core/test/okp_fallback_policy_test.cpp @@ -0,0 +1,372 @@ +// Copyright 2021 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +#include "okp_fallback_policy.h" + +#include + +#include + +#include "mock_clock.h" +#include "test_printers.h" + +namespace wvcdm { +namespace okp { +namespace { +// Thu 29 Jul 2021 10:43:21 PM UTC +constexpr int64_t kInitialTime = 1627598601; + +void SetSystemInfoAsProvisioned(SystemFallbackInfo* info, + int64_t provisioning_time) { + info->SetState(SystemState::kProvisioned); + if (!info->HasFirstCheckedTime()) { + info->SetFirstCheckedTime(kInitialTime); + } + info->ClearBackoffStartTime(); + info->SetProvisioningTime(provisioning_time); +} + +void SetSystemInfoAsFallback(SystemFallbackInfo* info, + int64_t backoff_start_time, + int64_t backoff_duration) { + info->SetState(SystemState::kFallbackMode); + if (!info->HasFirstCheckedTime()) { + info->SetFirstCheckedTime(kInitialTime); + } + info->SetBackoffStartTime(backoff_start_time); + info->SetBackoffDuration(backoff_duration); + info->ClearProvisioningTime(); +} + +class OkpFallbackPolicyTest : public ::testing::Test { + protected: + void SetUp() override { + clock_.SetTime(kInitialTime); + system_policy_ = SystemFallbackPolicy::CreateForTesting(&clock_); + ASSERT_TRUE(system_policy_); + system_policy_->MarkNeedsProvisioning(); + } + + void SetUpWithInfo(const SystemFallbackInfo& info) { + TearDown(); + system_policy_ = SystemFallbackPolicy::CreateForTesting(info, &clock_); + } + + void TearDown() override { system_policy_.reset(); } + + FrozenClock clock_; + std::unique_ptr system_policy_; +}; // class OkpFallbackPolicyTest +} // namespace + +// Test ensures that test fixture is initialized correctly. +TEST_F(OkpFallbackPolicyTest, Initialization) { + EXPECT_FALSE(system_policy_->IsProvisioned()); + EXPECT_FALSE(system_policy_->IsInFallbackMode()); + EXPECT_EQ(kInitialTime, system_policy_->info().first_checked_time()); + EXPECT_EQ(SystemState::kNeedsProvisioning, system_policy_->state()); +} + +// Setup: +// 1) Device needs OKP +// 2) Some CDM engine successfully provisions, and marks the fallback +// policy as provisioned. +// Expectation: +// Policy is marked as provisioned and timestamp is updated. +TEST_F(OkpFallbackPolicyTest, SuccessfulProvisioning) { + // App calls provideProvisionResponse() and succeeds. + constexpr int64_t kProvisioningTime = kInitialTime + 10; + clock_.SetTime(kProvisioningTime); + system_policy_->MarkProvisioned(); + + // Final checks. + EXPECT_TRUE(system_policy_->IsProvisioned()); + EXPECT_FALSE(system_policy_->IsInFallbackMode()); + EXPECT_EQ(kProvisioningTime, system_policy_->info().provisioning_time()); +} + +// Setup: +// 1) Device state is restored and is already provisioned +// Expectation: +// Fallback policy should still be marked as provisioned. +TEST_F(OkpFallbackPolicyTest, Restore_DeviceProvisioned) { + SystemFallbackInfo info; + SetSystemInfoAsProvisioned(&info, kInitialTime); + // Reinitialize. + SetUpWithInfo(info); + ASSERT_TRUE(system_policy_); + // Checks. + EXPECT_TRUE(system_policy_->IsProvisioned()); + EXPECT_FALSE(system_policy_->IsInFallbackMode()); +} + +// Setup: +// 1) Device needs OKP +// 2) App attempts OKP, but response fails and triggers fallback. +// Expectation: +// Policy should indicate fallback, and update info. +TEST_F(OkpFallbackPolicyTest, TriggerFallback) { + // App calls provideProvisionResponse() and fails. + constexpr int64_t kFallbackTime = kInitialTime + 10; + clock_.SetTime(kFallbackTime); + system_policy_->TriggerFallback(); + // Checks. + EXPECT_FALSE(system_policy_->IsProvisioned()); + EXPECT_TRUE(system_policy_->IsInFallbackMode()); + EXPECT_EQ(system_policy_->info().backoff_start_time(), kFallbackTime); + EXPECT_GE(system_policy_->info().backoff_duration(), + kMinInitialBackoffDuration); +} + +// Setup: +// 1) Device needs OKP +// 2) Two apps are performing provisioning simultaneously +// 3) The first app successfully provisions device +// 4) The second app succeeds (see notes) shortly after +// Expectation: +// The time of the first provisioning should be recorded, the +// second should not. +// Note: +// The CDM should silently drop responses which arrive after +// the first successful response. The fallback policy is agnostic +// to the exact mechanism to enforce this behavor. +TEST_F(OkpFallbackPolicyTest, TwoSuccessfulProvisionings) { + // App calls provideProvisionResponse() and succeeds. + constexpr int64_t kFirstProvisioningTime = kInitialTime + 10; + clock_.SetTime(kFirstProvisioningTime); + system_policy_->MarkProvisioned(); + + // App calls provideProvisionResponse() and fails. + constexpr int64_t kSecondProvisioningTime = kFirstProvisioningTime + 10; + clock_.SetTime(kSecondProvisioningTime); + system_policy_->TriggerFallback(); + // Checks. + EXPECT_TRUE(system_policy_->IsProvisioned()); + EXPECT_FALSE(system_policy_->IsInFallbackMode()); + EXPECT_EQ(kFirstProvisioningTime, system_policy_->info().provisioning_time()); +} + +// Setup: +// 1) Device needs OKP +// 2) Two apps are performing provisioning simultaneously +// 3) The first app successfully provisions device +// 4) The second app fails, and triggers fallback +// Expectation: +// Once provisioned, fallback policy should remain provisioned. +// Note: +// In this case, the second engine should check if provisioned +// before triggering its own L3 fallback. +TEST_F(OkpFallbackPolicyTest, TriggerFallbackAfterProvisioning) { + // App calls provideProvisionResponse() and succeeds. + constexpr int64_t kProvisioningTime = kInitialTime + 10; + clock_.SetTime(kProvisioningTime); + system_policy_->MarkProvisioned(); + + // App calls provideProvisionResponse() and fails. + constexpr int64_t kFallbackTime = kProvisioningTime + 10; + clock_.SetTime(kFallbackTime); + system_policy_->TriggerFallback(); + // Checks. + EXPECT_TRUE(system_policy_->IsProvisioned()); + EXPECT_FALSE(system_policy_->IsInFallbackMode()); + EXPECT_EQ(kProvisioningTime, system_policy_->info().provisioning_time()); + EXPECT_FALSE(system_policy_->info().HasBackoffStartTime()); + EXPECT_FALSE(system_policy_->info().HasBackoffDuration()); +} + +// Setup: +// 1) Device needs OKP +// 2) Two apps are performing provisioning simultaneously +// 3) The first app fails, and triggers fallback +// 4) The seoncd app successfully provisions device +// Expectation: +// Fallback policy should indicate that the device is provisioned; +// overriding the fallback. +// Note: +// Depending on the exact timing, the first app may or may not fallback +// to L3 for the remainder of the apps life cycle. If the app did +// fallback to L3, it will be able to resume use of L1 when it restarts. +TEST_F(OkpFallbackPolicyTest, ProvisionAfterFallback) { + // App calls provideProvisionResponse() and fails. + constexpr int64_t kFallbackTime = kInitialTime + 10; + clock_.SetTime(kFallbackTime); + system_policy_->TriggerFallback(); + + // App calls provideProvisionResponse() and succeeds. + constexpr int64_t kProvisioningTime = kFallbackTime + 10; + clock_.SetTime(kProvisioningTime); + system_policy_->MarkProvisioned(); + + // Checks. + EXPECT_TRUE(system_policy_->IsProvisioned()); + EXPECT_FALSE(system_policy_->IsInFallbackMode()); + EXPECT_EQ(kProvisioningTime, system_policy_->info().provisioning_time()); + EXPECT_FALSE(system_policy_->info().HasBackoffStartTime()); +} + +// Setup: +// 1) Device needs OKP +// 2) Fallback policy is restored, not in fallback mode +// 3) Provisioning is attempted +// Expectation: +// Policy should resume, allowing provisioning. +TEST_F(OkpFallbackPolicyTest, Restore_NeedsProvisioning) { + SystemFallbackInfo info; + info.SetState(SystemState::kNeedsProvisioning); + info.SetFirstCheckedTime(kInitialTime); + constexpr int64_t kRestoreTime = kInitialTime + 10; + clock_.SetTime(kRestoreTime); + // Restore + SetUpWithInfo(info); + ASSERT_TRUE(system_policy_); + // Checks + EXPECT_FALSE(system_policy_->IsProvisioned()); + EXPECT_FALSE(system_policy_->IsInFallbackMode()); + EXPECT_EQ(system_policy_->info().first_checked_time(), kInitialTime); + // Provision + constexpr int64_t kProvisioningTime = kRestoreTime + 10; + clock_.SetTime(kProvisioningTime); + system_policy_->MarkProvisioned(); + // Checks + EXPECT_TRUE(system_policy_->IsProvisioned()); + EXPECT_FALSE(system_policy_->IsInFallbackMode()); + EXPECT_EQ(kProvisioningTime, system_policy_->info().provisioning_time()); +} + +// Setup: +// 1) Device needs OKP +// 2) Fallback policy is restored, but had previously entered fallback mode +// 3) Device is still in "backoff period" +// Expectation: +// Fallback policy should continue to be in fallback mode after restore. +TEST_F(OkpFallbackPolicyTest, Restore_InBackoffPeriod) { + SystemFallbackInfo info; + constexpr int64_t kBackoffDuration = 100; + constexpr int64_t kFallbackTime = kInitialTime + 10; + SetSystemInfoAsFallback(&info, kFallbackTime, kBackoffDuration); + constexpr int64_t kRestoreTime = kFallbackTime + (kBackoffDuration / 2); + clock_.SetTime(kRestoreTime); // Within backoff period. + // Restore + SetUpWithInfo(info); + ASSERT_TRUE(system_policy_); + // Checks + EXPECT_FALSE(system_policy_->IsProvisioned()); + EXPECT_TRUE(system_policy_->IsInFallbackMode()); + EXPECT_EQ(system_policy_->info().backoff_start_time(), kFallbackTime); + EXPECT_EQ(system_policy_->info().backoff_duration(), kBackoffDuration); +} + +// Setup: +// 1) Device needs OKP +// 2) Fallback policy is restored, but had previously entered fallback mode +// 3) Backoff period is over +// Expectation: +// Fallback policy should no longer be in fallback mode. +TEST_F(OkpFallbackPolicyTest, Restore_AfterBackoffPeriod) { + SystemFallbackInfo info; + constexpr int64_t kBackoffDuration = 100; + constexpr int64_t kFallbackTime = kInitialTime + 10; + SetSystemInfoAsFallback(&info, kFallbackTime, kBackoffDuration); + constexpr int64_t kRestoreTime = kFallbackTime + (kBackoffDuration * 2); + clock_.SetTime(kRestoreTime); // After backoff period. + // Restore + SetUpWithInfo(info); + ASSERT_TRUE(system_policy_); + // Checks + EXPECT_FALSE(system_policy_->IsProvisioned()); + EXPECT_FALSE(system_policy_->IsInFallbackMode()); + // Backoff duration should still be recorded as it will be required + // for exponential backoff. + EXPECT_EQ(system_policy_->info().backoff_duration(), kBackoffDuration); +} + +// Setup: +// 1) Device needs OKP +// 2) Fallback policy is restored, but had previously entered fallback mode +// 3) Device system clock has been rewound to a time before backoff started +// Expectation: +// Restore should be successful, fallback may be canceled, provisioning +// should still be needed. +// Note: +// This is not an expected circumstance, but may occur if a user changes +// their devices system time. +TEST_F(OkpFallbackPolicyTest, Restore_InBackoffPeriod_SystemTimeRollback) { + SystemFallbackInfo info; + constexpr int64_t kBackoffDuration = 100; + constexpr int64_t kFallbackTime = kInitialTime + 10; + SetSystemInfoAsFallback(&info, kFallbackTime, kBackoffDuration); + constexpr int64_t kRestoreTime = kFallbackTime - 250; + clock_.SetTime(kRestoreTime); // Before backoff period. + // Restore + SetUpWithInfo(info); + ASSERT_TRUE(system_policy_); + // Checks + EXPECT_FALSE(system_policy_->IsProvisioned()); + EXPECT_FALSE(system_policy_->IsInFallbackMode()); +} + +// Setup: +// 1) Device needs OKP +// 2) Fallback policy is restored, but had previously entered fallback mode +// 3) Backoff period is over +// 4) OKP fails again, tiggering fallback again +// Expectation: +// Fallback policy should re-enter fallback mode, with a backoff period +// double that of before. +TEST_F(OkpFallbackPolicyTest, Restore_AfterBackoff_FallbackAgain) { + SystemFallbackInfo info; + constexpr int64_t kBackoffDuration = 100; + constexpr int64_t kFallbackTime = kInitialTime + 10; + SetSystemInfoAsFallback(&info, kFallbackTime, kBackoffDuration); + constexpr int64_t kRestoreTime = kFallbackTime + (kBackoffDuration * 2); + clock_.SetTime(kRestoreTime); // After backoff period. + // Restore + SetUpWithInfo(info); + ASSERT_TRUE(system_policy_); + ASSERT_FALSE(system_policy_->IsInFallbackMode()); + + // Trigger second fallback. + constexpr int64_t kSecondFallbackTime = kRestoreTime + 10; + clock_.SetTime(kSecondFallbackTime); + system_policy_->TriggerFallback(); + + constexpr int64_t kExpectedBackoffDuration = kBackoffDuration * 2; + EXPECT_FALSE(system_policy_->IsProvisioned()); + EXPECT_TRUE(system_policy_->IsInFallbackMode()); + EXPECT_EQ(system_policy_->info().backoff_start_time(), kSecondFallbackTime); + EXPECT_EQ(system_policy_->info().backoff_duration(), + kExpectedBackoffDuration); +} + +// Setup: +// 1) Device needs OKP again (see notes) +// 2) Fallback policy is restored, previously being marked as provisioned +// 3) Fallback policy is marked as needing provisioning +// Expectation: +// Once marked as needing re-provisioning, the fallback policy should +// clear is self and indicate that provisioning is needed. +// Note: +// Ideally this should never happen. Once a device is provisioned, is +// should never require OKP again. However, the device should have never +// required OKP in the first place. If a future bug arises, and a device +// requires OKP again, the fallback policy should be able to handle +// this situation without bricking L1. +TEST_F(OkpFallbackPolicyTest, Restore_NeedsProvisioningAgain) { + SystemFallbackInfo info; + constexpr int64_t kInitialProvisioningTime = kInitialTime; + SetSystemInfoAsProvisioned(&info, kInitialProvisioningTime); + constexpr int64_t kRestoreTime = kInitialProvisioningTime + 100; + clock_.SetTime(kRestoreTime); + // Restore + SetUpWithInfo(info); + ASSERT_TRUE(system_policy_); + ASSERT_TRUE(system_policy_->IsProvisioned()); + system_policy_->MarkNeedsProvisioning(); + + // Checks + EXPECT_FALSE(system_policy_->IsProvisioned()); + EXPECT_FALSE(system_policy_->IsInFallbackMode()); + EXPECT_EQ(system_policy_->info().first_checked_time(), kRestoreTime); +} +} // namespace okp +} // namespace wvcdm diff --git a/libwvdrmengine/cdm/core/test/ota_keybox_provisioner_test.cpp b/libwvdrmengine/cdm/core/test/ota_keybox_provisioner_test.cpp index ca10e39c..daaab941 100644 --- a/libwvdrmengine/cdm/core/test/ota_keybox_provisioner_test.cpp +++ b/libwvdrmengine/cdm/core/test/ota_keybox_provisioner_test.cpp @@ -7,13 +7,22 @@ #include #include "crypto_session.h" +#include "mock_clock.h" +#include "okp_fallback_policy.h" +#include "properties.h" +#include "string_conversions.h" namespace wvcdm { using ::testing::DoAll; using ::testing::NotNull; using ::testing::Return; using ::testing::SetArgPointee; +using ::video_widevine::ProvisioningResponse; +using ::video_widevine::SignedProvisioningMessage; +using ProvisioningType = SignedProvisioningMessage::ProvisioningType; + namespace { +constexpr int64_t kInitialTime = 1629321960; // Wed 18 Aug 2021 09:26:00 PM const std::string kEmptyString; const std::string kFakeOtaProvisioningRequest = "Totally real device ID, not fake" // Device ID : 32 bytes @@ -47,6 +56,14 @@ class MockCryptoSession : public CryptoSession { CdmResponseType(bool, std::string*)); MOCK_METHOD2(LoadOtaProvisioning, CdmResponseType(bool, const std::string&)); + // Client ID + uint8_t GetSecurityPatchLevel() override { return 0x00; } + bool GetBuildInformation(std::string* info) override { + if (info == nullptr) return false; + info->assign("OtaKeyboxProvisionerTest"); + return true; + } + void ExpectRequest(const std::string& request, CdmResponseType result) { if (result == NO_ERROR) { EXPECT_CALL(*this, PrepareOtaProvisioningRequest(false, NotNull())) @@ -69,174 +86,292 @@ class MockCryptoSession : public CryptoSession { }; metrics::CryptoMetrics MockCryptoSession::crypto_metrics_; + +std::vector ToVector(const std::string& s) { + return std::vector(s.begin(), s.end()); +} +std::string FromVector(const std::vector& v) { + return std::string(v.begin(), v.end()); +} } // namespace class OtaKeyboxProvisionerTest : public ::testing::Test { protected: void SetUp() override { - crypto_session_.reset(new MockCryptoSession()); - provisioner_ = OtaKeyboxProvisioner::Create(); + clock_.SetTime(kInitialTime); + fallback_policy_ = okp::SystemFallbackPolicy::CreateForTesting(&clock_); + ASSERT_TRUE(fallback_policy_); + fallback_policy_->MarkNeedsProvisioning(); + crypto_session_ = new MockCryptoSession(); + crypto_session_->SetIsOpen(true); + provisioner_ = OtaKeyboxProvisioner::CreateForTesting( + std::unique_ptr(crypto_session_), + fallback_policy_.get()); + ASSERT_TRUE(provisioner_); + } + + void SetUpWithInfo(const okp::SystemFallbackInfo& info) { + TearDown(); + fallback_policy_ = + okp::SystemFallbackPolicy::CreateForTesting(info, &clock_); + ASSERT_TRUE(fallback_policy_); + fallback_policy_->MarkNeedsProvisioning(); + crypto_session_ = new MockCryptoSession(); + crypto_session_->SetIsOpen(true); + provisioner_ = OtaKeyboxProvisioner::CreateForTesting( + std::unique_ptr(crypto_session_), + fallback_policy_.get()); } void TearDown() override { - crypto_session_.reset(); provisioner_.reset(); + crypto_session_ = nullptr; + fallback_policy_.reset(); } - std::unique_ptr crypto_session_; + FrozenClock clock_; + std::unique_ptr fallback_policy_; + MockCryptoSession* crypto_session_ = nullptr; std::unique_ptr provisioner_; + + bool restore_messages_are_json_ = false; }; -TEST_F(OtaKeyboxProvisionerTest, SingleRequestSingleResponse) { +// Used to make a variety of SignedProvisioningMessage, including invalid +// responses. +// |provisioning_type| - ProvisioningType of outer message. +// Valid response is ANDROID_ATTESTATION_KEYBOX_OTA +// |include_response| - Include the actual ProvisioningResponse message +// bytes. Valid response is true. +// |include_ota_response| - Includes the |android_ota_keybox_response| +// field of the ProvisioningResponse message. +// |ota_response| - Raw bytes of the ota_response field of the +// AndroidAttestationOtaKeyboxResponse. +// Valid responses contain the response to be passed +// into OEMCrypto. +void MakeSignedOtaProvisioningResponseEx(ProvisioningType provisioning_type, + bool include_response, + bool include_ota_response, + const std::string& ota_response, + std::string* response) { + ProvisioningResponse prov_response; + if (include_ota_response) { + prov_response.mutable_android_ota_keybox_response()->set_ota_response( + ota_response); + } + std::string message; + prov_response.SerializeToString(&message); + + SignedProvisioningMessage signed_response; + signed_response.set_protocol_version(SignedProvisioningMessage::VERSION_1); + signed_response.set_provisioning_type(provisioning_type); + if (include_response) { + signed_response.set_message(message); + } + std::string proto_response; + signed_response.SerializeToString(&proto_response); + + if (Properties::provisioning_messages_are_binary()) { + *response = std::move(proto_response); + return; + } + response->assign("{\n\"signedResponse\": \""); + response->append(Base64SafeEncodeNoPad(ToVector(proto_response))); + response->append("\"\n}\n"); +} + +// Make a valid SignedProvisioningMessage containing an OTA keybox response. +void MakeSignedOtaProvisioningResponse(const std::string& ota_response, + std::string* response) { + MakeSignedOtaProvisioningResponseEx( + SignedProvisioningMessage::ANDROID_ATTESTATION_KEYBOX_OTA, true, true, + ota_response, response); +} + +bool RequestContains(const std::string& request, const std::string& part) { + if (Properties::provisioning_messages_are_binary()) { + const auto request_pos = request.find(part); + return request_pos != std::string::npos; + } + const std::string decoded_request = FromVector(Base64SafeDecode(request)); + if (decoded_request.empty()) return false; + const auto request_pos = decoded_request.find(part); + return request_pos != std::string::npos; +} + +TEST_F(OtaKeyboxProvisionerTest, FullProvisioning) { // Pre-request conditions. EXPECT_FALSE(provisioner_->IsProvisioned()); - EXPECT_EQ(0u, provisioner_->request_count()); - EXPECT_EQ(0u, provisioner_->response_count()); + EXPECT_FALSE(provisioner_->request_generated()); + EXPECT_FALSE(provisioner_->response_received()); // Generate request. crypto_session_->ExpectRequest(kFakeOtaProvisioningRequest, NO_ERROR); - std::string request; - EXPECT_EQ(NO_ERROR, provisioner_->GenerateProvisioningRequest( - crypto_session_.get(), &request)); - EXPECT_EQ(request, kFakeOtaProvisioningRequest); + std::string signed_prov_message, default_url; + EXPECT_EQ(NO_ERROR, provisioner_->GetProvisioningRequest(&signed_prov_message, + &default_url)); + EXPECT_TRUE( + RequestContains(signed_prov_message, kFakeOtaProvisioningRequest)); + EXPECT_FALSE(default_url.empty()); // Post-request, pre-response conditions. EXPECT_FALSE(provisioner_->IsProvisioned()); - EXPECT_EQ(1u, provisioner_->request_count()); - EXPECT_EQ(0u, provisioner_->response_count()); + EXPECT_TRUE(provisioner_->request_generated()); + EXPECT_FALSE(provisioner_->response_received()); // Load response. - const std::string response = kFakeOtaProvisioningResponse; - crypto_session_->ExpectResponse(response, NO_ERROR); - EXPECT_EQ(NO_ERROR, provisioner_->HandleProvisioningResponse( - crypto_session_.get(), response)); + constexpr int64_t kProvisioningTime = kInitialTime + 10; + clock_.SetTime(kProvisioningTime); + std::string response; + MakeSignedOtaProvisioningResponse(kFakeOtaProvisioningResponse, &response); + crypto_session_->ExpectResponse(kFakeOtaProvisioningResponse, NO_ERROR); + EXPECT_EQ(NO_ERROR, provisioner_->HandleProvisioningResponse(response)); // Post-response conditions. EXPECT_TRUE(provisioner_->IsProvisioned()); - EXPECT_EQ(1u, provisioner_->request_count()); - EXPECT_EQ(1u, provisioner_->response_count()); + EXPECT_TRUE(provisioner_->request_generated()); + EXPECT_TRUE(provisioner_->response_received()); + EXPECT_EQ(kProvisioningTime, fallback_policy_->info().provisioning_time()); } -TEST_F(OtaKeyboxProvisionerTest, MultipleRequestsMultipleResponse) { - // Generate first request. +// The OTA keybox provisioning is a system-wide provisioning processes. +// After a particular CDM engine (A) begins the request, it may be +// completed by a different CDM engine (B) before A receives a response. +// Provisioning from A perspective should complete without issues. +TEST_F(OtaKeyboxProvisionerTest, CompletedInDifferentEngine) { + // Generate request (engine A). crypto_session_->ExpectRequest(kFakeOtaProvisioningRequest, NO_ERROR); - std::string first_request; - EXPECT_EQ(NO_ERROR, provisioner_->GenerateProvisioningRequest( - crypto_session_.get(), &first_request)); - EXPECT_EQ(first_request, kFakeOtaProvisioningRequest); + std::string signed_prov_message, default_url; + EXPECT_EQ(NO_ERROR, provisioner_->GetProvisioningRequest(&signed_prov_message, + &default_url)); - // Generate second request. - crypto_session_->ExpectRequest(kFakeOtaProvisioningRequest, NO_ERROR); - std::string second_request; - EXPECT_EQ(NO_ERROR, provisioner_->GenerateProvisioningRequest( - crypto_session_.get(), &second_request)); - EXPECT_EQ(second_request, kFakeOtaProvisioningRequest); - - EXPECT_FALSE(provisioner_->IsProvisioned()); - EXPECT_EQ(2u, provisioner_->request_count()); - EXPECT_EQ(0u, provisioner_->response_count()); - - // Load first response. - const std::string first_response = kFakeOtaProvisioningResponse; - crypto_session_->ExpectResponse(first_response, NO_ERROR); - EXPECT_EQ(NO_ERROR, provisioner_->HandleProvisioningResponse( - crypto_session_.get(), first_response)); - - // Loading second response should be silently dropped. - // No call to CryptoSession. - const std::string second_response = kAnotherFakeOtaProvisioningResponse; - EXPECT_EQ(NO_ERROR, provisioner_->HandleProvisioningResponse( - crypto_session_.get(), second_response)); - - // Post-response conditions. + // Complete provisioning in different context (outside of engine A's scope). + constexpr int64_t kProvisioningTime = kInitialTime + 10; + clock_.SetTime(kProvisioningTime); + fallback_policy_->MarkProvisioned(); + // Engine provisioner should still indicate that provisioning is complete. EXPECT_TRUE(provisioner_->IsProvisioned()); - EXPECT_EQ(2u, provisioner_->request_count()); - EXPECT_EQ(2u, provisioner_->response_count()); + EXPECT_FALSE(provisioner_->response_received()); + + // Load engine A's response. + constexpr int64_t kResponseTime = kProvisioningTime + 10; + clock_.SetTime(kResponseTime); + std::string response; + MakeSignedOtaProvisioningResponse(kAnotherFakeOtaProvisioningResponse, + &response); + // OtaKeyboxProvisioner::HandleProvisioningResponse will be called, but + // not OEMCrypto. + // Expect success. + EXPECT_EQ(NO_ERROR, provisioner_->HandleProvisioningResponse(response)); + + // Post-engine-response.; + EXPECT_TRUE(provisioner_->IsProvisioned()); + EXPECT_TRUE(provisioner_->response_received()); + EXPECT_EQ(kProvisioningTime, fallback_policy_->info().provisioning_time()); } -TEST_F(OtaKeyboxProvisionerTest, NullRequestParameters) { - // Null CryptoSession - std::string request; - EXPECT_NE(NO_ERROR, - provisioner_->GenerateProvisioningRequest(nullptr, &request)); - - // Null request, no call to CryptoSession. - EXPECT_NE(NO_ERROR, provisioner_->GenerateProvisioningRequest( - crypto_session_.get(), nullptr)); - // Counter should not increase. - EXPECT_FALSE(provisioner_->IsProvisioned()); - EXPECT_EQ(0u, provisioner_->request_count()); - EXPECT_EQ(0u, provisioner_->response_count()); -} - -TEST_F(OtaKeyboxProvisionerTest, EmptyRequest) { +// Test to ensure there are no critical failures when receiving a +// malformed SignedProvisioningMessage. +TEST_F(OtaKeyboxProvisionerTest, MalformedResponseMessage) { // Generate request. crypto_session_->ExpectRequest(kFakeOtaProvisioningRequest, NO_ERROR); - std::string request; - EXPECT_EQ(NO_ERROR, provisioner_->GenerateProvisioningRequest( - crypto_session_.get(), &request)); + std::string signed_prov_message, default_url; + EXPECT_EQ(NO_ERROR, provisioner_->GetProvisioningRequest(&signed_prov_message, + &default_url)); - // Attempt to load empty response. No call to CryptoSession. - const std::string response = ""; - EXPECT_NE(NO_ERROR, provisioner_->HandleProvisioningResponse( - crypto_session_.get(), response)); + // Invalid response 1: Cannot SignedProvisioningMessage. + std::string response = "Not a SignedProvisioningMessage"; + EXPECT_EQ(PARSE_OKP_RESPONSE_ERROR, + provisioner_->HandleProvisioningResponse(response)); + // Invalid response 2: Wrong SignedProvisioningMessage protocol. + MakeSignedOtaProvisioningResponseEx( + SignedProvisioningMessage::PROVISIONING_30, true, true, + kFakeOtaProvisioningResponse, &response); + EXPECT_EQ(PARSE_OKP_RESPONSE_ERROR, + provisioner_->HandleProvisioningResponse(response)); + + // Invalid response 3: Missing serialized ProvisioningResponse message. + MakeSignedOtaProvisioningResponseEx( + SignedProvisioningMessage::ANDROID_ATTESTATION_KEYBOX_OTA, false, true, + kFakeOtaProvisioningResponse, &response); + EXPECT_EQ(PARSE_OKP_RESPONSE_ERROR, + provisioner_->HandleProvisioningResponse(response)); + + // Invalid response 4: Missing AndroidAttestationOtaKeyboxResponse message, + MakeSignedOtaProvisioningResponseEx( + SignedProvisioningMessage::ANDROID_ATTESTATION_KEYBOX_OTA, true, false, + kFakeOtaProvisioningResponse, &response); + EXPECT_EQ(PARSE_OKP_RESPONSE_ERROR, + provisioner_->HandleProvisioningResponse(response)); + + // Invalid response 5: Missing raw OTA keybox response. + MakeSignedOtaProvisioningResponse(kEmptyString, &response); + EXPECT_EQ(PARSE_OKP_RESPONSE_ERROR, + provisioner_->HandleProvisioningResponse(response)); + + // Post-response failure conditions. EXPECT_FALSE(provisioner_->IsProvisioned()); - EXPECT_EQ(1u, provisioner_->request_count()); - EXPECT_EQ(0u, provisioner_->response_count()); + EXPECT_TRUE(provisioner_->request_generated()); + EXPECT_FALSE(provisioner_->response_received()); +} + +// Test case where OEMCrypto rejects the provided OTA keybox response. +TEST_F(OtaKeyboxProvisionerTest, RejectedResponse) { + // Generate request. + crypto_session_->ExpectRequest(kFakeOtaProvisioningRequest, NO_ERROR); + std::string request, default_url; + EXPECT_EQ(NO_ERROR, + provisioner_->GetProvisioningRequest(&request, &default_url)); + + // Load response. OEMCrypto returns error. + std::string response; + MakeSignedOtaProvisioningResponse(kFakeOtaProvisioningResponse, &response); + crypto_session_->ExpectResponse(kFakeOtaProvisioningResponse, UNKNOWN_ERROR); + EXPECT_NE(NO_ERROR, provisioner_->HandleProvisioningResponse(response)); + + // Post-response failure conditions. + EXPECT_FALSE(provisioner_->IsProvisioned()); + EXPECT_TRUE(provisioner_->IsInFallbackMode()); + EXPECT_TRUE(provisioner_->request_generated()); + EXPECT_FALSE(provisioner_->response_received()); } TEST_F(OtaKeyboxProvisionerTest, OtaProvisioningNotImplemented) { // Generate request. crypto_session_->ExpectRequest(kEmptyString, NOT_IMPLEMENTED_ERROR); - std::string request; - EXPECT_EQ(NOT_IMPLEMENTED_ERROR, provisioner_->GenerateProvisioningRequest( - crypto_session_.get(), &request)); - // Counter should not increase. + std::string request, default_url; + EXPECT_EQ(NOT_IMPLEMENTED_ERROR, + provisioner_->GetProvisioningRequest(&request, &default_url)); + + // Post-response failure conditions. EXPECT_FALSE(provisioner_->IsProvisioned()); - EXPECT_EQ(0u, provisioner_->request_count()); - EXPECT_EQ(0u, provisioner_->response_count()); -} - -TEST_F(OtaKeyboxProvisionerTest, ResponseRejected) { - // Generate request. - crypto_session_->ExpectRequest(kFakeOtaProvisioningRequest, NO_ERROR); - std::string request; - EXPECT_EQ(NO_ERROR, provisioner_->GenerateProvisioningRequest( - crypto_session_.get(), &request)); - - // Attempt to load response. OEMCrypto rejects response. - const std::string response = kFakeOtaProvisioningResponse; - crypto_session_->ExpectResponse(response, UNKNOWN_ERROR); - EXPECT_NE(NO_ERROR, provisioner_->HandleProvisioningResponse( - crypto_session_.get(), response)); - - // Should not be provisioned. - EXPECT_FALSE(provisioner_->IsProvisioned()); - EXPECT_EQ(1u, provisioner_->request_count()); - EXPECT_EQ(0u, provisioner_->response_count()); + EXPECT_TRUE(provisioner_->IsInFallbackMode()); + EXPECT_FALSE(provisioner_->request_generated()); + EXPECT_FALSE(provisioner_->response_received()); } TEST_F(OtaKeyboxProvisionerTest, GenerateRequestAfterProvisioning) { + fallback_policy_->MarkProvisioned(); // Generate request. - crypto_session_->ExpectRequest(kFakeOtaProvisioningRequest, NO_ERROR); - std::string first_request; - EXPECT_EQ(NO_ERROR, provisioner_->GenerateProvisioningRequest( - crypto_session_.get(), &first_request)); - - // Load response. - const std::string response = kFakeOtaProvisioningResponse; - crypto_session_->ExpectResponse(response, NO_ERROR); - EXPECT_EQ(NO_ERROR, provisioner_->HandleProvisioningResponse( - crypto_session_.get(), response)); - - // Attempt to generate second request. Should fail. - std::string second_request; - EXPECT_NE(NO_ERROR, provisioner_->GenerateProvisioningRequest( - crypto_session_.get(), &second_request)); + std::string request, default_url; + EXPECT_EQ(OKP_ALREADY_PROVISIONED, + provisioner_->GetProvisioningRequest(&request, &default_url)); EXPECT_TRUE(provisioner_->IsProvisioned()); - EXPECT_EQ(1u, provisioner_->request_count()); - EXPECT_EQ(1u, provisioner_->response_count()); + EXPECT_FALSE(provisioner_->request_generated()); + EXPECT_FALSE(provisioner_->response_received()); +} + +TEST_F(OtaKeyboxProvisionerTest, ResponseWithoutRequest) { + std::string response; + MakeSignedOtaProvisioningResponse(kFakeOtaProvisioningResponse, &response); + // Does not trigger system-wide fallback. This is a bad app. + EXPECT_NE(NO_ERROR, provisioner_->HandleProvisioningResponse(response)); + + EXPECT_FALSE(provisioner_->IsProvisioned()); + EXPECT_FALSE(provisioner_->IsInFallbackMode()); + EXPECT_FALSE(provisioner_->request_generated()); + EXPECT_FALSE(provisioner_->response_received()); // Not actually received. } } // namespace wvcdm diff --git a/libwvdrmengine/cdm/core/test/test_printers.cpp b/libwvdrmengine/cdm/core/test/test_printers.cpp index d57de31e..1027e598 100644 --- a/libwvdrmengine/cdm/core/test/test_printers.cpp +++ b/libwvdrmengine/cdm/core/test/test_printers.cpp @@ -7,7 +7,6 @@ #include "test_printers.h" namespace wvcdm { - void PrintTo(const enum CdmResponseType& value, ::std::ostream* os) { switch (value) { case NO_ERROR: @@ -623,6 +622,9 @@ void PrintTo(const enum CdmResponseType& value, ::std::ostream* os) { case NO_USAGE_ENTRIES: *os << "NO_USAGE_ENTRIES"; break; + case OKP_ALREADY_PROVISIONED: + *os << "OKP_ALREADY_PROVISIONED"; + break; case OPEN_CRYPTO_SESSION_ERROR: *os << "OPEN_CRYPTO_SESSION_ERROR"; break; @@ -632,6 +634,9 @@ void PrintTo(const enum CdmResponseType& value, ::std::ostream* os) { case PARAMETER_NULL: *os << "PARAMETER_NULL"; break; + case PARSE_OKP_RESPONSE_ERROR: + *os << "PARSE_OKP_RESPONSE_ERROR"; + break; case PARSE_REQUEST_ERROR_1: *os << "PARSE_REQUEST_ERROR_1"; break; @@ -1210,5 +1215,9 @@ void PrintTo(const enum OEMCryptoResult& value, ::std::ostream* os) { break; } } - +namespace okp { +void PrintTo(const SystemState& state, std::ostream* os) { + *os << SystemStateToString(state); +} +} // namespace okp } // namespace wvcdm diff --git a/libwvdrmengine/cdm/core/test/test_printers.h b/libwvdrmengine/cdm/core/test/test_printers.h index 0df9d06e..b51adc8f 100644 --- a/libwvdrmengine/cdm/core/test/test_printers.h +++ b/libwvdrmengine/cdm/core/test/test_printers.h @@ -8,7 +8,9 @@ #define CDM_TEST_PRINTERS_H_ #include + #include "OEMCryptoCENC.h" +#include "okp_info.h" #include "wv_cdm_types.h" namespace wvcdm { @@ -17,6 +19,8 @@ void PrintTo(const enum CdmLicenseType& value, ::std::ostream* os); void PrintTo(const enum CdmSecurityLevel& value, ::std::ostream* os); void PrintTo(const enum CdmCertificateType& value, ::std::ostream* os); void PrintTo(const enum OEMCryptoResult& value, ::std::ostream* os); +namespace okp { +void PrintTo(const SystemState& state, std::ostream* os); +} // namespace okp } // namespace wvcdm - #endif // CDM_TEST_PRINTERS_H_ diff --git a/libwvdrmengine/cdm/test/Android.mk b/libwvdrmengine/cdm/test/Android.mk index 2f12234b..09f472df 100644 --- a/libwvdrmengine/cdm/test/Android.mk +++ b/libwvdrmengine/cdm/test/Android.mk @@ -114,6 +114,15 @@ test_name := metrics_collections_unittest test_src_dir := ../metrics/test include $(LOCAL_PATH)/unit-test.mk +test_name := okp_fallback_policy_test +test_src_dir := ../core/test +include $(LOCAL_PATH)/unit-test.mk + +test_name := ota_keybox_provisioner_test +test_src_dir := ../core/test +test_main := ../core/test/test_main.cpp +include $(LOCAL_PATH)/integration-test.mk + test_name := policy_engine_constraints_unittest test_src_dir := ../core/test test_main := ../core/test/test_main.cpp diff --git a/libwvdrmengine/cdm/test/request_license_test.cpp b/libwvdrmengine/cdm/test/request_license_test.cpp index bd0b413a..976775c8 100644 --- a/libwvdrmengine/cdm/test/request_license_test.cpp +++ b/libwvdrmengine/cdm/test/request_license_test.cpp @@ -2097,7 +2097,7 @@ class WvCdmRequestLicenseTest : public WvCdmTestBase { } CdmProvisioningResponse provisioning_response; - if (!CertificateProvisioning::ExtractAndDecodeSignedMessageForTesting( + if (!CertificateProvisioning::ExtractAndDecodeSignedMessage( response, &provisioning_response)) { EXPECT_TRUE(false); return false; diff --git a/libwvdrmengine/include/WVErrors.h b/libwvdrmengine/include/WVErrors.h index 23cd2bfb..4fef1efa 100644 --- a/libwvdrmengine/include/WVErrors.h +++ b/libwvdrmengine/include/WVErrors.h @@ -302,10 +302,12 @@ enum { kCertProvisioningResponseError10 = ERROR_DRM_VENDOR_MIN + 317, kClientTokenNotSet = ERROR_DRM_VENDOR_MIN + 318, kUsageEntryAlreadyLoaded = ERROR_DRM_VENDOR_MIN + 319, + kParseOkpResponseError = ERROR_DRM_VENDOR_MIN + 320, + kOkpAlreadyProvisioned = ERROR_DRM_VENDOR_MIN + 321, // This should always follow the last error code. // The offset value should be updated each time a new error code is added. - kErrorWVDrmMaxErrorUsed = ERROR_DRM_VENDOR_MIN + 319, + kErrorWVDrmMaxErrorUsed = ERROR_DRM_VENDOR_MIN + 321, // Used by crypto test mode kErrorTestMode = ERROR_DRM_VENDOR_MAX, diff --git a/libwvdrmengine/include/mapErrors-inl.h b/libwvdrmengine/include/mapErrors-inl.h index 305644ec..f9ea0b98 100644 --- a/libwvdrmengine/include/mapErrors-inl.h +++ b/libwvdrmengine/include/mapErrors-inl.h @@ -415,8 +415,12 @@ static android::status_t mapCdmResponseType(wvcdm::CdmResponseType res) { return kNoUsageEntries; case wvcdm::OFFLINE_LICENSE_PROHIBITED: return kOfflineLicenseProhibited; + case wvcdm::OKP_ALREADY_PROVISIONED: + return kOkpAlreadyProvisioned; case wvcdm::PARAMETER_NULL: return kParameterNull; + case wvcdm::PARSE_OKP_RESPONSE_ERROR: + return kParseOkpResponseError; case wvcdm::PARSE_REQUEST_ERROR_1: return kParseRequestError1; case wvcdm::PARSE_REQUEST_ERROR_2: diff --git a/libwvdrmengine/include_hidl/mapErrors-inl.h b/libwvdrmengine/include_hidl/mapErrors-inl.h index bceea4d9..5b7128b1 100644 --- a/libwvdrmengine/include_hidl/mapErrors-inl.h +++ b/libwvdrmengine/include_hidl/mapErrors-inl.h @@ -362,6 +362,8 @@ static Status mapCdmResponseType_1_0(wvcdm::CdmResponseType res) { case wvcdm::CERT_PROVISIONING_RESPONSE_ERROR_10: case wvcdm::CLIENT_TOKEN_NOT_SET: case wvcdm::USAGE_ENTRY_ALREADY_LOADED: + case wvcdm::PARSE_OKP_RESPONSE_ERROR: + case wvcdm::OKP_ALREADY_PROVISIONED: ALOGW("Returns UNKNOWN error for legacy status: %d", res); return Status::ERROR_DRM_UNKNOWN; diff --git a/libwvdrmengine/run_all_unit_tests.sh b/libwvdrmengine/run_all_unit_tests.sh index 4ce288ab..ba648703 100755 --- a/libwvdrmengine/run_all_unit_tests.sh +++ b/libwvdrmengine/run_all_unit_tests.sh @@ -128,6 +128,8 @@ adb_shell_run libwvdrmmediacrypto_test adb_shell_run license_keys_unittest adb_shell_run license_unittest adb_shell_run odk_test +adb_shell_run okp_fallback_policy_test +adb_shell_run ota_keybox_provisioner_test adb_shell_run policy_engine_constraints_unittest adb_shell_run policy_engine_unittest adb_shell_run policy_integration_test