Wrapped OKP info into several classes. am: 39558526f6
Original change: https://googleplex-android-review.googlesource.com/c/platform/vendor/widevine/+/16058118 Change-Id: I77e2e5023bca6f9b1ff1279d517534e58693b2a2
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
113
libwvdrmengine/cdm/core/include/okp_fallback_policy.h
Normal file
113
libwvdrmengine/cdm/core/include/okp_fallback_policy.h
Normal 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_
|
||||
78
libwvdrmengine/cdm/core/include/okp_info.h
Normal file
78
libwvdrmengine/cdm/core/include/okp_info.h
Normal 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_
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
208
libwvdrmengine/cdm/core/src/okp_fallback_policy.cpp
Normal file
208
libwvdrmengine/cdm/core/src/okp_fallback_policy.cpp
Normal 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
|
||||
32
libwvdrmengine/cdm/core/src/okp_info.cpp
Normal file
32
libwvdrmengine/cdm/core/src/okp_info.cpp
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
372
libwvdrmengine/cdm/core/test/okp_fallback_policy_test.cpp
Normal file
372
libwvdrmengine/cdm/core/test/okp_fallback_policy_test.cpp
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user