Wrapped OKP info into several classes.

[ Merge of http://go/wvgerrit/133744 ]

This changes adds several small classes which contain and manage
system and engine information related to OTA keybox provisioning.
These classes closely map to the OKP device file messages.

Bug: 189232882
Test: Linux unit tests
Change-Id: Ia9334c38f9d7ea89b30d9ad05f0595570bb38658

Storing and loading OKP info.

[ Merge of http://go/wvgerrit/133763 and http://go/ag/15645333 ]

This change extends the DeviceFiles module to be able to store and
load OKP info.  Mild data validation is performed when storing and
loading the information.

Bug: 189232882
Test: Android unit tests
Change-Id: I077de3234157252f2255a4389bf82a8d5344a355

System OKP fallback policy.

[ Merge of http://go/wvgerrit/133783 and http://go/ag/15645334 ]

SystemFallbackPolicy provides a thread-safe interface for accessing
and modifying OKP info.

Bug: 189232882
Test: Android unit tests
Change-Id: I4e43e3bc047ed5fb6cb517b53e4094e812b70e1e

Engine OKP provisioner.

[ Merge of http://go/wvgerrit/133803 and http://go/ag/15645335 ]

The OtaKeyboxProvisioner provides a CdmEngine-specific context for
performing OTA keybox provisioning.  Utilizes the system-wide
SystemFallbackPolicy to relay provisioning status between engines.
The provisioner will handle message wrapping and unwrapping of the
raw OTA keybox request / response into the SignedProvisioningMessage
which is sent to/received from the provisioning server.

[ Partial merge of http://go/wvgerrit/125844 ]

Note: Includes partial CryptoSession changes from various CLs.
CryptoSession functionality has been stripped to reduce impact of
this CL.

Bug: 189232882
Test: Android unit tests
Change-Id: I282bf7d1887daefb2250af1bd595c4dc3dfcfb29

Integrated OKP into CDM Engine

[ Merge of http://go/wvgerrit/133804 and http://go/ag/15646376 ]

Extended the functionality of the CdmEngine to check if the device
requires OKP and to initialize OKP resources if required.  The
functionality of OpenSession() and GetProvisioningRequest() have been
the most affected.  If OKP is required, these methods will signal to
the app that provisioning is required and will return an OKP request.

Once a device is provisioned, the OKP data is cleared away and the
CdmEngine will resume normal operation.  Engines created after a
device is provisioned will immediately enter normal operations.
The exception is for CdmEngines which failed to perform OKP for some
reason and are still running.  Those apps will need to restart before
gaining access to L1 operations.

Bug: 187646550
Test: Android integration tests
Merged-In: Ia572a66a7b73479355758aa3d0c682691eaca0fc
Change-Id: Ia572a66a7b73479355758aa3d0c682691eaca0fc
This commit is contained in:
Rahul Frias
2021-09-15 05:00:19 -07:00
committed by Alex Dale
parent 52bd1d206e
commit 39558526f6
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