Wrapped OKP info into several classes. am: 39558526f6

Original change: https://googleplex-android-review.googlesource.com/c/platform/vendor/widevine/+/16058118

Change-Id: I77e2e5023bca6f9b1ff1279d517534e58693b2a2
This commit is contained in:
Rahul Frias
2021-10-28 17:02:11 +00:00
committed by Automerger Merge Worker
30 changed files with 2010 additions and 330 deletions

View File

@@ -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",

View File

@@ -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<OtaKeyboxProvisioner> 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);
};

View File

@@ -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_;

View File

@@ -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

View File

@@ -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<std::string, CryptoKey*>;
// 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<CryptoSessionFactory> 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<OtaKeyboxProvisioner> ota_keybox_provisioner_l1_;
static std::unique_ptr<okp::SystemFallbackPolicy> okp_fallback_policy_l1_;
CORE_DISALLOW_COPY_AND_ASSIGN(CryptoSession);
}; // class CryptoSession

View File

@@ -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);

View File

@@ -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 <inttypes.h>
#include <memory>
#include <mutex>
#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<SystemFallbackPolicy> 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<SystemFallbackPolicy> CreateForTesting(
Clock* clock = nullptr);
static std::unique_ptr<SystemFallbackPolicy> 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<FileSystem> fs_;
std::unique_ptr<DeviceFiles> 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_

View File

@@ -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 <inttypes.h>
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_

View File

@@ -5,62 +5,79 @@
#define WVCDM_CORE_OTA_KEYBOX_PROVISIONER_H_
#include <memory>
#include <mutex>
#include <string>
#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<OtaKeyboxProvisioner> 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<OtaKeyboxProvisioner> Create(
metrics::CryptoMetrics* crypto_metrics);
static std::unique_ptr<OtaKeyboxProvisioner> CreateForTesting(
std::unique_ptr<CryptoSession>&& 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<CryptoSession>&& 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<CryptoSession> 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

View File

@@ -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

View File

@@ -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<std::mutex> 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<std::mutex> lock(okp_mutex_);
// |okp_fallback_| would have been set previously if required.
if (okp_fallback_) forced_level3 = true;
}
CloseExpiredReleaseSessions();
std::unique_ptr<CdmSession> 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<int>(sts));
}
return sts;
}
if (sts != NO_ERROR) {
@@ -499,6 +540,14 @@ CdmResponseType CdmEngine::QueryStatus(SecurityLevel security_level,
std::unique_ptr<CryptoSession> 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<int>(status));
return status;
}
}
} else {
std::unique_lock<std::mutex> 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<std::mutex> 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<int>(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<CryptoSession> crypto_session(
@@ -1046,11 +1167,15 @@ CdmResponseType CdmEngine::Unprovision(CdmSecurityLevel security_level) {
CdmResponseType CdmEngine::ListStoredLicenses(
CdmSecurityLevel security_level, std::vector<std::string>* 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<std::string>* ksids,
std::vector<std::string>* 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<std::mutex> lock(okp_mutex_);
if (okp_initialized_) {
return static_cast<bool>(okp_provisioner_);
}
okp_initialized_ = true;
// Creating a CryptoSession will initialize OEMCrypto and flag the need
// for OKP.
std::unique_ptr<CryptoSession> 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<std::mutex> 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<std::mutex> 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<std::mutex> lock(okp_mutex_);
if (!okp_initialized_) {
LOGD("Call to OKP fallback before OKP setup");
return;
}
okp_provisioner_.reset();
}
} // namespace wvcdm

View File

@@ -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;
}

View File

@@ -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<uint8_t> 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<uint8_t> decoded_message =
wvcdm::Base64SafeDecode(message_string);
result->assign(decoded_message.begin(), decoded_message.end());
return true;
}
bool CertificateProvisioning::ExtractDeviceInfo(

View File

@@ -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<UsageTableHeader> CryptoSession::usage_table_header_l1_;
std::unique_ptr<UsageTableHeader> CryptoSession::usage_table_header_l3_;
std::recursive_mutex CryptoSession::usage_table_mutex_;
std::atomic<uint64_t> CryptoSession::request_id_index_source_(0);
std::unique_ptr<OtaKeyboxProvisioner> CryptoSession::ota_keybox_provisioner_l1_;
std::unique_ptr<okp::SystemFallbackPolicy>
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(

View File

@@ -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<int>(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<int>(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<int>(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<int>(file.type()),
static_cast<int>(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<int>(file.version()),
static_cast<int>(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<int>(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<int>(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));
}

View File

@@ -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 {

View File

@@ -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 <utility>
#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<std::mutex>;
namespace {
constexpr int64_t kErrorTime = -1;
int64_t GenerateInitialBackoffDuration() {
return static_cast<int64_t>(CdmRandom::RandomInRange(
kMinInitialBackoffDuration, kMaxInitialBackoffDuration));
}
} // namespace
// static
std::unique_ptr<SystemFallbackPolicy> SystemFallbackPolicy::Create() {
std::unique_ptr<FileSystem> fs(new FileSystem());
std::unique_ptr<DeviceFiles> device_files(new DeviceFiles(fs.get()));
if (!device_files->Init(kSecurityLevelL1)) {
LOGE("Failed to initialize device files");
return nullptr;
}
std::unique_ptr<SystemFallbackPolicy> policy(new SystemFallbackPolicy());
policy->fs_ = std::move(fs);
policy->device_files_ = std::move(device_files);
policy->TryRestore();
return policy;
}
// static
std::unique_ptr<SystemFallbackPolicy> SystemFallbackPolicy::CreateForTesting(
Clock* clock) {
std::unique_ptr<SystemFallbackPolicy> policy(new SystemFallbackPolicy());
if (clock != nullptr) {
policy->SetClockForTesting(clock);
}
// Device files are not supported for test instances.
return policy;
}
// static
std::unique_ptr<SystemFallbackPolicy> SystemFallbackPolicy::CreateForTesting(
const SystemFallbackInfo& info, Clock* clock) {
std::unique_ptr<SystemFallbackPolicy> 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<int>(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<int>(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<bool>(device_files_);
}
} // namespace okp
} // namespace wvcdm

View File

@@ -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

View File

@@ -3,81 +3,272 @@
// Agreement.
#include "ota_keybox_provisioner.h"
#include <assert.h>
#include <utility>
#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<std::mutex>;
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> OtaKeyboxProvisioner::Create() {
return std::unique_ptr<OtaKeyboxProvisioner>(new OtaKeyboxProvisioner());
std::unique_ptr<OtaKeyboxProvisioner> 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<CryptoSession> 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<int>(security_level));
crypto_session.reset();
return nullptr;
}
std::unique_ptr<OtaKeyboxProvisioner> 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> OtaKeyboxProvisioner::CreateForTesting(
std::unique_ptr<CryptoSession>&& 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<OtaKeyboxProvisioner> 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<CryptoSession>&& crypto_session,
okp::SystemFallbackPolicy* fallback_policy)
: crypto_session_(std::move(crypto_session)),
fallback_policy_(fallback_policy) {
assert(static_cast<bool>(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<int>(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<int>(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<uint8_t>(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<int>(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

View File

@@ -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

View File

@@ -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 <memory>
#include <gtest/gtest.h>
#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<SystemFallbackPolicy> 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

View File

@@ -7,13 +7,22 @@
#include <gtest/gtest.h>
#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<uint8_t> ToVector(const std::string& s) {
return std::vector<uint8_t>(s.begin(), s.end());
}
std::string FromVector(const std::vector<uint8_t>& 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<CryptoSession>(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<CryptoSession>(crypto_session_),
fallback_policy_.get());
}
void TearDown() override {
crypto_session_.reset();
provisioner_.reset();
crypto_session_ = nullptr;
fallback_policy_.reset();
}
std::unique_ptr<MockCryptoSession> crypto_session_;
FrozenClock clock_;
std::unique_ptr<okp::SystemFallbackPolicy> fallback_policy_;
MockCryptoSession* crypto_session_ = nullptr;
std::unique_ptr<OtaKeyboxProvisioner> 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

View File

@@ -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

View File

@@ -8,7 +8,9 @@
#define CDM_TEST_PRINTERS_H_
#include <iostream>
#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_

View File

@@ -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

View File

@@ -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;

View File

@@ -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,

View File

@@ -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:

View File

@@ -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;

View File

@@ -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