V18.4.0 CAS plugin

Note that this version does not have Widevine Provisioning 4.0 support.
It is only suitable for device upgrades. A new patch with provisioning
4.0 support will be made later.
This commit is contained in:
Lu Chen
2024-02-22 13:45:32 -08:00
parent ff9728aaa2
commit 5f209e6980
92 changed files with 25729 additions and 0 deletions

36
plugin/Android.bp Normal file
View File

@@ -0,0 +1,36 @@
cc_library_static {
name: "libwvcasplugins",
local_include_dirs: [
"include",
],
export_include_dirs: ["include"],
srcs: [
"src/crypto_session.cpp",
"src/widevine_cas_session_map.cpp",
"src/widevine_media_cas.cpp",
"src/cas_license.cpp",
"src/ecm_parser.cpp",
"src/ecm_parser_v2.cpp",
"src/ecm_parser_v3.cpp",
"src/emm_parser.cpp",
"src/license_key_status.cpp",
"src/oemcrypto_interface.cpp",
"src/policy_engine.cpp",
"src/widevine_cas_session.cpp",
"src/widevine_media_cas_plugin.cpp",
"src/widevine_cas_api.cpp",
],
proprietary: true,
shared_libs: [
"libcrypto",
"libutils",
],
static_libs: [
"//vendor/widevine/libwvmediacas/wvutil:libcasutil",
"//vendor/widevine/libwvmediacas/protos:libcas_protos",
],
header_libs: [
"//vendor/widevine/libwvmediacas/oemcrypto:oemcastroheaders",
"media_plugin_headers",
],
}

128
plugin/include/cas_events.h Normal file
View File

@@ -0,0 +1,128 @@
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine Master
// License Agreement.
#ifndef CAS_EVENTS_H
#define CAS_EVENTS_H
#define PROVISIONING_EVENT_START 1000
#define LICENSING_EVENT_START 2000
#define CAS_SESSION_EVENT_START 3000
#define CAS_QUERY_EVENT_START 4000
#define CAS_ERROR_EVENT_START 5000
#define CAS_PARENTAL_CONTROL_EVENT_START 6000
#define CAS_FINGERPRINTING_EVENT_START 6100
#define CAS_SERVICE_BLOCKING_EVENT_START 6200
#define CAS_TEST_EVENT_START 10000
typedef enum {
UNKNOWN = 0,
INDIVIDUALIZATION_REQUEST = PROVISIONING_EVENT_START,
INDIVIDUALIZATION_RESPONSE,
INDIVIDUALIZATION_COMPLETE,
LICENSE_REQUEST = LICENSING_EVENT_START,
LICENSE_RESPONSE,
CAS_ERROR_DEPRECATED,
LICENSE_RENEWAL_REQUEST,
LICENSE_RENEWAL_RESPONSE,
LICENSE_RENEWAL_URL,
LICENSE_CAS_READY,
LICENSE_CAS_RENEWAL_READY,
LICENSE_REMOVAL,
LICENSE_REMOVED,
ASSIGN_LICENSE_ID,
LICENSE_ID_ASSIGNED,
LICENSE_NEW_EXPIRY_TIME,
MULTI_CONTENT_LICENSE_INFO,
GROUP_LICENSE_INFO,
LICENSE_ENTITLEMENT_PERIOD_UPDATE_REQUEST,
LICENSE_ENTITLEMENT_PERIOD_UPDATE_RESPONSE,
LICENSE_ENTITLEMENT_PERIOD_UPDATED,
// TODO(jfore): Evaluate removing this event in favor of return status codes
// from
// frameworks/av/media/libstagefright/include/media/stagefright/MediaErrors.h
CAS_ERROR = CAS_ERROR_EVENT_START,
CAS_SESSION_ID = CAS_SESSION_EVENT_START,
SET_CAS_SOC_ID,
SET_CAS_SOC_DATA,
UNIQUE_ID = CAS_QUERY_EVENT_START,
QUERY_UNIQUE_ID,
WV_CAS_PLUGIN_VERSION,
QUERY_WV_CAS_PLUGIN_VERSION,
SET_PARENTAL_CONTROL_AGE = CAS_PARENTAL_CONTROL_EVENT_START,
DEPRECATED_PARENTAL_CONTROL_AGE_UPDATED,
ACCESS_DENIED_BY_PARENTAL_CONTROL,
AGE_RESTRICTION_UPDATED,
// The content of FINGERPRINTING_INFO events follows TLV (Type (1 byte) -
// Length (2 bytes) - Value) format. See FingerprintingFieldType for possible
// types. A FINGERPRINTING_INFO event contains {one or more CHANNEL, one
// CONTROL}.
FINGERPRINTING_INFO = CAS_FINGERPRINTING_EVENT_START,
// Fingerprinting control info for a session. The content of the event follows
// TLV (Type (1 byte) - Length (2 bytes) - Value) format. See
// SessionFingerprintingFieldType for possible types. It will contain {one
// FINGERPRINTING_CONTROL}.
SESSION_FINGERPRINTING_INFO,
// The content of SERVICE_BLOCKING_INFO events follows TLV (Type (1 byte) -
// Length (2 bytes) - Value) format. See ServiceBlockingFieldType for possible
// types. A SERVICE_BLOCKING_INFO event contains {one or more CHANNEL, one or
// more DEVICE_GROUP, zero or one START_TIME_SECONDS, one END_TIME_SECONDS}.
SERVICE_BLOCKING_INFO = CAS_SERVICE_BLOCKING_EVENT_START,
// Service blocking device group for a session. The content of the event
// follows TLV (Type (1 byte) - Length (2 bytes) - Value) format. See
// SessionServiceBlockingFieldType for possible types. It will contain {one or
// more SERVICE_BLOCKING_DEVICE_GROUP}.
SESSION_SERVICE_BLOCKING_INFO,
TEST_FOR_ECHO =
CAS_TEST_EVENT_START, // Request an ECHO response to test events passing.
ECHO, // Respond to TEST_FOR_ECHO.
} CasEventId;
// Types used inside an FINGERPRINTING_INFO event.
typedef enum {
FINGERPRINTING_CHANNEL = 0,
FINGERPRINTING_CONTROL,
} FingerprintingFieldType;
// Types used inside an SERVICE_BLOCKING_INFO event.
typedef enum {
SERVICE_BLOCKING_CHANNEL = 0,
SERVICE_BLOCKING_DEVICE_GROUP,
// Epoch time in seconds. Missing of this field or a value of 0 means
// immediate start.
SERVICE_BLOCKING_START_TIME_SECONDS,
SERVICE_BLOCKING_END_TIME_SECONDS, // Epoch time in seconds.
} ServiceBlockingFieldType;
// Types used inside an SESSION_FINGERPRINTING_CONTROL event.
typedef enum {
SESSION_FINGERPRINTING_CONTROL = 0,
} SessionFingerprintingFieldType;
// Types used inside an SESSION_SERVICE_BLOCKING_GROUPS event.
typedef enum {
SESSION_SERVICE_BLOCKING_DEVICE_GROUP = 0,
} SessionServiceBlockingFieldType;
// Types used inside a MULTI_CONTENT_LICENSE_INFO event.
typedef enum {
MULTI_CONTENT_LICENSE_ID = 0,
MULTI_CONTENT_LICENSE_CONTENT_ID,
} MultiContentLicenseFieldType;
// Types used inside a GROUP_LICENSE_INFO event.
typedef enum {
GROUP_LICENSE_ID = 0,
GROUP_LICENSE_GROUP_ID,
} GroupLicenseFieldType;
#endif // CAS_EVENTS_H

View File

@@ -0,0 +1,184 @@
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine Master
// License Agreement.
#ifndef CAS_LICENSE_H
#define CAS_LICENSE_H
#include <memory>
#include <string>
#include "cas_status.h"
#include "crypto_session.h"
#include "policy_engine.h"
#include "timer.h"
namespace wvcas {
// CasLicense implements the core functionality needed to interact with service
// to obtain and manage entitlements.
class CasLicense : public wvutil::TimerHandler, public wvcas::CasEventListener {
public:
CasLicense() {}
virtual ~CasLicense() {}
// Initialize CasLicense with a |crypto_session|. |listener| may be null.
virtual CasStatus initialize(std::shared_ptr<CryptoSession> crypto_session,
CasEventListener* listener);
// Generate a request to obtain a device certificate for requesting
// entitlements. The generated message is set in |provisioning_request|.
virtual CasStatus GenerateDeviceProvisioningRequest(
std::string* provisioning_request) const;
// Process a server response containing a device certificate for use in
// requesting entitlements. The contained certificate data will be extracted
// and wrapped for storage. The public key cert will be returned in
// |device_certificate|. The private key information will be wrapped by the
// crypto session and returned in |wrapped_rsa_key|.
// A secure binary file image containing the device cert is returned in
// |device_file| if not nullptr. This file is suitable for storage on a device
virtual CasStatus HandleDeviceProvisioningResponse(
const std::string& signed_provisioning_response,
std::string* device_certificate, std::string* wrapped_rsa_key,
std::string* device_file) const;
// Generate a request to obtain an EMM (Entitlement Management Message) to
// use to enable processing of ECM(s) (Encryption Management Message).
// |init_data| is widevine metadata about the stream needed in the request.
// |wrapped_rsa_key| and |signed_license_request| are the device certificate
// obtained by HandleDeviceProvisioningResponse.
virtual CasStatus GenerateEntitlementRequest(
const std::string& init_data, const std::string& device_certificate,
const std::string& wrapped_rsa_key, LicenseType license_type,
std::string* signed_license_request);
// Restores a stored license making the keys available for use.
virtual CasStatus HandleStoredLicense(const std::string& wrapped_rsa_key,
const std::string& license_file);
// Process a server response containing a EMM for use in the processing of
// ECM(s).
// If |device_file| is not nullptr and the license policy allows a license to
// be stored |device_file| is populated with the bytes of the license secured
// for storage.
virtual CasStatus HandleEntitlementResponse(
const std::string& entitlement_response, std::string* device_file);
// Process a previously stored device |certificate| and make it available
// for use in an EMM request.
virtual CasStatus HandleStoredDrmCert(const std::string& certificate,
std::string* device_certificate,
std::string* wrapped_rsa_key);
// Generate an entitlement renewal request message in
// |signed_renewal_request|.
virtual CasStatus GenerateEntitlementRenewalRequest(
const std::string& device_certificate,
std::string* signed_renewal_request);
// Process a server response containing a EMM renewal. If |device_file| is not
// nullptr and the license policy allows a license renewal to be stored
// |device_file| is populated with the bytes of the license secured for
// storage.
virtual CasStatus HandleEntitlementRenewalResponse(
const std::string& renewal_response, std::string* device_file);
// Query the license to see if a key is usable.
virtual bool CanDecryptContent(const KeyId& key_id) const;
// Update the license after handling license remove. Plugin is disabled to
// playback stream, store and renew license.
virtual void UpdateLicenseForLicenseRemove();
// Query the license to see if storage is allowed.
virtual bool CanStoreLicense() const;
// Returns the group id specified in the license. Group id is expected to be
// non-empty if the license is MULTI_CONTENT_LICENSE or GROUP_LICENSE; and
// empty if the license is SINGLE_CONTENT_LICENSE_DEFAULT.
virtual std::string GetGroupId() const;
// If the license is MULTI_CONTENT_LICENSE, the returned vector contains all
// content ids that the license is for. Returns empty if the license if not
// MULTI_CONTENT_LICENSE.
virtual std::vector<std::string> GetContentIdList() const;
// Returns true if the license is MULTI_CONTENT_LICENSE, and false otherwise.
virtual bool IsMultiContentLicense() const;
// Returns true if the license is GROUP_LICENSE, and false otherwise.
virtual bool IsGroupLicense() const;
// Policy timer implentation.
void OnTimerEvent() override;
// Event listener implementation.
void OnSessionRenewalNeeded() override;
void OnSessionKeysChange(const KeyStatusMap& keys_status,
bool has_new_usable_key) override;
void OnExpirationUpdate(int64_t new_expiry_time_seconds) override;
void OnLicenseExpiration() override;
void OnNewRenewalServerUrl(const std::string& renewal_server_url) override;
void OnAgeRestrictionUpdated(const WvCasSessionId& sessionId,
uint8_t ecm_age_restriction) override;
void OnFingerprintingUpdated(const CasData& fingerprinting) override;
void OnServiceBlockingUpdated(const CasData& service_blocking) override;
void OnSessionFingerprintingUpdated(const WvCasSessionId& sessionId,
const CasData& fingerprinting) override;
void OnSessionServiceBlockingUpdated(
const WvCasSessionId& sessionId,
const CasData& service_blocking) override;
void OnEntitlementPeriodUpdateNeeded(
const std::string& signed_license_request) override;
// Query to see if the license is expired.
virtual bool IsExpired() const;
// Notify the license that playback decryption has begun.
virtual void BeginDecryption();
// Returns NoError if a valid entitlement period index exists in
// |license_file|. The index will be assigned to |entitlement_period_index|.
static CasStatus GetEntitlementPeriodIndexFromStoredLicense(
const std::string& license_file, uint32_t& entitlement_period_index);
CasLicense(const CasLicense&) = delete;
CasLicense& operator=(const CasLicense&) = delete;
private:
CasStatus GenerateDeviceProvisioningRequestWithKeybox(
std::string* provisioning_request) const;
CasStatus GenerateDeviceProvisioningRequestWithOEMCert() const;
CasStatus InstallLicense(const std::string& session_key,
const std::string& serialized_license,
const std::string& core_message,
const std::string& signature);
CasStatus InstallLicenseRenewal(const std::string& serialized_license,
const std::string& core_message,
const std::string& signature);
virtual std::unique_ptr<PolicyEngine> GetPolicyEngine();
std::unique_ptr<PolicyEngine> policy_engine_;
std::shared_ptr<CryptoSession> crypto_session_;
CasEventListener* event_listener_ = nullptr;
video_widevine::License license_;
std::string emm_request_;
std::string emm_response_;
std::string renewal_request_;
std::string renewal_response_;
std::string init_data_;
bool is_renewal_in_license_file_ = false;
};
} // namespace wvcas
#endif // CAS_LICENSE_H

View File

@@ -0,0 +1,25 @@
#ifndef CAS_MEDIA_ID_H
#define CAS_MEDIA_ID_H
namespace wvcas {
class CasMediaId {
protected:
CasMediaId() {}
public:
CasMediaId(const CasMediaId&) = delete;
CasMediaId& operator=(const CasMediaId&) = delete;
virtual ~CasMediaId() {}
static std::unique_ptr<CasMediaId> create();
virtual CasStatus initialize(const std::string& init_data) = 0;
virtual const std::string content_id() = 0;
virtual const std::string provider_id() = 0;
virtual bool is_entitlement_rotation_enabled() { return false; }
virtual uint32_t entitlement_period_index() = 0;
virtual std::string get_init_data() = 0;
};
} // namespace wvcas
#endif // CAS_MEDIA_ID_H

View File

@@ -0,0 +1,53 @@
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine Master
// License Agreement.
#ifndef CAS_STATUS_H
#define CAS_STATUS_H
#include <string>
namespace wvcas {
// TODO(jfore): Add more detailed error status codes.
enum class CasStatusCode : int32_t {
kUnknownError = 0,
kNoError = 1,
kCryptoInterfaceError = 2,
kCryptoSessionError = 3,
kCasLicenseError = 4,
kIndividualizationError = 5,
kInvalidParameter = 6,
kDecryptionError = 7,
kKeyNotFound = 8,
kSessionNotFound = 9,
kUnknownLicenseType = 10,
kLicenseFileParseError = 11,
kInvalidLicenseFile = 12,
kInvalidPesData = 13,
kDeferedEcmProcessing = 14,
kAccessDeniedByParentalControl = 15,
kUnknownEvent = 16,
kOEMCryptoVersionMismatch = 17,
};
class CasStatus {
public:
CasStatus(CasStatusCode status = CasStatusCode::kNoError,
const std::string& err_string = std::string())
: status_(status), err_string_(err_string) {}
static CasStatus OkStatus() { return CasStatus(); }
virtual ~CasStatus() {}
virtual CasStatusCode status_code() const { return status_; }
virtual const std::string& error_string() const { return err_string_; }
virtual bool ok() const { return status_ == CasStatusCode::kNoError; }
private:
CasStatusCode status_;
std::string err_string_;
};
} // namespace wvcas
#endif // CAS_STATUS_H

116
plugin/include/cas_types.h Normal file
View File

@@ -0,0 +1,116 @@
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine Master
// License Agreement.
#ifndef CAS_TYPES_H
#define CAS_TYPES_H
#include <map>
#include <string>
#include <vector>
static const int kCryptoSessionErrorStart = 1000;
namespace wvcas {
typedef std::vector<uint8_t> WvCasSessionId;
enum class LicenseType {
kStreaming = 0,
kOffline = 1,
// License type decision is left to the provider.
kAutomatic = 2,
};
typedef enum {
ProvisioningError = 0, // Device cannot be provisioned.
DrmCertificate = 1, // Device has baked in DRM certificate
// (level 3 only)
Keybox = 2, // Device has factory installed unique keybox.
OEMCertificate = 3 // Device has factory installed OEM certificate.
} CasProvisioningMethod;
enum class CryptoMode {
kInvalid = -1,
kAesCBC = 0,
kAesCTR = 1,
kDvbCsa2 = 2,
kDvbCsa3 = 3,
kAesOFB = 4,
kAesSCTE = 5,
kAesECB = 6,
};
enum KeyStatus {
kKeyStatusKeyUnknown,
kKeyStatusUsable,
kKeyStatusExpired,
kKeyStatusOutputNotAllowed,
kKeyStatusPending,
kKeyStatusInternalError,
};
enum SecurityLevel {
kSecurityLevelUninitialized,
kSecurityLevelL1,
kSecurityLevelL2,
kSecurityLevelL3,
kSecurityLevelUnknown
};
typedef std::vector<uint8_t> CasEcm;
typedef std::vector<uint8_t> CasEmm;
typedef std::vector<uint8_t> CasData;
typedef std::string KeyId;
typedef std::map<KeyId, KeyStatus> KeyStatusMap;
// Listener for events from the policy engine.
class CasEventListener {
public:
CasEventListener() {}
virtual ~CasEventListener() {}
// Notify listeners that a license renewal is needed.
virtual void OnSessionRenewalNeeded() = 0;
// Notify listeners that the keys have changed state.
virtual void OnSessionKeysChange(const KeyStatusMap& keys_status,
bool has_new_usable_key) = 0;
// |new_expiry_time_seconds| of 0 means "never expires".
virtual void OnExpirationUpdate(int64_t new_expiry_time_seconds) = 0;
// Notify listeners of the current renewal url.
virtual void OnNewRenewalServerUrl(const std::string& renewal_server_url) = 0;
// Notify listeners of current license is expired.
virtual void OnLicenseExpiration() = 0;
// Notify listeners of new age restriction value in processed ECM.
virtual void OnAgeRestrictionUpdated(const WvCasSessionId& sessionId,
uint8_t ecm_age_restriction) = 0;
// Notifies listeners of new session fingerprinting info.
virtual void OnSessionFingerprintingUpdated(
const WvCasSessionId& sessionId, const CasData& fingerprinting) = 0;
// Notifies listeners of new session service blocking info.
virtual void OnSessionServiceBlockingUpdated(
const WvCasSessionId& sessionId, const CasData& service_blocking) = 0;
// Notifies listeners of new fingerprinting info.
virtual void OnFingerprintingUpdated(const CasData& fingerprinting) = 0;
// Notifies listeners of new service blocking info.
virtual void OnServiceBlockingUpdated(const CasData& service_blocking) = 0;
virtual void OnEntitlementPeriodUpdateNeeded(
const std::string& signed_license_request) = 0;
CasEventListener(const CasEventListener&) = delete;
CasEventListener& operator=(const CasEventListener&) = delete;
};
} // namespace wvcas
#endif // CAS_TYPES_H

21
plugin/include/cas_util.h Normal file
View File

@@ -0,0 +1,21 @@
// Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine Master
// License Agreement.
#ifndef CAS_UTIL_H_
#define CAS_UTIL_H_
#include <memory>
#if __cplusplus >= 201402L || \
(defined __cpp_lib_make_unique && __cpp_lib_make_unique >= 201304L) || \
(defined(_MSC_VER) && _MSC_VER >= 1900)
using std::make_unique;
#else
template <typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args) {
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}
#endif
#endif // CAS_UTIL_H_

View File

@@ -0,0 +1,310 @@
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine Master
// License Agreement.
#ifndef CRYPTO_SESSION_H
#define CRYPTO_SESSION_H
#include <limits>
#include <memory>
#include <unordered_map>
#include <vector>
#include "OEMCryptoCENC.h"
#include "cas_status.h"
#include "cas_types.h"
#include "oemcrypto_interface.h"
#include "rw_lock.h"
namespace wvcas {
struct KeySlot {
std::vector<uint8_t> key_id;
std::vector<uint8_t> entitlement_key_id;
std::vector<uint8_t> wrapped_key;
std::vector<uint8_t> wrapped_key_iv;
std::vector<uint8_t> content_iv;
CryptoMode cipher_mode;
};
struct SubSample {
uint32_t num_bytes_of_clear;
uint32_t num_bytes_of_encrypted;
};
typedef OEMCrypto_HDCP_Capability HdcpCapability;
class CryptoLock {
public:
CryptoLock(){};
// These methods should be used to take the various CryptoSession mutexes in
// preference to taking the mutexes directly.
//
// A lock should be taken on the Static Field Mutex before accessing any of
// CryptoSession's non-atomic static fields. It can be taken as a reader or as
// a writer, depending on how you will be accessing the static fields.
//
// Before calling into OEMCrypto, code must take locks on the OEMCrypto Mutex
// and/or the OEMCrypto Session Mutex. Which of them should be taken and how
// depends on the OEMCrypto function being called; consult the OEMCrypto
// specification's threading guarantees before making any calls. The OEMCrypto
// specification defines several classes of functions for the purposes of
// parallelism. The methods below lock the OEMCrypto Mutex and OEMCrypto
// Session Mutex in the correct order and manner to fulfill the guarantees in
// the specification.
//
// For this function class... | ...use this locking method
// ------------------------------+---------------------------
// Initialization & Termination | WithOecWriteLock()
// Property | WithOecReadLock()
// Session Initialization | WithOecWriteLock()
// Usage Table | WithOecWriteLock()
// Session | WithOecSessionLock()
//
// Note that accessing |key_session_| often accesses the OEMCrypto session, so
// WithOecSessionLock() should be used before accessing |key_session_| as
// well.
//
// If a function needs to take a lock on both the Static Field Mutex and some
// of the OEMCrypto mutexes simultaneously, it must *always* lock the Static
// Field Mutex before the OEMCrypto mutexes.
//
// In general, all locks should only be held for the minimum time necessary
// (e.g. a lock on the OEMCrypto mutexes should only be held for the duration
// of a single call into OEMCrypto) unless there is a compelling argument
// otherwise, such as making two calls into OEMCrypto immediately after each
// other.
template <class Func>
static auto WithStaticFieldWriteLock(const char* tag, Func body)
-> decltype(body());
template <class Func>
static auto WithStaticFieldReadLock(const char* tag, Func body)
-> decltype(body());
template <class Func>
static auto WithOecWriteLock(const char* tag, Func body) -> decltype(body());
template <class Func>
static auto WithOecReadLock(const char* tag, Func body) -> decltype(body());
template <class Func>
auto WithOecSessionLock(const char* tag, Func body) -> decltype(body());
private:
// The locking methods above should be used in preference to taking these
// mutexes directly. If code takes these manually and needs to take more
// than one, it must *always* take them in the order they are defined here.
static wvutil::shared_mutex static_field_mutex_;
static wvutil::shared_mutex oem_crypto_mutex_;
std::mutex oem_crypto_session_mutex_;
};
class CryptoInterface {
CryptoInterface();
public:
virtual ~CryptoInterface();
virtual OEMCryptoResult OEMCrypto_OpenSession(OEMCrypto_SESSION* session);
virtual OEMCryptoResult OEMCrypto_CloseSession(OEMCrypto_SESSION session);
virtual OEMCrypto_ProvisioningMethod OEMCrypto_GetProvisioningMethod();
virtual OEMCryptoResult OEMCrypto_GetKeyData(uint8_t* keyData,
size_t* keyDataLength);
virtual uint32_t OEMCrypto_SupportedCertificates();
virtual OEMCryptoResult OEMCrypto_GenerateNonce(OEMCrypto_SESSION session,
uint32_t* nonce);
virtual OEMCryptoResult OEMCrypto_GenerateDerivedKeys(
OEMCrypto_SESSION session, const uint8_t* mac_key_context,
uint32_t mac_key_context_length, const uint8_t* enc_key_context,
uint32_t enc_key_context_length);
virtual OEMCryptoResult OEMCrypto_PrepAndSignLicenseRequest(
OEMCrypto_SESSION session, uint8_t* message, size_t message_length,
size_t* core_message_size, uint8_t* signature, size_t* signature_length);
virtual OEMCryptoResult OEMCrypto_PrepAndSignRenewalRequest(
OEMCrypto_SESSION session, uint8_t* message, size_t message_length,
size_t* core_message_size, uint8_t* signature, size_t* signature_length);
virtual OEMCryptoResult OEMCrypto_PrepAndSignProvisioningRequest(
OEMCrypto_SESSION session, uint8_t* message, size_t message_length,
size_t* core_message_size, uint8_t* signature, size_t* signature_length);
virtual OEMCryptoResult OEMCrypto_LoadProvisioning(
OEMCrypto_SESSION session, const uint8_t* message, size_t message_length,
size_t core_message_length, const uint8_t* signature,
size_t signature_length, uint8_t* wrapped_private_key,
size_t* wrapped_private_key_length);
virtual OEMCryptoResult OEMCrypto_GetOEMPublicCertificate(
OEMCrypto_SESSION session, uint8_t* public_cert,
size_t* public_cert_length);
virtual OEMCryptoResult OEMCrypto_LoadDRMPrivateKey(
OEMCrypto_SESSION session, OEMCrypto_PrivateKeyType key_type,
const uint8_t* wrapped_rsa_key, size_t wrapped_rsa_key_length);
virtual OEMCryptoResult OEMCrypto_GenerateRSASignature(
OEMCrypto_SESSION session, const uint8_t* message, size_t message_length,
uint8_t* signature, size_t* signature_length,
RSA_Padding_Scheme padding_scheme);
virtual OEMCryptoResult OEMCrypto_DeriveKeysFromSessionKey(
OEMCrypto_SESSION session, const uint8_t* enc_session_key,
size_t enc_session_key_length, const uint8_t* mac_key_context,
size_t mac_key_context_length, const uint8_t* enc_key_context,
size_t enc_key_context_length);
virtual OEMCryptoResult OEMCrypto_LoadLicense(OEMCrypto_SESSION session,
const uint8_t* message,
size_t message_length,
size_t core_message_length,
const uint8_t* signature,
size_t signature_length);
virtual OEMCryptoResult OEMCrypto_LoadRenewal(OEMCrypto_SESSION session,
const uint8_t* message,
size_t message_length,
size_t core_message_length,
const uint8_t* signature,
size_t signature_length);
virtual OEMCryptoResult OEMCrypto_LoadCasECMKeys(
OEMCrypto_SESSION session, const uint8_t* message, size_t message_length,
const OEMCrypto_EntitledContentKeyObject* even_key,
const OEMCrypto_EntitledContentKeyObject* odd_key);
virtual OEMCryptoResult OEMCrypto_GetHDCPCapability(
OEMCrypto_HDCP_Capability* current, OEMCrypto_HDCP_Capability* max);
virtual OEMCryptoResult OEMCrypto_GetDeviceID(uint8_t* deviceID,
size_t* idLength);
virtual const char* OEMCrypto_SecurityLevel();
virtual OEMCryptoResult OEMCrypto_CreateEntitledKeySession(
OEMCrypto_SESSION session, OEMCrypto_SESSION* entitled_key_session_id);
virtual OEMCryptoResult OEMCrypto_RemoveEntitledKeySession(
OEMCrypto_SESSION entitled_key_session_id);
virtual OEMCryptoResult OEMCrypto_ReassociateEntitledKeySession(
OEMCrypto_SESSION key_sid, OEMCrypto_SESSION oec_sid);
virtual uint32_t OEMCrypto_APIVersion();
virtual OEMCryptoResult OEMCrypto_GetOEMKeyToken(
OEMCrypto_SESSION key_session, uint8_t* key_token,
size_t* key_token_length);
virtual OEMCryptoResult OEMCrypto_GetSignatureHashAlgorithm(
OEMCrypto_SESSION session, OEMCrypto_SignatureHashAlgorithm* algorithm);
// This is the factory method used to enable the oemcrypto interface.
static OEMCryptoResult create(std::unique_ptr<CryptoInterface>* init) {
// This is *the* oemcrypto interface used in the normal running. There is
// only one and there is not need to destroy it.
static OEMCryptoInterface oemcrypto_interface;
return create_internal(&oemcrypto_interface, init);
}
// This initializer factory method is templated to allow tests to pass in
// a mocked OEMCryptoInterface. The caller retains ownership of
// |oemcrypto_interface|.
template <typename CI>
static OEMCryptoResult create(std::unique_ptr<CryptoInterface>* init,
CI* oemcrypto_interface) {
return create_internal(oemcrypto_interface, init);
}
CryptoInterface(const CryptoInterface&) = delete;
CryptoInterface& operator=(const CryptoInterface&) = delete;
private:
static OEMCryptoResult create_internal(
OEMCryptoInterface* oemcrypto_interface,
std::unique_ptr<CryptoInterface>* init);
static bool initialized_;
static int session_count_;
static std::unique_ptr<CryptoLock> lock_;
OEMCryptoInterface* oemcrypto_interface_;
};
class SupportedCertificates {
public:
explicit SupportedCertificates(uint32_t supported) : supported_(supported) {}
bool rsa_2048bit() { return OEMCrypto_Supports_RSA_2048bit & supported_; }
bool rsa_3072bit() { return OEMCrypto_Supports_RSA_3072bit & supported_; }
bool rsa_CASTbit() { return OEMCrypto_Supports_RSA_CAST & supported_; }
private:
uint32_t supported_;
};
// CryptoSession implements the core methods need to interface with OEMCrypto.
class CryptoSession {
public:
explicit CryptoSession();
virtual ~CryptoSession();
virtual CasStatus initialize();
virtual CasStatus reset();
virtual CasStatus close();
virtual CasProvisioningMethod provisioning_method();
virtual CasStatus GetKeyData(uint8_t* keyData, size_t* keyDataLength);
virtual SupportedCertificates supported_certificates();
virtual CasStatus GenerateNonce(uint32_t* nonce);
virtual CasStatus GenerateDerivedKeys(const uint8_t* mac_key_context,
uint32_t mac_key_context_length,
const uint8_t* enc_key_context,
uint32_t enc_key_context_length);
virtual CasStatus PrepareAndSignLicenseRequest(
const std::string& message, std::string* core_message,
std::string* signature, bool& should_specify_algorithm,
OEMCrypto_SignatureHashAlgorithm& algorithm);
virtual CasStatus PrepareAndSignRenewalRequest(const std::string& message,
std::string* core_message,
std::string* signature);
virtual CasStatus PrepareAndSignProvisioningRequest(
const std::string& message, std::string* core_message,
std::string* signature, bool& should_specify_algorithm,
OEMCrypto_SignatureHashAlgorithm& algorithm);
virtual CasStatus LoadProvisioning(const std::string& signed_message,
const std::string& core_message,
const std::string& signature,
std::string* wrapped_private_key);
virtual CasStatus GetOEMPublicCertificate(uint8_t* public_cert,
size_t* public_cert_length);
virtual CasStatus LoadDeviceRSAKey(const uint8_t* wrapped_rsa_key,
size_t wrapped_rsa_key_length);
virtual CasStatus GenerateRSASignature(const uint8_t* message,
size_t message_length,
uint8_t* signature,
size_t* signature_length,
RSA_Padding_Scheme padding_scheme);
virtual CasStatus DeriveKeysFromSessionKey(const uint8_t* enc_session_key,
size_t enc_session_key_length,
const uint8_t* mac_key_context,
size_t mac_key_context_length,
const uint8_t* enc_key_context,
size_t enc_key_context_length);
virtual CasStatus LoadLicense(const std::string& signed_message,
const std::string& core_message,
const std::string& signature);
virtual CasStatus LoadRenewal(const std::string& signed_message,
const std::string& core_message,
const std::string& signature);
// LoadCasECMKeys loads the ecm keys into the crypto library making them
// available for use.
// |odd_key| - if not null, contains control word data.
// |even_key| - if not null, contains control word data.
virtual CasStatus LoadCasECMKeys(OEMCrypto_SESSION session,
const KeySlot* even_key,
const KeySlot* odd_key);
virtual bool GetHdcpCapabilities(HdcpCapability* current,
HdcpCapability* max);
virtual CasStatus GetDeviceID(std::string* buffer);
virtual const char* SecurityLevel();
virtual CasStatus CreateEntitledKeySession(
OEMCrypto_SESSION* entitled_key_session_id);
virtual CasStatus RemoveEntitledKeySession(
OEMCrypto_SESSION entitled_key_session_id);
virtual CasStatus ReassociateEntitledKeySession(
OEMCrypto_SESSION entitled_key_session_id);
virtual CasStatus APIVersion(uint32_t* api_version);
virtual CasStatus GetOEMKeyToken(OEMCrypto_SESSION entitled_key_session_id,
std::vector<uint8_t>& token);
CryptoSession(const CryptoSession&) = delete;
CryptoSession& operator=(const CryptoSession&) = delete;
private:
virtual OEMCryptoResult getCryptoInterface(
std::unique_ptr<CryptoInterface>* interface);
// TODO(jfore, widevine-eng): Merge CryptoInterface into CryptoSession and
// drop this shared pointer.
std::unique_ptr<CryptoInterface> crypto_interface_;
OEMCrypto_SESSION session_;
};
} // namespace wvcas
#endif // CRYPTO_SESSION_H

View File

@@ -0,0 +1,59 @@
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine Master
// License Agreement.
#ifndef ECM_PARSER_H
#define ECM_PARSER_H
#include <memory>
#include <vector>
#include "cas_types.h"
#include "media_cas.pb.h"
namespace wvcas {
enum class KeySlotId { kEvenKeySlot, kOddKeySlot };
// EcmParser allows random access to the fields of an ECM.
class EcmParser {
public:
EcmParser() = default;
virtual ~EcmParser() {}
// The EcmParser factory method.
// Validates the ecm. If validations is successful returns true and constructs
// an EcmParser in |parser| using |ecm|.
static std::unique_ptr<EcmParser> Create(const CasEcm& ecm);
// Accessor methods.
virtual uint8_t version() const = 0;
virtual CryptoMode crypto_mode() const = 0;
virtual bool rotation_enabled() const = 0;
virtual size_t content_iv_size() const = 0;
virtual uint8_t age_restriction() const = 0;
virtual std::vector<uint8_t> entitlement_key_id(KeySlotId id) const = 0;
virtual std::vector<uint8_t> content_key_id(KeySlotId id) const = 0;
virtual std::vector<uint8_t> wrapped_key_data(KeySlotId id) const = 0;
virtual std::vector<uint8_t> wrapped_key_iv(KeySlotId id) const = 0;
virtual std::vector<uint8_t> content_iv(KeySlotId id) const = 0;
// Process group content keys instead of the normal content keys.
virtual bool set_group_id(const std::string& group_id) = 0;
virtual bool has_fingerprinting() const = 0;
virtual video_widevine::Fingerprinting fingerprinting() const = 0;
virtual bool has_service_blocking() const = 0;
virtual video_widevine::ServiceBlocking service_blocking() const = 0;
// The serialized payload that the signature is calculated on.
virtual std::string ecm_serialized_payload() const = 0;
virtual std::string signature() const = 0;
virtual bool is_entitlement_rotation_enabled() const = 0;
virtual uint32_t entitlement_period_index() const = 0;
virtual uint32_t entitlement_rotation_window_left() const = 0;
};
} // namespace wvcas
#endif // ECM_PARSER_H

View File

@@ -0,0 +1,85 @@
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine Master
// License Agreement.
#ifndef ECM_PARSER_V2_H
#define ECM_PARSER_V2_H
#include <memory>
#include <vector>
#include "cas_types.h"
#include "ecm_parser.h"
namespace wvcas {
struct EcmKeyData;
// EcmParserV2 allows random access to the fields of an ECM version 2 and under.
// It should be initialized via EcmParser factory create only.
class EcmParserV2 : public EcmParser {
public:
~EcmParserV2() override = default;
EcmParserV2(const EcmParserV2&) = delete;
EcmParserV2& operator=(const EcmParserV2&) = delete;
// The EcmParserV2 factory method.
// |ecm| must be Widevine ECM v2 or under without section header.
// Validates the ecm. The only validation performed is to ensure that the ecm
// passed in is large enough to hold a single key entry. If validations is
// successful returns true and constructs an EcmParserV2 in |parser| using
// |ecm|.
static bool create(const CasEcm& cas_ecm,
std::unique_ptr<EcmParserV2>* parser);
// Accessor methods.
uint8_t version() const override;
CryptoMode crypto_mode() const override;
bool rotation_enabled() const override;
size_t content_iv_size() const override;
uint8_t age_restriction() const override;
std::vector<uint8_t> entitlement_key_id(KeySlotId id) const override;
std::vector<uint8_t> content_key_id(KeySlotId id) const override;
std::vector<uint8_t> wrapped_key_data(KeySlotId id) const override;
std::vector<uint8_t> wrapped_key_iv(KeySlotId id) const override;
std::vector<uint8_t> content_iv(KeySlotId id) const override;
// Group keys not supported in v2.
bool set_group_id(const std::string& group_id) override {
return group_id.empty();
};
// ECM v2 or under does not have these fields.
bool has_fingerprinting() const override { return false; }
video_widevine::Fingerprinting fingerprinting() const override {
video_widevine::Fingerprinting fingerprinting;
return fingerprinting;
}
bool has_service_blocking() const override { return false; };
video_widevine::ServiceBlocking service_blocking() const override {
video_widevine::ServiceBlocking service_blocking;
return service_blocking;
}
std::string ecm_serialized_payload() const override { return ""; }
std::string signature() const override { return ""; }
bool is_entitlement_rotation_enabled() const override { return false; }
uint32_t entitlement_period_index() const override { return 0; }
uint32_t entitlement_rotation_window_left() const override { return 0; }
private:
// Constructs an EcmParserV2 using |ecm|.
explicit EcmParserV2(const CasEcm& ecm);
size_t key_data_size() const;
// Returns false if the ecm used to construct the object is not a valid size.
// TODO(jfore): Add validation using the version field.
bool is_valid_size() const;
const EcmKeyData* key_slot_data(KeySlotId id) const;
CasEcm ecm_;
};
} // namespace wvcas
#endif // ECM_PARSER_V2_H

View File

@@ -0,0 +1,68 @@
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine Master
// License Agreement.
#ifndef ECM_PARSER_V3_H
#define ECM_PARSER_V3_H
#include <memory>
#include <vector>
#include "cas_types.h"
#include "ecm_parser.h"
#include "media_cas.pb.h"
namespace wvcas {
// EcmParser allows random access to the fields of an ECM.
class EcmParserV3 : public EcmParser {
public:
~EcmParserV3() override = default;
EcmParserV3(const EcmParserV3&) = delete;
EcmParserV3& operator=(const EcmParserV3&) = delete;
// The EcmParserV3 factory method.
// |ecm| must be Widevine ECM v3 (or higher if compatible) without section
// header. Validates the ecm. If validations is successful returns an
// EcmParserV3, otherwise an nullptr.
static std::unique_ptr<EcmParserV3> Create(const CasEcm& ecm);
// Accessor methods.
uint8_t version() const override;
CryptoMode crypto_mode() const override;
bool rotation_enabled() const override;
size_t content_iv_size() const override;
uint8_t age_restriction() const override;
std::vector<uint8_t> entitlement_key_id(KeySlotId id) const override;
std::vector<uint8_t> content_key_id(KeySlotId id) const override;
std::vector<uint8_t> wrapped_key_data(KeySlotId id) const override;
std::vector<uint8_t> wrapped_key_iv(KeySlotId id) const override;
std::vector<uint8_t> content_iv(KeySlotId id) const override;
bool set_group_id(const std::string& group_id) override;
bool has_fingerprinting() const override;
video_widevine::Fingerprinting fingerprinting() const override;
bool has_service_blocking() const override;
video_widevine::ServiceBlocking service_blocking() const override;
// The serialized payload that the signature is calculated on.
std::string ecm_serialized_payload() const override;
std::string signature() const override;
bool is_entitlement_rotation_enabled() const override;
uint32_t entitlement_period_index() const override;
uint32_t entitlement_rotation_window_left() const override;
private:
// Constructs an EcmParserV3 using |ecm|.
EcmParserV3(video_widevine::SignedEcmPayload signed_ecm_payload,
video_widevine::EcmPayload ecm_payload);
video_widevine::SignedEcmPayload signed_ecm_payload_;
video_widevine::EcmPayload ecm_payload_;
video_widevine::EcmKeyData even_key_data_;
video_widevine::EcmKeyData odd_key_data_;
};
} // namespace wvcas
#endif // ECM_PARSER_V3_H

View File

@@ -0,0 +1,49 @@
// Copyright 2020 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine Master
// License Agreement.
#ifndef EMM_PARSER_H
#define EMM_PARSER_H
#include <memory>
#include <vector>
#include "cas_types.h"
#include "media_cas.pb.h"
namespace wvcas {
using video_widevine::EmmPayload;
class EmmParser {
public:
EmmParser(const EmmParser&) = delete;
EmmParser& operator=(const EmmParser&) = delete;
virtual ~EmmParser() = default;
// The EmmParser factory method.
// The methods validates the passed in |emm|. If validation is successful, it
// constructs and returns an EmmParser. Otherwise, nullptr is returned.
static std::unique_ptr<const EmmParser> Create(const CasEmm& emm);
// Accessor methods.
virtual uint64_t timestamp() const { return timestamp_; }
virtual std::string signature() const { return signature_; }
virtual EmmPayload emm_payload() const { return emm_payload_; };
protected:
// Called by the factory create and unit test.
EmmParser() = default;
private:
bool Parse(int start_index, const CasEmm& emm);
uint8_t version_;
uint64_t timestamp_;
EmmPayload emm_payload_;
std::string signature_;
};
} // namespace wvcas
#endif // EMM_PARSER_H

View File

@@ -0,0 +1,210 @@
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine Master
// License Agreement.
#ifndef WIDEVINE_CAS_LICENSE_KEY_STATUS_H_
#define WIDEVINE_CAS_LICENSE_KEY_STATUS_H_
#include <map>
#include "crypto_session.h"
#include "license_protocol.pb.h"
namespace wvcas {
class LicenseKeyStatus;
using video_widevine::WidevinePsshData_EntitledKey;
// Policy engine HDCP enforcement
static constexpr uint32_t HDCP_UNSPECIFIED_VIDEO_RESOLUTION = 0;
static constexpr int64_t HDCP_DEVICE_CHECK_INTERVAL = 10;
enum KeySecurityLevel {
kKeySecurityLevelUnset,
kSoftwareSecureCrypto,
kSoftwareSecureDecode,
kHardwareSecureCrypto,
kHardwareSecureDecode,
kHardwareSecureAll,
kKeySecurityLevelUnknown,
};
class KeyAllowedUsage {
public:
KeyAllowedUsage() { Clear(); }
bool Valid() const { return valid_; }
void SetValid() { valid_ = true; }
void Clear() {
decrypt_to_clear_buffer = false;
decrypt_to_secure_buffer = false;
generic_encrypt = false;
generic_decrypt = false;
generic_sign = false;
generic_verify = false;
key_security_level_ = kKeySecurityLevelUnset;
valid_ = false;
}
bool Equals(const KeyAllowedUsage& other) {
if (!valid_ || !other.Valid() ||
decrypt_to_clear_buffer != other.decrypt_to_clear_buffer ||
decrypt_to_secure_buffer != other.decrypt_to_secure_buffer ||
generic_encrypt != other.generic_encrypt ||
generic_decrypt != other.generic_decrypt ||
generic_sign != other.generic_sign ||
generic_verify != other.generic_verify ||
key_security_level_ != other.key_security_level_) {
return false;
}
return true;
}
bool decrypt_to_clear_buffer;
bool decrypt_to_secure_buffer;
bool generic_encrypt;
bool generic_decrypt;
bool generic_sign;
bool generic_verify;
KeySecurityLevel key_security_level_;
private:
bool valid_;
};
// Holds all content and operator session keys for a session.
class LicenseKeys {
public:
LicenseKeys() {}
virtual ~LicenseKeys() { Clear(); }
virtual bool Empty() { return key_statuses_.empty(); }
// Returns true if the key is a content key (not an operator session key)
virtual bool IsContentKey(const KeyId& key_id);
// Returns true if the key is currently usable for content decryption.
virtual bool CanDecryptContent(const KeyId& key_id);
// Returns the allowed usages for a key.
virtual bool GetAllowedUsage(const KeyId& key_id,
KeyAllowedUsage* allowed_usage);
// Applies a new status to each content key.
// Returns true if any statuses changed, and sets new_usable_keys to
// true if the status changes resulted in keys becoming usable.
virtual bool ApplyStatusChange(KeyStatus new_status, bool* new_usable_keys);
// Returns current KeyStatus for the given key.
// Returns kKeyStatusKeyUnknown if key_id not found.
virtual KeyStatus GetKeyStatus(const KeyId& key_id);
// Populates a KeyStatusMap with the current content keys.
virtual void ExtractKeyStatuses(KeyStatusMap* content_keys);
// Determines whether the specified key can be used under the current
// resolution and/or hdcp constraints. If no constraints have been applied
// to the key, returns true.
virtual bool MeetsConstraints(const KeyId& key_id);
// Applies a resolution and/or hdcp change to each key, updating their
// useability under their constraints.
virtual void ApplyConstraints(uint32_t new_resolution,
HdcpCapability new_hdcp_level);
// Extracts the keys from a license and makes them available for
// querying usage and constraint settings.
virtual void SetFromLicense(const video_widevine::License& license);
// Sets the keys from the input entitled key data.
virtual void SetEntitledKeys(
const std::vector<WidevinePsshData_EntitledKey>& keys);
LicenseKeys(const LicenseKeys&) = delete;
LicenseKeys& operator=(const LicenseKeys&) = delete;
private:
typedef ::video_widevine::License::KeyContainer KeyContainer;
typedef std::map<KeyId, LicenseKeyStatus*>::const_iterator
LicenseKeyStatusIterator;
void Clear();
bool is_initialized_;
// |key_statuses_| can hold either content key statuses, or entitlement key
// statuses.
std::map<KeyId, LicenseKeyStatus*> key_statuses_;
// |content_keyid_to_entitlement_key_id_| maps a content key id to an
// entitlement_key_id. The resulting key id can be used to obtain the current
// key status from |key_statuses_| when using entitlement key licensing.
std::map<KeyId, KeyId> content_keyid_to_entitlement_key_id_;
};
// Holds the current license status of a key.
class LicenseKeyStatus {
friend class LicenseKeys;
public:
// Returns true if the key is a content key (not an operator session key)
virtual bool IsContentKey() { return is_content_key_; }
// Returns true if the key is currently usable for content decryption
virtual bool CanDecryptContent();
// Returns the usages allowed for this key.
virtual bool GetAllowedUsage(KeyAllowedUsage* allowed_usage);
// Returns the current status of the key.
virtual KeyStatus GetKeyStatus() const { return key_status_; }
// Applies a new status to this key.
// Returns true if the status changed, and sets new_usable_keys to
// true if the status changes resulted in the key becoming usable.
virtual bool ApplyStatusChange(KeyStatus new_status, bool* new_usable_keys);
// Returns the current constraint status of this key. The result
// may change due to calls to ApplyConstraints().
// Note: this will return true until the first call to ApplyConstraints().
virtual bool MeetsConstraints() const { return meets_constraints_; }
// Applies the given changes in resolution or HDCP settings.
virtual void ApplyConstraints(uint32_t new_resolution,
HdcpCapability new_hdcp_level);
LicenseKeyStatus(const LicenseKeyStatus&) = delete;
LicenseKeyStatus& operator=(const LicenseKeyStatus&) = delete;
protected:
typedef ::video_widevine::License::KeyContainer KeyContainer;
typedef KeyContainer::OperatorSessionKeyPermissions
OperatorSessionKeyPermissions;
typedef KeyContainer::OutputProtection OutputProtection;
typedef KeyContainer::VideoResolutionConstraint VideoResolutionConstraint;
typedef ::google::protobuf::RepeatedPtrField<VideoResolutionConstraint>
ConstraintList;
LicenseKeyStatus(const KeyContainer& key);
virtual ~LicenseKeyStatus() {}
private:
void ParseContentKey(const KeyContainer& key);
void ParseOperatorSessionKey(const KeyContainer& key);
bool HasConstraints() { return is_content_key_ && constraints_.size() != 0; }
void SetConstraints(const ConstraintList& constraints);
bool is_content_key_;
KeyStatus key_status_;
bool meets_constraints_;
KeyAllowedUsage allowed_usage_;
HdcpCapability default_hdcp_level_;
ConstraintList constraints_;
};
} // namespace wvcas
#endif // WIDEVINE_CAS_LICENSE_KEY_STATUS_H_

View File

@@ -0,0 +1,138 @@
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine Master
// License Agreement.
#ifndef OEMCRYPTO_INTERFACE_H
#define OEMCRYPTO_INTERFACE_H
#include <memory>
#include <string>
#include <vector>
#include "OEMCryptoCENC.h"
namespace wvcas {
// InputStreamParams mirrors the parameters in OEMCrypto API. The
// purpose is to allow OEMCrypto_Descramble to be mocked. OEMCrypto_Descramble
// takes 11 parameters as of API V15. GoogleMock allows a maximum of 10.
struct InputStreamParams {
const uint8_t* data_addr;
size_t data_length;
bool is_encrypted;
InputStreamParams(){};
InputStreamParams(const uint8_t* data_addr, size_t data_length,
bool is_encrypted)
: data_addr(data_addr),
data_length(data_length),
is_encrypted(is_encrypted) {}
};
// Calls to oemcrypto are called via this object. The purpose of this object is
// to allow OEMCrypto to be mocked. The implementation of this object only wraps
// OEMCrypto methods adding limited additional functionality. Added
// functionality is limited to adapt the input parameters to the oemcrypto api.
// Method signatures in this class can only have a maximum of 10 parameters to
// maintain compatibility with googlemock.
class OEMCryptoInterface {
public:
OEMCryptoInterface();
virtual ~OEMCryptoInterface();
virtual OEMCryptoResult OEMCrypto_Initialize(void);
virtual OEMCryptoResult OEMCrypto_Terminate(void);
virtual OEMCryptoResult OEMCrypto_OpenSession(
OEMCrypto_SESSION* session) const;
virtual OEMCryptoResult OEMCrypto_CloseSession(
OEMCrypto_SESSION session) const;
virtual OEMCrypto_ProvisioningMethod OEMCrypto_GetProvisioningMethod() const;
virtual OEMCryptoResult OEMCrypto_GetKeyData(uint8_t* keyData,
size_t* keyDataLength) const;
virtual uint32_t OEMCrypto_SupportedCertificates() const;
virtual OEMCryptoResult OEMCrypto_GenerateNonce(OEMCrypto_SESSION session,
uint32_t* nonce) const;
virtual OEMCryptoResult OEMCrypto_GenerateDerivedKeys(
OEMCrypto_SESSION session, const uint8_t* mac_key_context,
uint32_t mac_key_context_length, const uint8_t* enc_key_context,
uint32_t enc_key_context_length) const;
virtual OEMCryptoResult OEMCrypto_PrepAndSignLicenseRequest(
OEMCrypto_SESSION session, uint8_t* message, size_t message_length,
size_t* core_message_size, uint8_t* signature,
size_t* signature_length) const;
virtual OEMCryptoResult OEMCrypto_PrepAndSignRenewalRequest(
OEMCrypto_SESSION session, uint8_t* message, size_t message_length,
size_t* core_message_size, uint8_t* signature,
size_t* signature_length) const;
virtual OEMCryptoResult OEMCrypto_PrepAndSignProvisioningRequest(
OEMCrypto_SESSION session, uint8_t* message, size_t message_length,
size_t* core_message_size, uint8_t* signature,
size_t* signature_length) const;
virtual OEMCryptoResult OEMCrypto_LoadProvisioning(
OEMCrypto_SESSION session, const uint8_t* message, size_t message_length,
size_t core_message_length, const uint8_t* signature,
size_t signature_length, uint8_t* wrapped_private_key,
size_t* wrapped_private_key_length) const;
virtual OEMCryptoResult OEMCrypto_GetOEMPublicCertificate(
OEMCrypto_SESSION session, uint8_t* public_cert,
size_t* public_cert_length) const;
virtual OEMCryptoResult OEMCrypto_LoadDRMPrivateKey(
OEMCrypto_SESSION session, OEMCrypto_PrivateKeyType key_type,
const uint8_t* wrapped_rsa_key, size_t wrapped_rsa_key_length) const;
virtual OEMCryptoResult OEMCrypto_GenerateRSASignature(
OEMCrypto_SESSION session, const uint8_t* message, size_t message_length,
uint8_t* signature, size_t* signature_length,
RSA_Padding_Scheme padding_scheme) const;
virtual OEMCryptoResult OEMCrypto_DeriveKeysFromSessionKey(
OEMCrypto_SESSION session, const uint8_t* enc_session_key,
size_t enc_session_key_length, const uint8_t* mac_key_context,
size_t mac_key_context_length, const uint8_t* enc_key_context,
size_t enc_key_context_length) const;
virtual OEMCryptoResult OEMCrypto_LoadLicense(OEMCrypto_SESSION session,
const uint8_t* message,
size_t message_length,
size_t core_message_length,
const uint8_t* signature,
size_t signature_length) const;
virtual OEMCryptoResult OEMCrypto_LoadRenewal(OEMCrypto_SESSION session,
const uint8_t* message,
size_t message_length,
size_t core_message_length,
const uint8_t* signature,
size_t signature_length) const;
virtual OEMCryptoResult OEMCrypto_LoadCasECMKeys(
OEMCrypto_SESSION session, const uint8_t* message, size_t message_length,
const OEMCrypto_EntitledContentKeyObject* even_key,
const OEMCrypto_EntitledContentKeyObject* odd_key) const;
virtual OEMCryptoResult OEMCrypto_GetHDCPCapability(
OEMCrypto_HDCP_Capability* current, OEMCrypto_HDCP_Capability* max) const;
virtual OEMCryptoResult OEMCrypto_GetDeviceID(uint8_t* deviceID,
size_t* idLength) const;
virtual OEMCryptoResult OEMCrypto_LoadTestKeybox(const uint8_t* buffer,
size_t length) const;
virtual const char* OEMCrypto_SecurityLevel() const;
virtual OEMCryptoResult OEMCrypto_CreateEntitledKeySession(
OEMCrypto_SESSION oec_session, OEMCrypto_SESSION* key_session) const;
virtual OEMCryptoResult OEMCrypto_RemoveEntitledKeySession(
OEMCrypto_SESSION key_session) const;
virtual OEMCryptoResult OEMCrypto_ReassociateEntitledKeySession(
OEMCrypto_SESSION key_session, OEMCrypto_SESSION oec_session) const;
virtual uint32_t OEMCrypto_APIVersion() const;
virtual OEMCryptoResult OEMCrypto_GetOEMKeyToken(
OEMCrypto_SESSION key_session, uint8_t* key_token,
size_t* key_token_length) const;
virtual OEMCryptoResult OEMCrypto_GetSignatureHashAlgorithm(
OEMCrypto_SESSION session,
OEMCrypto_SignatureHashAlgorithm* algorithm) const;
OEMCryptoInterface(const OEMCryptoInterface&) = delete;
OEMCryptoInterface& operator=(const OEMCryptoInterface&) = delete;
private:
class Impl;
std::unique_ptr<Impl> impl_;
};
} // namespace wvcas
#endif // OEMCRYPTO_INTERFACE_H

View File

@@ -0,0 +1,214 @@
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine Master
// License Agreement.
#ifndef WIDEVINE_CAS_POLICY_ENGINE_H_
#define WIDEVINE_CAS_POLICY_ENGINE_H_
#include <map>
#include <memory>
#include <string>
#include "cas_util.h"
#include "clock.h"
#include "crypto_session.h"
#include "license_key_status.h"
#include "license_protocol.pb.h"
#include "timer.h"
namespace wvcas {
// This acts as an oracle that basically says "Yes(true) you may still decrypt
// or no(false) you may not decrypt this data anymore."
// TODO(jfore): Just pass in a function object? OnTimerEvent can be a
class PolicyEngine : public wvutil::TimerHandler {
typedef enum {
kLicenseStateInitial,
kLicenseStatePending, // if license is issued for sometime in the future
kLicenseStateCanPlay,
kLicenseStateNeedRenewal,
kLicenseStateWaitingLicenseUpdate,
kLicenseStateExpired
} LicenseState;
public:
// The default constructor.
PolicyEngine() {}
virtual ~PolicyEngine() {}
virtual void initialize(std::shared_ptr<CryptoSession> crypto_session,
CasEventListener* event_listener) {
license_keys_ = CreateLicenseKeys();
clock_ = CreateClock();
event_listener_ = event_listener;
crypto_session_ = crypto_session;
}
// The value returned should be taken as a hint rather than an absolute
// status. It is computed during the last call to either SetLicense/
// UpdateLicense/OnTimerEvent/BeginDecryption and may be out of sync
// depending on the amount of time elapsed. The current decryption
// status is not calculated to avoid overhead in the decryption path.
virtual bool CanDecryptContent(const KeyId& key_id) const;
// The value returned indicates if a license renewal is allowed under the
// current policy.
virtual bool CanRenew() const { return policy_.can_renew(); }
// The value returned indicates if the license is persisted to non-volatile
// storage for offline use.
virtual bool CanPersist() const { return policy_.can_persist(); }
// The value returned indicates whether or not the client id should be
// included in renewal requests.
virtual bool always_include_client_id() const {
return policy_.always_include_client_id();
}
// The value returned is the current license id.
virtual const video_widevine::LicenseIdentification& license_id() const {
return license_id_;
}
virtual const std::string& renewal_server_url() const {
return policy_.renewal_server_url();
}
// SetLicense is used in handling the initial license response. It stores
// an exact copy of the policy information stored in the license.
// The license state transitions to kLicenseStateCanPlay if the license
// permits playback.
virtual void SetLicense(const video_widevine::License& license);
// UpdateLicense is used in handling a license response for a renewal request.
// The response may only contain any policy fields that have changed. In this
// case an exact copy is not what we want to happen. We also will receive an
// updated license_start_time from the server. The license will transition to
// kLicenseStateCanPlay if the license permits playback.
virtual void UpdateLicense(const video_widevine::License& license);
// Call this on first decrypt to set the start of playback.
virtual void BeginDecryption(void);
// OnTimerEvent is called when a timer fires. It notifies the Policy Engine
// that the timer has fired and dispatches the relevant events through
// |event_listener_|.
virtual void OnTimerEvent() override;
// Used to update the currently loaded entitled content keys.
virtual void SetEntitledLicenseKeys(
const std::vector<WidevinePsshData_EntitledKey>& entitled_keys);
// Used to query if the current license state is expired.
virtual bool IsExpired() const {
return license_state_ == kLicenseStateExpired;
}
// for offline save and restore
int64_t GetPlaybackStartTime() const { return playback_start_time_; }
int64_t GetLastPlaybackTime() const { return last_playback_time_; }
int64_t GetGracePeriodEndTime() const { return grace_period_end_time_; }
void RestorePlaybackTimes(int64_t playback_start_time,
int64_t last_playback_time,
int64_t grace_period_end_time);
PolicyEngine(const PolicyEngine&) = delete;
PolicyEngine& operator=(const PolicyEngine&) = delete;
private:
// Notifies updates in keys information and fire OnKeysChange event if
// key changes.
void NotifyKeysChange(KeyStatus new_status);
void NotifyLicenseExpired(LicenseState key_status);
bool HasLicenseOrPlaybackDurationExpired(int64_t current_time);
// Notifies updates in expiry time and fire OnExpirationUpdate event if
// expiry time changes.
void NotifyExpirationUpdate(int64_t current_time);
// Notify listeners of the current renewal url.
void NotifyRenewalServerUpdate();
// Guard against clock rollbacks
int64_t GetCurrentTime();
LicenseState license_state_ = kLicenseStateInitial;
void CheckDeviceHdcpStatus();
// Gets the clock time that the license expires based on whether we have
// started playing. This takes into account GetHardLicenseExpiryTime.
int64_t GetExpiryTime(int64_t current_time,
bool ignore_soft_enforce_playback_duration);
void CheckDeviceHdcpStatusOnTimer(int64_t current_time);
bool HasPlaybackStarted(int64_t current_time) {
if (playback_start_time_ == 0) return false;
const int64_t playback_time = current_time - playback_start_time_;
return playback_time >= policy_.play_start_grace_period_seconds();
}
// Gets the clock time that the rental duration will expire, using the license
// duration if one is not present.
int64_t GetRentalExpiryTime();
// Gets the clock time that the license expires. This is the hard limit that
// all license types must obey at all times.
int64_t GetHardLicenseExpiryTime();
bool HasRenewalDelayExpired(int64_t current_time);
bool HasRenewalRetryIntervalExpired(int64_t current_time);
void UpdateRenewalRequest(int64_t current_time);
virtual std::unique_ptr<wvcas::LicenseKeys> CreateLicenseKeys();
virtual std::unique_ptr<wvutil::Clock> CreateClock();
// This is the current policy information for this license. This gets updated
// as license renewals occur.
video_widevine::License::Policy policy_;
// This is the license id field from server response. This data gets passed
// back to the server in each renewal request. When we get a renewal response
// from the license server we will get an updated id field.
video_widevine::LicenseIdentification license_id_;
// The server returns the license start time in the license/license renewal
// response based off the request time sent by the client in the
// license request/renewal
int64_t license_start_time_ = 0;
int64_t playback_start_time_ = 0;
int64_t last_playback_time_ = 0;
int64_t grace_period_end_time_ = 0;
bool last_expiry_time_set_ = false;
bool was_expired_on_load_ = false;
// This is used as a reference point for policy management. This value
// represents an offset from license_start_time_. This is used to
// calculate the time where renewal retries should occur.
int64_t next_renewal_time_ = 0;
CasEventListener* event_listener_ = nullptr;
// Keys associated with license - holds allowed usage, usage constraints,
// and current status (KeyStatus)
std::unique_ptr<LicenseKeys> license_keys_;
std::unique_ptr<wvutil::Clock> clock_;
// to assist in clock rollback checks
int64_t last_recorded_current_time_ = 0;
int64_t last_expiry_time_ = 0;
int64_t next_device_check_ = 0;
std::shared_ptr<wvcas::CryptoSession> crypto_session_;
uint32_t current_resolution_ = 0;
std::string renewal_server_url_;
};
} // namespace wvcas
#endif // WIDEVINE_CAS_POLICY_ENGINE_H_

View File

@@ -0,0 +1,210 @@
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine Master
// License Agreement.
#ifndef WIDEVINE_CAS_API_H
#define WIDEVINE_CAS_API_H
#include <memory>
#include <mutex>
#include <set>
#include "cas_license.h"
#include "cas_media_id.h"
#include "cas_status.h"
#include "cas_types.h"
#include "crypto_session.h"
#include "ecm_parser.h"
#include "emm_parser.h"
#include "file_store.h"
#include "media_cas.pb.h"
#include "timer.h"
#include "widevine_cas_session.h"
namespace wvcas {
// TODO(jfore): Fix the function name inconsistency in this class. These
// functions were migrated from the android plugin api implementation. They
// should not follow Android's style.
class WidevineCas : public wvutil::TimerHandler {
public:
WidevineCas() {}
virtual ~WidevineCas() {}
virtual CasStatus initialize(CasEventListener* event_listener);
// Open a session for descrambling a program, or one or more elementary
// streams.
virtual CasStatus openSession(WvCasSessionId* sessionId);
// Close a previously opened session.
virtual CasStatus closeSession(const WvCasSessionId& sessionId);
// Process an EMM which may contain fingerprinting and service blocking info.
virtual CasStatus processEmm(const CasEmm& emm);
// Process an ECM from the ECM stream for this sessions elementary
// stream.
virtual CasStatus processEcm(const WvCasSessionId& sessionId,
const CasEcm& ecm);
// Generates a device provisioning request message in |provisioning_request|.
virtual CasStatus generateDeviceProvisioningRequest(
std::string* provisioning_request);
// Processes a |response| to provisioning request.
virtual CasStatus handleProvisioningResponse(const std::string& response);
// Generates an entitlement license request in |entitlement_request| for the
// media described in |init_data|.
virtual CasStatus generateEntitlementRequest(const std::string& init_data,
std::string* entitlement_request,
std::string& license_id);
// Processes the entitlement |response| to a entitlement license request.
// |license_id| is the id of the license installed. Can be used to select
// which license to install.
// |multi_content_license_info| contains the message that can be sent to the
// app if the installed license is a multi content license.
// |group_license_info| contains the message that can be sent to the app if
// the installed license is a group license.
virtual CasStatus handleEntitlementResponse(
const std::string& response, std::string& license_id,
std::string& multi_content_license_info, std::string& group_license_info);
// Generates an entitlement license request in |entitlement_request| for the
// media described in |init_data|.
virtual CasStatus generateEntitlementRenewalRequest(
std::string* entitlement_renewal_request);
// Processes the entitlement renewal |response| to a entitlement license
// request.
virtual CasStatus handleEntitlementRenewalResponse(
const std::string& response, std::string& license_id);
// Generates an entitlement license request in a new crypto session, and send
// the license request as an event to the app.
virtual CasStatus generateEntitlementPeriodUpdateRequest(
const std::string& init_data);
// Processes the license |response| to switch the current license to this
// new one.
virtual CasStatus handleEntitlementPeriodUpdateResponse(
const std::string& response, std::string& license_id);
// Returns true if the device has been provisioned with a device certificate.
virtual bool is_provisioned() const;
// Processes the CAS |private_data| from a CAT table. If successful a
// serialized pssh data is retured in |init_data|.
virtual CasStatus ProcessCAPrivateData(const CasData& private_data,
std::string* init_data);
// Processes the CAS |private_data| from a PMT table. If successful a
// serialized pssh data is retured in |init_data|. The CA private data can be
// unique to the ecm session indicated by |session_id|.
virtual CasStatus ProcessSessionCAPrivateData(
const WvCasSessionId& session_id, const CasData& private_data,
std::string* init_data);
// Returns the device unique identifier.
virtual CasStatus GetUniqueID(std::string* buffer);
// Set the minimum age required to process ECM.
virtual CasStatus HandleSetParentalControlAge(const CasData& data);
// Remove the license file given the filename user provides.
virtual CasStatus RemoveLicense(const std::string& file_name);
// Record the license id that user provides. This license id will be used to
// select license if multiple licenses exist.
virtual CasStatus RecordLicenseId(const std::string& license_id);
void OnTimerEvent() override;
// Stops the timer thread. Called by CAS plugin destructor to avoid race.
void StopTimer();
private:
virtual CasStatus HandleStoredDrmCert(const std::string& certificate);
virtual CasStatus HandleProcessEcm(const WvCasSessionId& sessionId,
const CasEcm& ecm);
virtual CasStatus HandleDeferredECMs();
// Extracts the entitlement rotation period index from ECM if specified, and
// store it. The function should be called before any license request and the
// extracted index will be included in the license request.
virtual void TryExtractEntitlementPeriodIndex(const CasEcm& ecm);
// Returns true if an offline license with |filename| is successfully loaded.
virtual bool TryReuseStoredLicense(const std::string& filename);
// Check if a new license is needed due to entitlement period changes. If so,
// it will call generateEntitlementPeriodUpdateRequest().
void CheckEntitlementPeriodUpdate(uint32_t period_index,
uint32_t window_left);
virtual std::shared_ptr<CryptoSession> getCryptoSession();
virtual std::unique_ptr<CasLicense> getCasLicense();
virtual std::unique_ptr<wvutil::FileSystem> getFileSystem();
virtual std::shared_ptr<WidevineCasSession> newCasSession();
virtual std::unique_ptr<EcmParser> getEcmParser(const CasEcm& ecm) const;
// Creates an EmmParser. Marked as virtual for easier unit test.
virtual std::unique_ptr<const EmmParser> getEmmParser(
const CasEmm& emm) const;
std::vector<uint8_t> GenerateFingerprintingEventMessage(
const video_widevine::Fingerprinting& fingerprinting) const;
std::vector<uint8_t> GenerateServiceBlockingEventMessage(
const video_widevine::ServiceBlocking& service_blocking) const;
// The CryptoSession will be shared by the all cas sessions. It is also needed
// by the cas api to generate EMM requests.
std::shared_ptr<CryptoSession> crypto_session_;
std::unique_ptr<CasLicense> cas_license_;
std::unique_ptr<wvutil::FileSystem> file_system_;
std::string device_certificate_;
std::string wrapped_rsa_key_;
CasEventListener* event_listener_ = nullptr;
std::mutex lock_;
wvutil::Timer policy_timer_;
LicenseType license_type_;
std::unique_ptr<CasMediaId> media_id_;
// Sometimes delays in receiving a license or the format in which the content
// is encoded my result in ecms being processed before a valid license has
// been loaded. In this cas |has_license_| will be false and the ecm will be
// stored in |deferred_ecms_|. Once a license has been loaded, the stored ecms
// are processed to set the current content keys.
std::map<WvCasSessionId, const CasEcm> deferred_ecms_;
// The value |has_license_| will be false when the plugin is created. Once a
// license is loaded, |has_license_| will be set to true.
bool has_license_ = false;
// The age_restriction field in ECM must be greater or equal to
// |parental_control_min_age|. Otherwise, ECM will stop being processed.
uint parental_control_age_ = 0;
// The requested_license_id helps to indicate which license file current
// content will use if multiple licenses exist.
std::string requested_license_id_;
// The current in use license_id.
std::string license_id_;
// The group id of a Group license. Empty if the license is not a Group
// license (multi content license is not a group license). Used in processECM
// to select group keys that can be decrypted by the license.
std::string license_group_id_;
// Fingerprinting events sent in processing last ECM/EMM. Used to avoid
// sending a same event again.
std::set<CasData> last_fingerprinting_events_;
// Service blocking events sent in processing last ECM/EMM. Used to avoid
// sending a same event again.
std::set<CasData> last_service_blocking_events_;
// Indicates if |entitlement_period_index_| below is valid or not.
bool is_entitlement_rotation_enabled_ = false;
// The entitlement period index in the last received ECM.
uint32_t entitlement_period_index_;
// |next_*| used to handle entitlement key rotation. They will be moved to
// normal ones once the license switch completed.
std::shared_ptr<CryptoSession> next_crypto_session_;
std::unique_ptr<CasLicense> next_cas_license_;
std::unique_ptr<CasMediaId> next_media_id_;
}; // namespace wvcas
} // namespace wvcas
#endif // WIDEVINE_CAS_API_H

View File

@@ -0,0 +1,113 @@
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine Master
// License Agreement.
#ifndef WIDEVINE_CAS_SESSION_H
#define WIDEVINE_CAS_SESSION_H
#include <memory>
#include <mutex>
#include <set>
#include <string>
#include "cas_types.h"
#include "crypto_session.h"
#include "ecm_parser.h"
#include "media_cas.pb.h"
namespace wvcas {
class WidevineCasSession;
typedef std::shared_ptr<WidevineCasSession> CasSessionPtr;
class CasKeySlotData {
public:
CasKeySlotData() {}
~CasKeySlotData() {}
KeySlot& operator[](KeySlotId slot_id);
const KeySlot& operator[](KeySlotId slot_id) const;
private:
KeySlot keys_[2]; // Odd and even key slots.
};
enum class ScramblingControl {
kScrambling_Unscrambled = 0,
kScrambling_Reserved = 1,
kScrambling_EvenKey = 2,
kScrambling_OddKey = 3,
};
// WidevineCasSession represents an encryption context for a single ECM key
// stream. It processes ECMs for the stream and maintains the key information.
class WidevineCasSession {
public:
WidevineCasSession() {}
virtual ~WidevineCasSession();
CasStatus initialize(std::shared_ptr<CryptoSession> crypto_session,
CasEventListener* event_listener,
WvCasSessionId* session_id);
CasStatus resetCryptoSession(std::shared_ptr<CryptoSession> crypto_session);
// Process an ecm and extract the key slot data. Extracted data will be used
// to update |current_ecm_| and |entitlement_key_id_| and |keys_|.
// |parental_control_age| (if non-zero) must be greater or equal to the
// age_restriction field specified in |ecm|. Otherwise, ECM will not be
// processed and error will be returned.
// |license_group_id| if non empty, processEcm will decrypt content keys that
// are specified by |license_group_id|.
virtual CasStatus processEcm(const CasEcm& ecm, uint8_t parental_control_age,
const std::string& license_group_id);
// Returns the security level retrieved from OEMCrypto.
const char* securityLevel();
// Returns current ecm age restriction value.
uint8_t GetEcmAgeRestriction() const { return ecm_age_restriction_; }
// Returns the entitlement period index specified in the last received ECM.
uint32_t GetEntitlementPeriodIndex() const {
return entitlement_period_index_;
}
// Returns the entitlement rotation window left value specified in the last
// received ECM.
uint32_t GetEntitlementRotationWindowLeft() const {
return entitlement_rotation_window_left_;
}
WidevineCasSession(const WidevineCasSession&) = delete;
WidevineCasSession& operator=(const WidevineCasSession&) = delete;
private:
// Creates an EcmParser.
virtual std::unique_ptr<EcmParser> getEcmParser(const CasEcm& ecm) const;
CasKeySlotData keys_; // Odd and even key slots.
std::string entitlement_key_id_;
std::mutex crypto_lock_;
CasEcm current_ecm_;
uint8_t ecm_age_restriction_ = 0;
std::shared_ptr<CryptoSession> crypto_session_;
// Id of the entitled key session in OEMCrypto associated with this session.
uint32_t key_session_id_;
// This is the session id returned to the app. It is actually the OEM key
// token.
std::vector<uint8_t> external_key_session_id_;
CasEventListener* event_listener_ = nullptr;
// Fingerprinting events sent in processing last ECM/EMM. Used to avoid
// sending a same event again.
std::vector<uint8_t> last_fingerprinting_message_;
// Service blocking events sent in processing last ECM/EMM. Used to avoid
// sending a same event again.
std::vector<uint8_t> last_service_blocking_message_;
// The entitlement period index in the last received ECM.
uint32_t entitlement_period_index_;
// The entitlement rotation window left in the last received ECM.
uint32_t entitlement_rotation_window_left_;
};
} // namespace wvcas
#endif // WIDEVINE_CAS_SESSION_H

View File

@@ -0,0 +1,54 @@
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine Master
// License Agreement.
#ifndef WIDEVINE_CAS_SESSION_MAP_H
#define WIDEVINE_CAS_SESSION_MAP_H
#include <map>
#include <memory>
#include <mutex>
#include <string>
#include <utility>
#include "cas_types.h"
#include "widevine_cas_session.h"
namespace wvcas {
typedef std::map<WvCasSessionId, CasSessionPtr> CasSessionMap;
// WidevineCasSessionMap is a singleton. It used as a shared resource used by
// both cas and descrambler plugins. Cas sessions are created by the cas plugin,
// and can be *discovered* by descrambler plugins.
class WidevineCasSessionMap {
public:
virtual ~WidevineCasSessionMap() {}
// Adds a new Widevine cas session to the map.
// Returns true if the session is successfully added to the map, false
// otherwise.
bool AddSession(const WvCasSessionId& cas_session_id, CasSessionPtr session);
// Obtain a shared pointer to a cas session. If the session does not exist in
// the map, the returned pointer == nullptr.
CasSessionPtr GetSession(const WvCasSessionId& cas_session_id) const;
// Remove an entry in the map.
void RemoveSession(const WvCasSessionId& cas_session_id);
// Retrieves all the session ids.
std::vector<CasSessionPtr> GetAllSessions() const;
// Returns a reference to the map.
static WidevineCasSessionMap& instance();
WidevineCasSessionMap(const WidevineCasSessionMap&) = delete;
WidevineCasSessionMap& operator=(const WidevineCasSessionMap&) = delete;
private:
WidevineCasSessionMap() {}
CasSessionMap map_;
mutable std::mutex lock_;
};
} // namespace wvcas
#endif // WIDEVINE_CAS_SESSION_MAP_H

View File

@@ -0,0 +1,59 @@
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine Master
// License Agreement.
#ifndef WIDEVINE_MEDIA_CAS_H
#define WIDEVINE_MEDIA_CAS_H
#include "media/cas/CasAPI.h"
using android::CasFactory;
using android::CasPlugin;
using android::CasPluginCallback;
using android::CasPluginCallbackExt;
using android::CasPluginDescriptor;
using android::status_t;
using android::String8;
extern "C" {
android::CasFactory* createCasFactory();
}
namespace wvcas {
// WidevineCasFactory implements the android::CasFactory interface.
class WidevineCasFactory : public CasFactory {
public:
// Implements android::CasFactory* createCasFactory(). This method is used
// by the MediaCas framework to construct our factory.
static WidevineCasFactory* createCasFactory();
~WidevineCasFactory() override {}
// Test the input |CA_system_id| and return true if it is supported.
// We only support the Widevine CA ID.
bool isSystemIdSupported(int32_t CA_system_id) const override;
// Add a descriptor to the vector |descriptors| for each CA ID we support.
// We only support the Widevine CA ID.
status_t queryPlugins(
std::vector<CasPluginDescriptor>* descriptors) const override;
// Construct an instance of our CAS API plugin.
status_t createPlugin(int32_t CA_system_id, void* appData,
CasPluginCallback callback,
CasPlugin** plugin) override;
// Construct a new extend instance of a CasPlugin given a CA_system_id
status_t createPlugin(int32_t CA_system_id, void* appData,
CasPluginCallbackExt callback,
CasPlugin** plugin) override;
private:
WidevineCasFactory() {}
WidevineCasFactory(const WidevineCasFactory&);
WidevineCasFactory& operator=(const WidevineCasFactory&);
};
} // namespace wvcas
#endif // WIDEVINE_MEDIA_CAS_H

View File

@@ -0,0 +1,154 @@
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine Master
// License Agreement.
#ifndef WIDEVINE_MEDIA_CAS_PLUGIN
#define WIDEVINE_MEDIA_CAS_PLUGIN
#include <memory>
#include "media/cas/CasAPI.h"
#include "widevine_cas_api.h"
using android::CasPlugin;
using android::CasPluginCallback;
using android::CasPluginCallbackExt;
using android::CasPluginStatusCallback;
using android::CasSessionId;
using android::status_t;
using android::String8;
namespace wvcas {
// WidevineCasPlugin inplements the android::CasPlugin interface.
class WidevineCasPlugin : public CasPlugin, public CasEventListener {
public:
// Constructs a new WidevineCasPlugin. The |callback| is a method to transmit
// events to the user application. To receive these events, a MediaCas
// application must implement The MediaCas.EventListener java interface and
// call MediaCas.setEventListener. More information can be found in the
// MediaCas platform api documentation.
WidevineCasPlugin(void* appData, CasPluginCallback callback);
WidevineCasPlugin(void* appData, CasPluginCallbackExt callback);
~WidevineCasPlugin() override;
status_t initialize();
// Provide a callback to report plugin status.
status_t setStatusCallback(CasPluginStatusCallback callback) override;
// Provide the CA private data from a CA_descriptor in the conditional
// access table.
status_t setPrivateData(const CasData& privateData) override;
// Open a session for descrambling a program, or one or more elementary
// streams.
status_t openSession(CasSessionId* sessionId) override;
// Open a session with intend and mode for descrambling a program, or one
// or more elementary streams.
status_t openSession(uint32_t intent, uint32_t mode,
CasSessionId* sessionId) override;
// Close a previously opened session.
status_t closeSession(const CasSessionId& sessionId) override;
// Provide the CA private data from a CA_descriptor in the program map
// table.
status_t setSessionPrivateData(const CasSessionId& sessionId,
const CasData& privateData) override;
// Process an ECM from the ECM stream for this sessions elementary stream.
status_t processEcm(const CasSessionId& sessionId,
const CasEcm& ecm) override;
// Process an in-band EMM from the EMM stream.
status_t processEmm(const CasEmm& emm) override;
// Deliver an event to the CasPlugin. The format of the event is specific
// to the CA scheme and is opaque to the framework.
status_t sendEvent(int32_t event, int32_t arg,
const CasData& eventData) override;
// Deliver an session event to the CasPlugin. The format of the event is
// specific to the CA scheme and is opaque to the framework.
status_t sendSessionEvent(const CasSessionId& sessionId, int32_t event,
int32_t arg, const CasData& eventData) override;
// Native implementation of the MediaCas Java API provision method.
status_t provision(const String8& provisionString) override;
// Native implementation of the MediaCas Java API refreshEntitlements method.
status_t refreshEntitlements(int32_t refreshType,
const CasData& refreshData) override;
WidevineCasPlugin(const WidevineCasPlugin&) = delete;
WidevineCasPlugin& operator=(const WidevineCasPlugin&) = delete;
protected:
// For unit test only.
virtual void SetWidevineCasApi(
std::unique_ptr<WidevineCas> widevine_cas_api) {
widevine_cas_api_ = std::move(widevine_cas_api);
}
WidevineCasPlugin(){};
private:
// |sessionId| is nullptr if the event is not a session event.
CasStatus processEvent(int32_t event, int32_t arg, const CasData& eventData,
const CasSessionId* sessionId);
CasStatus HandleIndividualizationResponse(const CasData& response);
CasStatus HandleEntitlementResponse(const CasData& response);
status_t requestLicense(const std::string& init_data);
CasStatus HandleEntitlementRenewalResponse(const CasData& response);
CasStatus HandleUniqueIDQuery();
CasStatus HandleSetParentalControlAge(const CasData& data);
CasStatus HandleLicenseRemoval(const CasData& license_id);
CasStatus HandleAssignLicenseID(const CasData& license_id);
CasStatus HandlePluginVersionQuery();
CasStatus HandleEntitlementPeriodUpdateResponse(const CasData& response);
// Returns true if the device has been provisioned with a device certificate.
bool is_provisioned() const;
// Event listener implementation
void OnSessionRenewalNeeded() override;
void OnSessionKeysChange(const KeyStatusMap& keys_status,
bool has_new_usable_key) override;
// |new_expiry_time_seconds| of 0 means "never expires".
void OnExpirationUpdate(int64_t new_expiry_time_seconds) override;
void OnNewRenewalServerUrl(const std::string& renewal_server_url) override;
void OnLicenseExpiration() override;
void OnAgeRestrictionUpdated(const WvCasSessionId& sessionId,
uint8_t ecm_age_restriction) override;
void OnSessionFingerprintingUpdated(const WvCasSessionId& sessionId,
const CasData& fingerprinting) override;
void OnSessionServiceBlockingUpdated(
const WvCasSessionId& sessionId,
const CasData& service_blocking) override;
void OnFingerprintingUpdated(const CasData& fingerprinting) override;
void OnServiceBlockingUpdated(const CasData& service_blocking) override;
void OnEntitlementPeriodUpdateNeeded(
const std::string& signed_license_request) override;
// Choose to use |callback_| or |callback_ext_| to send back information.
// |sessionId| is ignored if |callback_ext_| is null,
virtual void CallBack(void* appData, int32_t event, int32_t arg,
uint8_t* data, size_t size,
const CasSessionId* sessionId) const;
void* app_data_;
CasPluginCallback callback_;
CasPluginCallbackExt callback_ext_;
// If provision is called with a non-empty string,
// it is taken as a PSSH that overrides data in CA descripor.
// Otherwise, first CA descriptor available to the plugin
// is used to build a PSSH, and others are discarded.
bool is_emm_request_sent_ = false;
// This is always the serialized PSSH data.
std::string init_data_;
std::unique_ptr<WidevineCas> widevine_cas_api_;
};
} // namespace wvcas
#endif // WIDEVINE_MEDIA_CAS_PLUGIN

1138
plugin/src/cas_license.cpp Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

84
plugin/src/ecm_parser.cpp Normal file
View File

@@ -0,0 +1,84 @@
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine Master
// License Agreement.
#include "ecm_parser.h"
#include "ecm_parser_v2.h"
#include "ecm_parser_v3.h"
#include "log.h"
namespace wvcas {
namespace {
// 2 bytes cas id, 1 byte version.
constexpr int kEcmHeaderSize = 3;
constexpr int kCasIdIndex = 0;
constexpr int kVersionIndex = 2;
// Legacy Widevine CAS ID
constexpr uint16_t kWidevineCasId = 0x4AD4;
// New Widevine CAS IDs 0x56C0 to 0x56C9 (all inclusive).
constexpr uint16_t kWidevineNewCasIdLowerBound = 0x56C0;
constexpr uint16_t kWidevineNewCasIdUpperBound = 0x56C9;
// Two values of the table_id field (0x80 and 0x81) are reserved for
// transmission of ECM data. A change of these two table_id values signals
// that a change of ECM contents has occurred.
constexpr uint8_t kSectionHeader1 = 0x80;
constexpr uint8_t kSectionHeader2 = 0x81;
constexpr size_t kSectionHeaderSize = 3;
constexpr size_t kSectionHeaderWithPointerSize = 4;
constexpr uint8_t kPointerFieldZero = 0x00;
// Returns the possible starting index of ECM. It assumes the pointer field will
// always set to 0, if present.
int find_ecm_start_index(const CasEcm& cas_ecm) {
if (cas_ecm.empty()) {
return 0;
}
// Case 1: Pointer field (always set to 0); section header; ECM.
if (cas_ecm[0] == kPointerFieldZero) {
return kSectionHeaderWithPointerSize;
}
// Case 2: Section header (3 bytes), ECM.
if (cas_ecm[0] == kSectionHeader1 || cas_ecm[0] == kSectionHeader2) {
return kSectionHeaderSize;
}
// Case 3: ECM.
return 0;
}
} // namespace
std::unique_ptr<EcmParser> EcmParser::Create(const CasEcm& cas_ecm) {
// Detect and strip optional section header.
const int offset = find_ecm_start_index(cas_ecm);
if (offset < 0 ||
(offset + kEcmHeaderSize > static_cast<int>(cas_ecm.size()))) {
LOGE("Unable to find start of ECM");
return nullptr;
}
const CasEcm ecm(cas_ecm.begin() + offset, cas_ecm.end());
// Confirm ecm data starts with valid Widevine CAS ID.
uint16_t cas_id_val = (ecm[kCasIdIndex] << 8) | ecm[kCasIdIndex + 1];
if (cas_id_val != kWidevineCasId &&
(cas_id_val < kWidevineNewCasIdLowerBound ||
cas_id_val > kWidevineNewCasIdUpperBound)) {
LOGE("Supported Widevine CAS IDs not found at the start of ECM. Found: %u",
cas_id_val);
return nullptr;
}
if (ecm[kVersionIndex] <= 2) {
std::unique_ptr<EcmParserV2> parser;
if (!EcmParserV2::create(ecm, &parser)) {
return nullptr;
}
return parser;
} else {
return EcmParserV3::Create(ecm);
}
}
} // namespace wvcas

View File

@@ -0,0 +1,181 @@
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine Master
// License Agreement.
#include "ecm_parser_v2.h"
#include <algorithm>
namespace wvcas {
namespace {
// ECM constants
constexpr uint8_t kCryptoModeFlags = (0x3 << 1); // Mode bits 1..2
constexpr uint8_t kCryptoModeFlagsV2 = (0xF << 1); // Mode bits 1..4
constexpr uint8_t kRotationFlag = (0x1 << 0); // Mode bit 0
constexpr uint8_t kContentIVSizeFlag = (0x1 << 6);
constexpr uint8_t kAgeRestrictionMask = (0x1F << 1);
constexpr size_t kEntitlementKeyIDSizeBytes = 16;
constexpr size_t kContentKeyIDSizeBytes = 16;
constexpr size_t kContentKeyDataSizeBytes = 16;
constexpr size_t kWrappedKeyIVSizeBytes = 16;
// Size is either 8 or 16 bytes, depending on ContentIVSize flag.
constexpr size_t kContentKeyMaxIVSizeBytes = 16;
constexpr size_t kMaxTsPayloadSizeBytes = 184;
} // namespace
#pragma pack(push, 1) // No padding in ecm struct definition.
struct EcmKeyData {
const uint8_t entitlement_key_id[kEntitlementKeyIDSizeBytes];
const uint8_t content_key_id[kContentKeyIDSizeBytes];
const uint8_t control_word[kContentKeyDataSizeBytes];
const uint8_t control_word_iv[kWrappedKeyIVSizeBytes];
// Actual size can be either 8 or 16 bytes.
const uint8_t content_iv[kContentKeyMaxIVSizeBytes];
};
struct EcmDescriptor {
const uint16_t ca_id;
const uint8_t version;
const uint8_t flags_cipher_rotation;
const uint8_t flags_iv_age;
};
static_assert((sizeof(EcmDescriptor) + 2 * sizeof(EcmKeyData)) <=
kMaxTsPayloadSizeBytes,
"Maximum possible ecm size is larger than a ts payload");
#pragma pack(pop) // Revert padding value to previous.
EcmParserV2::EcmParserV2(const CasEcm& ecm) : ecm_(ecm) {}
size_t EcmParserV2::key_data_size() const {
return sizeof(EcmKeyData) + content_iv_size() - kContentKeyMaxIVSizeBytes;
}
bool EcmParserV2::is_valid_size() const {
size_t expected_size =
sizeof(EcmDescriptor) + key_data_size() * (rotation_enabled() ? 2 : 1);
// Parser always receives entire ts payload of 184 bytes.
return ecm_.size() >= expected_size;
}
const EcmKeyData* EcmParserV2::key_slot_data(KeySlotId id) const {
// ECM descriptor is followed by either one or two ECM key data.
size_t key_data_offset = sizeof(EcmDescriptor);
if (rotation_enabled()) {
if (id == KeySlotId::kOddKeySlot) {
key_data_offset += key_data_size();
} else if (id != KeySlotId::kEvenKeySlot) {
return nullptr;
}
} else {
// No rotation enabled.
if (id != KeySlotId::kEvenKeySlot) {
return nullptr;
}
}
return reinterpret_cast<const EcmKeyData*>(&ecm_[key_data_offset]);
}
bool EcmParserV2::create(const CasEcm& cas_ecm,
std::unique_ptr<EcmParserV2>* parser) {
if (parser == nullptr) {
return false;
}
// Using 'new' to access a non-public constructor.
auto new_parser = std::unique_ptr<EcmParserV2>(new EcmParserV2(cas_ecm));
if (!new_parser->is_valid_size()) {
return false;
}
*parser = std::move(new_parser);
return true;
}
uint8_t EcmParserV2::version() const {
const EcmDescriptor* ecm = reinterpret_cast<const EcmDescriptor*>(&ecm_[0]);
return ecm->version;
}
CryptoMode EcmParserV2::crypto_mode() const {
const EcmDescriptor* ecm = reinterpret_cast<const EcmDescriptor*>(&ecm_[0]);
if (version() == 1) {
return static_cast<CryptoMode>(
(ecm->flags_cipher_rotation & kCryptoModeFlags) >> 1);
}
return static_cast<CryptoMode>(
(ecm->flags_cipher_rotation & kCryptoModeFlagsV2) >> 1);
}
bool EcmParserV2::rotation_enabled() const {
const EcmDescriptor* ecm = reinterpret_cast<const EcmDescriptor*>(&ecm_[0]);
return (ecm->flags_cipher_rotation & kRotationFlag);
}
size_t EcmParserV2::content_iv_size() const {
const EcmDescriptor* ecm = reinterpret_cast<const EcmDescriptor*>(&ecm_[0]);
// Content key IV size is 8 bytes if flag is zero, and 16 if flag is set.
return (ecm->flags_iv_age & kContentIVSizeFlag) ? 16 : 8;
}
uint8_t EcmParserV2::age_restriction() const {
if (version() == 1) {
return 0;
}
const EcmDescriptor* ecm = reinterpret_cast<const EcmDescriptor*>(&ecm_[0]);
return (ecm->flags_iv_age & kAgeRestrictionMask) >> 1;
}
std::vector<uint8_t> EcmParserV2::entitlement_key_id(KeySlotId id) const {
std::vector<uint8_t> ekey_id;
const EcmKeyData* key_data = key_slot_data(id);
if (key_data) {
ekey_id.assign(
&key_data->entitlement_key_id[0],
&key_data->entitlement_key_id[0] + kEntitlementKeyIDSizeBytes);
}
return ekey_id;
}
std::vector<uint8_t> EcmParserV2::content_key_id(KeySlotId id) const {
std::vector<uint8_t> ckey_id;
const EcmKeyData* key_data = key_slot_data(id);
if (key_data) {
ckey_id.assign(&key_data->content_key_id[0],
&key_data->content_key_id[0] + kContentKeyIDSizeBytes);
}
return ckey_id;
}
std::vector<uint8_t> EcmParserV2::wrapped_key_data(KeySlotId id) const {
std::vector<uint8_t> ckey_data;
const EcmKeyData* key_data = key_slot_data(id);
if (key_data) {
ckey_data.assign(&key_data->control_word[0],
&key_data->control_word[0] + kContentKeyDataSizeBytes);
}
return ckey_data;
}
std::vector<uint8_t> EcmParserV2::wrapped_key_iv(KeySlotId id) const {
std::vector<uint8_t> iv;
const EcmKeyData* key_data = key_slot_data(id);
if (key_data) {
iv.assign(&key_data->control_word_iv[0],
&key_data->control_word_iv[0] + kWrappedKeyIVSizeBytes);
}
return iv;
}
std::vector<uint8_t> EcmParserV2::content_iv(KeySlotId id) const {
std::vector<uint8_t> iv;
const EcmKeyData* key_data = key_slot_data(id);
if (key_data) {
iv.assign(&key_data->content_iv[0],
&key_data->content_iv[0] + content_iv_size());
}
return iv;
}
} // namespace wvcas

View File

@@ -0,0 +1,223 @@
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine Master
// License Agreement.
#include "ecm_parser_v3.h"
#include <utility>
#include "log.h"
namespace wvcas {
namespace {
using video_widevine::EcmKeyData;
using video_widevine::EcmMetaData;
using video_widevine::EcmPayload;
using video_widevine::SignedEcmPayload;
constexpr int kEcmHeaderSize = 3;
constexpr uint8_t kEcmVersion = 3;
// 16 bytes fixed content key ids
constexpr uint8_t kEvenContentKeyId[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00};
constexpr uint8_t kOddContentKeyId[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01};
CryptoMode ConvertProtoCipherMode(EcmMetaData::CipherMode cipher_mode) {
switch (cipher_mode) {
case EcmMetaData::AES_CBC:
return CryptoMode::kAesCBC;
case EcmMetaData::AES_CTR:
return CryptoMode::kAesCTR;
case EcmMetaData::DVB_CSA2:
return CryptoMode::kDvbCsa2;
case EcmMetaData::DVB_CSA3:
return CryptoMode::kDvbCsa3;
case EcmMetaData::AES_OFB:
return CryptoMode::kAesOFB;
case EcmMetaData::AES_SCTE52:
return CryptoMode::kAesSCTE;
case EcmMetaData::AES_ECB:
return CryptoMode::kAesECB;
case EcmMetaData::UNSPECIFIED:
default:
return CryptoMode::kInvalid;
}
}
} // namespace
EcmParserV3::EcmParserV3(SignedEcmPayload signed_ecm_payload,
EcmPayload ecm_payload)
: signed_ecm_payload_(std::move(signed_ecm_payload)),
ecm_payload_(std::move(ecm_payload)) {
even_key_data_ = ecm_payload_.even_key_data();
odd_key_data_ = ecm_payload_.odd_key_data();
}
std::unique_ptr<EcmParserV3> EcmParserV3::Create(const CasEcm& cas_ecm) {
if (cas_ecm.size() <= kEcmHeaderSize) {
LOGE("ECM is too short. Size: %lu", cas_ecm.size());
return nullptr;
}
SignedEcmPayload signed_ecm_payload;
// The 3 byte ecm header is ignored.
if (!signed_ecm_payload.ParseFromArray(cas_ecm.data() + kEcmHeaderSize,
cas_ecm.size() - kEcmHeaderSize)) {
LOGE("Unable to parse signed ecm payload");
return nullptr;
}
EcmPayload ecm_payload;
if (!ecm_payload.ParseFromString(signed_ecm_payload.serialized_payload())) {
LOGE("Unable to parse ecm payload");
return nullptr;
}
// Using 'new' to access a non-public constructor.
return std::unique_ptr<EcmParserV3>(
new EcmParserV3(signed_ecm_payload, ecm_payload));
}
uint8_t EcmParserV3::version() const { return kEcmVersion; }
CryptoMode EcmParserV3::crypto_mode() const {
return ConvertProtoCipherMode(ecm_payload_.meta_data().cipher_mode());
}
bool EcmParserV3::rotation_enabled() const {
return ecm_payload_.has_odd_key_data();
}
size_t EcmParserV3::content_iv_size() const {
return ecm_payload_.even_key_data().content_iv().size();
}
uint8_t EcmParserV3::age_restriction() const {
return static_cast<uint8_t>(ecm_payload_.meta_data().age_restriction());
}
std::vector<uint8_t> EcmParserV3::entitlement_key_id(KeySlotId id) const {
// Use the even entitlement_key_id if the odd one is empty (omitted).
const EcmKeyData& key_data =
id == KeySlotId::kOddKeySlot &&
!odd_key_data_.entitlement_key_id().empty()
? odd_key_data_
: even_key_data_;
return {key_data.entitlement_key_id().begin(),
key_data.entitlement_key_id().end()};
}
std::vector<uint8_t> EcmParserV3::content_key_id(KeySlotId id) const {
if (id == KeySlotId::kEvenKeySlot && ecm_payload_.has_even_key_data()) {
return {kEvenContentKeyId, kEvenContentKeyId + sizeof(kEvenContentKeyId)};
} else if (id == KeySlotId::kOddKeySlot && ecm_payload_.has_odd_key_data()) {
return {kOddContentKeyId, kOddContentKeyId + sizeof(kOddContentKeyId)};
}
return {};
}
std::vector<uint8_t> EcmParserV3::wrapped_key_data(KeySlotId id) const {
const EcmKeyData& key_data =
id == KeySlotId::kOddKeySlot ? odd_key_data_ : even_key_data_;
return {key_data.wrapped_key_data().begin(),
key_data.wrapped_key_data().end()};
}
std::vector<uint8_t> EcmParserV3::wrapped_key_iv(KeySlotId id) const {
// Use the even wrapped_key_iv if the odd one is empty (omitted).
const EcmKeyData* key_data =
id == KeySlotId::kOddKeySlot && !odd_key_data_.wrapped_key_iv().empty()
? &odd_key_data_
: &even_key_data_;
// Wrapped key IV may be omitted for group keys.
if (key_data->wrapped_key_iv().empty()) {
key_data = id == KeySlotId::kOddKeySlot &&
!ecm_payload_.odd_key_data().wrapped_key_iv().empty()
? &ecm_payload_.odd_key_data()
: &ecm_payload_.even_key_data();
}
return {key_data->wrapped_key_iv().begin(), key_data->wrapped_key_iv().end()};
}
std::vector<uint8_t> EcmParserV3::content_iv(KeySlotId id) const {
// Use the even content_iv if the odd one is empty (omitted).
const EcmKeyData* key_data =
id == KeySlotId::kOddKeySlot && !odd_key_data_.content_iv().empty()
? &odd_key_data_
: &even_key_data_;
// Content IV may be omitted for group keys.
if (key_data->content_iv().empty()) {
key_data = id == KeySlotId::kOddKeySlot &&
!ecm_payload_.odd_key_data().content_iv().empty()
? &ecm_payload_.odd_key_data()
: &ecm_payload_.even_key_data();
}
return {key_data->content_iv().begin(), key_data->content_iv().end()};
}
bool EcmParserV3::has_fingerprinting() const {
return ecm_payload_.has_fingerprinting();
}
video_widevine::Fingerprinting EcmParserV3::fingerprinting() const {
return ecm_payload_.fingerprinting();
}
bool EcmParserV3::has_service_blocking() const {
return ecm_payload_.has_service_blocking();
}
video_widevine::ServiceBlocking EcmParserV3::service_blocking() const {
return ecm_payload_.service_blocking();
}
std::string EcmParserV3::ecm_serialized_payload() const {
return signed_ecm_payload_.serialized_payload();
}
std::string EcmParserV3::signature() const {
return signed_ecm_payload_.signature();
}
bool EcmParserV3::set_group_id(const std::string& group_id) {
if (group_id.empty()) {
even_key_data_ = ecm_payload_.even_key_data();
odd_key_data_ = ecm_payload_.odd_key_data();
return true;
}
bool found = false;
for (int i = 0; i < ecm_payload_.group_key_data_size(); ++i) {
const video_widevine::EcmGroupKeyData& group_key_data =
ecm_payload_.group_key_data(i);
if (group_key_data.group_id() == group_id) {
found = true;
even_key_data_ = group_key_data.even_key_data();
odd_key_data_ = group_key_data.odd_key_data();
break;
}
}
return found;
}
bool EcmParserV3::is_entitlement_rotation_enabled() const {
return ecm_payload_.meta_data().has_entitlement_period_index();
}
uint32_t EcmParserV3::entitlement_period_index() const {
return ecm_payload_.meta_data().entitlement_period_index();
}
uint32_t EcmParserV3::entitlement_rotation_window_left() const {
return ecm_payload_.meta_data().entitlement_rotation_window_left();
}
} // namespace wvcas

75
plugin/src/emm_parser.cpp Normal file
View File

@@ -0,0 +1,75 @@
// Copyright 2020 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine Master
// License Agreement.
#include "emm_parser.h"
#include "log.h"
namespace wvcas {
namespace {
// ETSI ETR 289 specifies table ids 0x82 to 0x8F are for CA System private
// usage, which are typically used by EMM, with one table id for each EMM type.
constexpr uint16_t kSectionHeader = 0x82;
constexpr size_t kSectionHeaderSize = 3;
constexpr size_t kSectionHeaderWithPointerSize = 4;
constexpr uint8_t kPointerFieldZero = 0x00;
// Returns the possible starting index of EMM. -1 will be returned in case of
// error. It assumes the pointer field will always set to 0, if present.
int find_emm_start_index(const CasEmm& cas_emm) {
if (cas_emm.empty()) {
return -1;
}
// Case 1: Pointer field (always set to 0); section header; EMM.
if (cas_emm[0] == kPointerFieldZero) {
return kSectionHeaderWithPointerSize < cas_emm.size()
? kSectionHeaderWithPointerSize
: -1;
}
// Case 2: Section header (3 bytes), EMM.
if (cas_emm[0] == kSectionHeader) {
return kSectionHeaderSize < cas_emm.size() ? kSectionHeaderSize : -1;
}
// Case 3: EMM.
return 0;
}
} // namespace
std::unique_ptr<const EmmParser> EmmParser::Create(const CasEmm& emm) {
auto parser = std::unique_ptr<EmmParser>(new EmmParser());
if (!parser->Parse(find_emm_start_index(emm), emm)) {
return nullptr;
}
return parser;
}
bool EmmParser::Parse(int start_index, const CasEmm& emm) {
if (start_index < 0) {
return false;
}
video_widevine::SignedEmmPayload signed_emm;
if (!signed_emm.ParseFromArray(emm.data() + start_index,
emm.size() - start_index)) {
LOGE("Failed to parse signed EMM.");
return false;
}
signature_ = signed_emm.signature();
if (signature_.empty()) {
LOGE("No signature in the EMM.");
return false;
}
if (!emm_payload_.ParseFromString(signed_emm.serialized_payload())) {
LOGE("Failed to parse EMM payload.");
return false;
}
timestamp_ = emm_payload_.timestamp_secs();
return true;
}
} // namespace wvcas

View File

@@ -0,0 +1,355 @@
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine Master
// License Agreement.
#include "license_key_status.h"
#include <string>
#include "log.h"
namespace {
// License protocol aliases
typedef ::video_widevine::License::KeyContainer KeyContainer;
typedef KeyContainer::OutputProtection OutputProtection;
typedef KeyContainer::VideoResolutionConstraint VideoResolutionConstraint;
typedef ::google::protobuf::RepeatedPtrField<VideoResolutionConstraint>
ConstraintList;
// Map the HDCP protection associated with a key in the license to
// an equivalent OEMCrypto HDCP protection level
wvcas::HdcpCapability ProtobufHdcpToOemCryptoHdcp(
const OutputProtection::HDCP& input) {
switch (input) {
case OutputProtection::HDCP_NONE:
return HDCP_NONE;
case OutputProtection::HDCP_V1:
return HDCP_V1;
case OutputProtection::HDCP_V2:
return HDCP_V2;
case OutputProtection::HDCP_V2_1:
return HDCP_V2_1;
case OutputProtection::HDCP_V2_2:
return HDCP_V2_2;
case OutputProtection::HDCP_V2_3:
return HDCP_V2_3;
case OutputProtection::HDCP_NO_DIGITAL_OUTPUT:
return HDCP_NO_DIGITAL_OUTPUT;
default:
LOGE(
"ContentKeyStatus::ProtobufHdcpToOemCryptoHdcp: "
"Unknown HDCP Level: input=%d, returning HDCP_NO_DIGITAL_OUTPUT",
input);
return HDCP_NO_DIGITAL_OUTPUT;
}
}
// Returns the constraint from a set of constraints that matches the
// specified resolution, or null if none match
VideoResolutionConstraint* GetConstraintForRes(
uint32_t res, ConstraintList& constraints_from_key) {
typedef ConstraintList::pointer_iterator Iterator;
for (Iterator i = constraints_from_key.pointer_begin();
i != constraints_from_key.pointer_end(); ++i) {
VideoResolutionConstraint* constraint = *i;
if (constraint->has_min_resolution_pixels() &&
constraint->has_max_resolution_pixels() &&
res >= constraint->min_resolution_pixels() &&
res <= constraint->max_resolution_pixels()) {
return constraint;
}
}
return NULL;
}
} // namespace
namespace wvcas {
bool LicenseKeys::IsContentKey(const std::string& key_id) {
if (key_statuses_.count(key_id) > 0) {
return key_statuses_[key_id]->IsContentKey();
} else if (content_keyid_to_entitlement_key_id_.count(key_id) > 0) {
return true;
} else {
return false;
}
}
bool LicenseKeys::CanDecryptContent(const std::string& key_id) {
if (key_statuses_.count(key_id) > 0) {
return key_statuses_[key_id]->CanDecryptContent();
} else if (content_keyid_to_entitlement_key_id_.count(key_id) > 0) {
if (key_statuses_.count(content_keyid_to_entitlement_key_id_[key_id]) > 0) {
return key_statuses_[content_keyid_to_entitlement_key_id_[key_id]]
->CanDecryptContent();
}
return false;
} else {
return false;
}
}
bool LicenseKeys::GetAllowedUsage(const KeyId& key_id,
KeyAllowedUsage* allowed_usage) {
if (key_statuses_.count(key_id) > 0) {
return key_statuses_[key_id]->GetAllowedUsage(allowed_usage);
} else if (content_keyid_to_entitlement_key_id_.count(key_id) > 0) {
if (key_statuses_.count(content_keyid_to_entitlement_key_id_[key_id]) > 0) {
return key_statuses_[content_keyid_to_entitlement_key_id_[key_id]]
->CanDecryptContent();
}
return false;
} else {
return false;
}
}
bool LicenseKeys::ApplyStatusChange(KeyStatus new_status,
bool* new_usable_keys) {
bool keys_changed = false;
bool newly_usable = false;
*new_usable_keys = false;
for (LicenseKeyStatusIterator it = key_statuses_.begin();
it != key_statuses_.end(); ++it) {
bool usable;
if (it->second->ApplyStatusChange(new_status, &usable)) {
newly_usable |= usable;
keys_changed = true;
}
}
*new_usable_keys = newly_usable;
return keys_changed;
}
KeyStatus LicenseKeys::GetKeyStatus(const KeyId& key_id) {
if (key_statuses_.count(key_id) == 0) {
return kKeyStatusKeyUnknown;
}
return key_statuses_[key_id]->GetKeyStatus();
}
void LicenseKeys::ExtractKeyStatuses(KeyStatusMap* content_keys) {
content_keys->clear();
for (LicenseKeyStatusIterator it = key_statuses_.begin();
it != key_statuses_.end(); ++it) {
if (it->second->IsContentKey()) {
const KeyId key_id = it->first;
KeyStatus key_status = it->second->GetKeyStatus();
(*content_keys)[key_id] = key_status;
}
}
}
bool LicenseKeys::MeetsConstraints(const KeyId& key_id) {
if (key_statuses_.count(key_id) > 0) {
return key_statuses_[key_id]->MeetsConstraints();
} else {
// If a Key ID is unknown to us, we don't know of any constraints for it,
// so never block decryption.
return true;
}
}
void LicenseKeys::ApplyConstraints(uint32_t new_resolution,
HdcpCapability new_hdcp_level) {
for (LicenseKeyStatusIterator i = key_statuses_.begin();
i != key_statuses_.end(); ++i) {
i->second->ApplyConstraints(new_resolution, new_hdcp_level);
}
}
void LicenseKeys::SetFromLicense(const video_widevine::License& license) {
this->Clear();
for (int32_t key_index = 0; key_index < license.key_size(); ++key_index) {
const KeyContainer& key = license.key(key_index);
if (key.has_id() && (key.type() == KeyContainer::CONTENT ||
key.type() == KeyContainer::OPERATOR_SESSION ||
key.type() == KeyContainer::ENTITLEMENT)) {
const KeyId& key_id = key.id();
key_statuses_[key_id] = new LicenseKeyStatus(key);
}
}
}
void LicenseKeys::SetEntitledKeys(
const std::vector<WidevinePsshData_EntitledKey>& keys) {
for (std::vector<WidevinePsshData_EntitledKey>::const_iterator key =
keys.begin();
key != keys.end(); key++) {
// Check to see if we have an entitlement key for this content key.
std::map<KeyId, LicenseKeyStatus*>::iterator entitlement =
key_statuses_.find(key->entitlement_key_id());
if (entitlement == key_statuses_.end()) {
continue;
}
// And set the new content key id.
content_keyid_to_entitlement_key_id_[key->key_id()] =
key->entitlement_key_id();
}
}
LicenseKeyStatus::LicenseKeyStatus(const KeyContainer& key)
: is_content_key_(false),
key_status_(kKeyStatusInternalError),
meets_constraints_(true),
default_hdcp_level_(HDCP_NONE) {
allowed_usage_.Clear();
constraints_.Clear();
if (key.type() == KeyContainer::CONTENT ||
key.type() == KeyContainer::ENTITLEMENT) {
ParseContentKey(key);
} else if (key.type() == KeyContainer::OPERATOR_SESSION) {
ParseOperatorSessionKey(key);
}
}
void LicenseKeyStatus::ParseContentKey(const KeyContainer& key) {
is_content_key_ = true;
if (key.has_level() && ((key.level() == KeyContainer::HW_SECURE_DECODE) ||
(key.level() == KeyContainer::HW_SECURE_ALL))) {
allowed_usage_.decrypt_to_clear_buffer = false;
allowed_usage_.decrypt_to_secure_buffer = true;
} else {
allowed_usage_.decrypt_to_clear_buffer = true;
allowed_usage_.decrypt_to_secure_buffer = true;
}
if (key.has_level()) {
switch (key.level()) {
case KeyContainer::SW_SECURE_CRYPTO:
allowed_usage_.key_security_level_ = kSoftwareSecureCrypto;
break;
case KeyContainer::SW_SECURE_DECODE:
allowed_usage_.key_security_level_ = kSoftwareSecureDecode;
break;
case KeyContainer::HW_SECURE_CRYPTO:
allowed_usage_.key_security_level_ = kHardwareSecureCrypto;
break;
case KeyContainer::HW_SECURE_DECODE:
allowed_usage_.key_security_level_ = kHardwareSecureDecode;
break;
case KeyContainer::HW_SECURE_ALL:
allowed_usage_.key_security_level_ = kHardwareSecureAll;
break;
default:
allowed_usage_.key_security_level_ = kKeySecurityLevelUnknown;
break;
}
} else {
allowed_usage_.key_security_level_ = kKeySecurityLevelUnset;
}
allowed_usage_.SetValid();
if (key.video_resolution_constraints_size() > 0) {
SetConstraints(key.video_resolution_constraints());
}
if (key.has_required_protection()) {
default_hdcp_level_ =
ProtobufHdcpToOemCryptoHdcp(key.required_protection().hdcp());
}
}
void LicenseKeyStatus::ParseOperatorSessionKey(const KeyContainer& key) {
is_content_key_ = false;
if (key.has_operator_session_key_permissions()) {
OperatorSessionKeyPermissions permissions =
key.operator_session_key_permissions();
if (permissions.has_allow_encrypt())
allowed_usage_.generic_encrypt = permissions.allow_encrypt();
if (permissions.has_allow_decrypt())
allowed_usage_.generic_decrypt = permissions.allow_decrypt();
if (permissions.has_allow_sign())
allowed_usage_.generic_sign = permissions.allow_sign();
if (permissions.has_allow_signature_verify())
allowed_usage_.generic_verify = permissions.allow_signature_verify();
} else {
allowed_usage_.generic_encrypt = false;
allowed_usage_.generic_decrypt = false;
allowed_usage_.generic_sign = false;
allowed_usage_.generic_verify = false;
}
allowed_usage_.SetValid();
}
void LicenseKeys::Clear() {
for (LicenseKeyStatusIterator i = key_statuses_.begin();
i != key_statuses_.end(); ++i) {
delete i->second;
}
key_statuses_.clear();
}
bool LicenseKeyStatus::CanDecryptContent() {
return is_content_key_ && key_status_ == kKeyStatusUsable;
}
bool LicenseKeyStatus::GetAllowedUsage(KeyAllowedUsage* allowed_usage) {
if (NULL == allowed_usage) return false;
*allowed_usage = allowed_usage_;
return true;
}
bool LicenseKeyStatus::ApplyStatusChange(KeyStatus new_status,
bool* new_usable_key) {
*new_usable_key = false;
if (!is_content_key_) {
return false;
}
KeyStatus updated_status = new_status;
if (updated_status == kKeyStatusUsable) {
if (!MeetsConstraints()) {
updated_status = kKeyStatusOutputNotAllowed;
}
}
if (key_status_ != updated_status) {
key_status_ = updated_status;
if (updated_status == kKeyStatusUsable) {
*new_usable_key = true;
}
return true;
}
return false;
}
// If the key has constraints, find the constraint that applies.
// If none found, then the constraint test fails.
// If a constraint is found, verify that the device's current HDCP
// level is sufficient. If the constraint has an HDCP setting, use it,
// If the key has no constraints, or if the constraint has no HDCP
// requirement, use the key's default HDCP setting to check against the
// device's current HDCP level.
void LicenseKeyStatus::ApplyConstraints(uint32_t video_pixels,
HdcpCapability new_hdcp_level) {
VideoResolutionConstraint* current_constraint = NULL;
if (HasConstraints() && video_pixels != HDCP_UNSPECIFIED_VIDEO_RESOLUTION) {
current_constraint = GetConstraintForRes(video_pixels, constraints_);
if (NULL == current_constraint) {
meets_constraints_ = false;
return;
}
}
HdcpCapability desired_hdcp_level;
if (current_constraint && current_constraint->has_required_protection()) {
desired_hdcp_level = ProtobufHdcpToOemCryptoHdcp(
current_constraint->required_protection().hdcp());
} else {
desired_hdcp_level = default_hdcp_level_;
}
meets_constraints_ = (new_hdcp_level >= desired_hdcp_level);
}
void LicenseKeyStatus::SetConstraints(const ConstraintList& constraints) {
if (!is_content_key_) {
return;
}
constraints_.Clear();
constraints_.MergeFrom(constraints);
meets_constraints_ = true;
}
} // namespace wvcas

View File

@@ -0,0 +1,413 @@
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine Master
// License Agreement.
#include "oemcrypto_interface.h"
#include <dlfcn.h>
#include <memory>
#include "cas_properties.h"
#include "cas_util.h"
#include "log.h"
// These macros lookup the obfuscated name used for oemcrypto.
#define QUOTE_DEFINE(A) #A
#define QUOTE(A) QUOTE_DEFINE(A)
#define LOOKUP(handle, name) dlsym(handle, QUOTE(name))
#define LOAD_SYM(name) \
name = reinterpret_cast<name##_t>(LOOKUP(handle_, OEMCrypto_##name)); \
if (nullptr == name) { \
LOGE("%s", dlerror()); \
return false; \
}
#define TRY_LOAD_SYM(name) \
name = reinterpret_cast<name##_t>(LOOKUP(handle_, OEMCrypto_##name)); \
if (nullptr == name) { \
LOGW("%s", dlerror()); \
}
namespace wvcas {
class OEMCryptoInterface::Impl {
public:
~Impl() {}
static std::unique_ptr<Impl> create() {
// Using 'new' to access a non-public constructor.
std::unique_ptr<Impl> interface = std::unique_ptr<Impl>(new Impl());
if (!interface->initialize()) {
return std::unique_ptr<Impl>();
}
return interface;
}
typedef OEMCryptoResult (*Initialize_t)();
typedef OEMCryptoResult (*Terminate_t)();
typedef OEMCryptoResult (*OpenSession_t)(OEMCrypto_SESSION*);
typedef OEMCryptoResult (*CloseSession_t)(OEMCrypto_SESSION);
typedef OEMCrypto_ProvisioningMethod (*GetProvisioningMethod_t)();
typedef OEMCryptoResult (*GetKeyData_t)(uint8_t*, size_t*);
typedef uint32_t (*SupportedCertificates_t)();
typedef OEMCryptoResult (*GenerateNonce_t)(OEMCrypto_SESSION session,
uint32_t* nonce);
typedef OEMCryptoResult (*PrepAndSignLicenseRequest_t)(OEMCrypto_SESSION,
const uint8_t*, size_t,
size_t*, uint8_t*,
size_t*);
typedef OEMCryptoResult (*PrepAndSignRenewalRequest_t)(OEMCrypto_SESSION,
const uint8_t*, size_t,
size_t*, uint8_t*,
size_t*);
typedef OEMCryptoResult (*PrepAndSignProvisioningRequest_t)(
OEMCrypto_SESSION, const uint8_t*, size_t, size_t*, uint8_t*, size_t*);
typedef OEMCryptoResult (*LoadProvisioning_t)(OEMCrypto_SESSION,
const uint8_t*, size_t, size_t,
const uint8_t*, size_t,
uint8_t*, size_t*);
typedef OEMCryptoResult (*GenerateDerivedKeys_t)(
OEMCrypto_SESSION session, const uint8_t* mac_key_context,
uint32_t mac_key_context_length, const uint8_t* enc_key_context,
uint32_t enc_key_context_length);
typedef OEMCryptoResult (*GetOEMPublicCertificate_t)(OEMCrypto_SESSION,
uint8_t*, size_t*);
typedef OEMCryptoResult (*LoadDRMPrivateKey_t)(OEMCrypto_SESSION,
OEMCrypto_PrivateKeyType,
const uint8_t*, size_t);
typedef OEMCryptoResult (*GenerateRSASignature_t)(OEMCrypto_SESSION,
const uint8_t*, size_t,
uint8_t*, size_t*,
RSA_Padding_Scheme);
typedef OEMCryptoResult (*DeriveKeysFromSessionKey_t)(OEMCrypto_SESSION,
const uint8_t*, size_t,
const uint8_t*, size_t,
const uint8_t*, size_t);
typedef OEMCryptoResult (*LoadLicense_t)(OEMCrypto_SESSION session,
const uint8_t* message,
size_t message_length,
size_t core_message_length,
const uint8_t* signature,
size_t signature_length);
typedef OEMCryptoResult (*LoadRenewal_t)(OEMCrypto_SESSION session,
const uint8_t* message,
size_t message_length,
size_t core_message_length,
const uint8_t* signature,
size_t signature_length);
typedef OEMCryptoResult (*LoadCasECMKeys_t)(
OEMCrypto_SESSION, const uint8_t*, size_t,
const OEMCrypto_EntitledContentKeyObject*,
const OEMCrypto_EntitledContentKeyObject*);
typedef OEMCryptoResult (*GetHDCPCapability_t)(OEMCrypto_HDCP_Capability*,
OEMCrypto_HDCP_Capability*);
typedef OEMCryptoResult (*GetDeviceID_t)(uint8_t* deviceID, size_t* idLength);
typedef OEMCryptoResult (*LoadTestKeybox_t)(const uint8_t* buffer,
size_t length);
typedef const char* (*SecurityLevel_t)();
typedef OEMCryptoResult (*CreateEntitledKeySession_t)(
OEMCrypto_SESSION oec_session, OEMCrypto_SESSION* key_session);
typedef OEMCryptoResult (*RemoveEntitledKeySession_t)(
OEMCrypto_SESSION key_session);
typedef OEMCryptoResult (*ReassociateEntitledKeySession_t)(
OEMCrypto_SESSION key_session, OEMCrypto_SESSION oec_session);
typedef uint32_t (*APIVersion_t)();
typedef OEMCryptoResult (*GetOEMKeyToken_t)(OEMCrypto_SESSION key_session,
uint8_t* key_token,
size_t* key_token_length);
typedef OEMCryptoResult (*GetSignatureHashAlgorithm_t)(
OEMCrypto_SESSION session, OEMCrypto_SignatureHashAlgorithm* algorithm);
Initialize_t Initialize = nullptr;
Terminate_t Terminate = nullptr;
OpenSession_t OpenSession = nullptr;
CloseSession_t CloseSession = nullptr;
GetProvisioningMethod_t GetProvisioningMethod = nullptr;
GetKeyData_t GetKeyData = nullptr;
SupportedCertificates_t SupportedCertificates = nullptr;
GenerateNonce_t GenerateNonce = nullptr;
GenerateDerivedKeys_t GenerateDerivedKeys = nullptr;
PrepAndSignLicenseRequest_t PrepAndSignLicenseRequest = nullptr;
PrepAndSignRenewalRequest_t PrepAndSignRenewalRequest = nullptr;
PrepAndSignProvisioningRequest_t PrepAndSignProvisioningRequest = nullptr;
LoadProvisioning_t LoadProvisioning = nullptr;
GetOEMPublicCertificate_t GetOEMPublicCertificate = nullptr;
LoadDRMPrivateKey_t LoadDRMPrivateKey = nullptr;
GenerateRSASignature_t GenerateRSASignature = nullptr;
DeriveKeysFromSessionKey_t DeriveKeysFromSessionKey = nullptr;
LoadLicense_t LoadLicense = nullptr;
LoadRenewal_t LoadRenewal = nullptr;
LoadCasECMKeys_t LoadCasECMKeys = nullptr;
GetHDCPCapability_t GetHDCPCapability = nullptr;
GetDeviceID_t GetDeviceID = nullptr;
LoadTestKeybox_t LoadTestKeybox = nullptr;
SecurityLevel_t SecurityLevel = nullptr;
CreateEntitledKeySession_t CreateEntitledKeySession = nullptr;
RemoveEntitledKeySession_t RemoveEntitledKeySession = nullptr;
ReassociateEntitledKeySession_t ReassociateEntitledKeySession = nullptr;
APIVersion_t APIVersion = nullptr;
GetOEMKeyToken_t GetOEMKeyToken = nullptr;
GetSignatureHashAlgorithm_t GetSignatureHashAlgorithm = nullptr;
private:
bool initialize() {
dlerror();
std::string oemcrypto_path;
if (!Properties::GetOEMCryptoPath(&oemcrypto_path)) {
LOGE("Can't get oemc library path");
return false;
}
handle_ = dlopen(oemcrypto_path.c_str(), RTLD_NOW);
if (nullptr == handle_) {
LOGE("Can't open oemc library: %s", dlerror());
return false;
}
LOAD_SYM(Initialize);
LOAD_SYM(Terminate);
LOAD_SYM(OpenSession);
LOAD_SYM(CloseSession);
LOAD_SYM(GetProvisioningMethod);
LOAD_SYM(GetKeyData);
LOAD_SYM(SupportedCertificates);
LOAD_SYM(GenerateNonce);
LOAD_SYM(GenerateDerivedKeys);
LOAD_SYM(PrepAndSignLicenseRequest);
LOAD_SYM(PrepAndSignRenewalRequest);
LOAD_SYM(PrepAndSignProvisioningRequest);
LOAD_SYM(LoadProvisioning);
LOAD_SYM(GetOEMPublicCertificate);
LOAD_SYM(LoadDRMPrivateKey);
LOAD_SYM(GenerateRSASignature);
LOAD_SYM(DeriveKeysFromSessionKey);
LOAD_SYM(LoadLicense);
LOAD_SYM(LoadRenewal);
LOAD_SYM(LoadCasECMKeys);
LOAD_SYM(GetHDCPCapability);
LOAD_SYM(GetDeviceID);
LOAD_SYM(SecurityLevel);
LOAD_SYM(CreateEntitledKeySession);
LOAD_SYM(RemoveEntitledKeySession);
LOAD_SYM(APIVersion);
LOAD_SYM(ReassociateEntitledKeySession);
TRY_LOAD_SYM(GetOEMKeyToken);
TRY_LOAD_SYM(GetSignatureHashAlgorithm);
// Optional methods that may be available.
TRY_LOAD_SYM(LoadTestKeybox);
return true;
}
Impl() : handle_(nullptr) {}
void* handle_ = nullptr;
};
OEMCryptoInterface::OEMCryptoInterface() {}
OEMCryptoInterface::~OEMCryptoInterface() = default;
OEMCryptoResult OEMCryptoInterface::OEMCrypto_Initialize(void) {
if (!impl_) {
impl_ = Impl::create();
if (!impl_) {
return OEMCrypto_ERROR_INIT_FAILED;
}
}
return impl_->Initialize();
}
OEMCryptoResult OEMCryptoInterface::OEMCrypto_Terminate(void) {
return impl_->Terminate();
}
OEMCryptoResult OEMCryptoInterface::OEMCrypto_OpenSession(
OEMCrypto_SESSION* session) const {
return impl_->OpenSession(session);
}
OEMCryptoResult OEMCryptoInterface::OEMCrypto_CloseSession(
OEMCrypto_SESSION session) const {
return impl_->CloseSession(session);
}
OEMCrypto_ProvisioningMethod
OEMCryptoInterface::OEMCrypto_GetProvisioningMethod() const {
return impl_->GetProvisioningMethod();
}
OEMCryptoResult OEMCryptoInterface::OEMCrypto_GetKeyData(
uint8_t* keyData, size_t* keyDataLength) const {
return impl_->GetKeyData(keyData, keyDataLength);
}
uint32_t OEMCryptoInterface::OEMCrypto_SupportedCertificates() const {
return impl_->SupportedCertificates();
}
OEMCryptoResult OEMCryptoInterface::OEMCrypto_GenerateNonce(
OEMCrypto_SESSION session, uint32_t* nonce) const {
return impl_->GenerateNonce(session, nonce);
}
OEMCryptoResult OEMCryptoInterface::OEMCrypto_GenerateDerivedKeys(
OEMCrypto_SESSION session, const uint8_t* mac_key_context,
uint32_t mac_key_context_length, const uint8_t* enc_key_context,
uint32_t enc_key_context_length) const {
return impl_->GenerateDerivedKeys(session, mac_key_context,
mac_key_context_length, enc_key_context,
enc_key_context_length);
}
OEMCryptoResult OEMCryptoInterface::OEMCrypto_PrepAndSignLicenseRequest(
OEMCrypto_SESSION session, uint8_t* message, size_t message_length,
size_t* core_message_size, uint8_t* signature,
size_t* signature_length) const {
return impl_->PrepAndSignLicenseRequest(session, message, message_length,
core_message_size, signature,
signature_length);
}
OEMCryptoResult OEMCryptoInterface::OEMCrypto_PrepAndSignRenewalRequest(
OEMCrypto_SESSION session, uint8_t* message, size_t message_length,
size_t* core_message_size, uint8_t* signature,
size_t* signature_length) const {
return impl_->PrepAndSignRenewalRequest(session, message, message_length,
core_message_size, signature,
signature_length);
}
OEMCryptoResult OEMCryptoInterface::OEMCrypto_PrepAndSignProvisioningRequest(
OEMCrypto_SESSION session, uint8_t* message, size_t message_length,
size_t* core_message_size, uint8_t* signature,
size_t* signature_length) const {
return impl_->PrepAndSignProvisioningRequest(session, message, message_length,
core_message_size, signature,
signature_length);
}
OEMCryptoResult OEMCryptoInterface::OEMCrypto_LoadProvisioning(
OEMCrypto_SESSION session, const uint8_t* message, size_t message_length,
size_t core_message_length, const uint8_t* signature,
size_t signature_length, uint8_t* wrapped_private_key,
size_t* wrapped_private_key_length) const {
return impl_->LoadProvisioning(
session, message, message_length, core_message_length, signature,
signature_length, wrapped_private_key, wrapped_private_key_length);
}
OEMCryptoResult OEMCryptoInterface::OEMCrypto_GetOEMPublicCertificate(
OEMCrypto_SESSION session, uint8_t* public_cert,
size_t* public_cert_length) const {
return impl_->GetOEMPublicCertificate(session, public_cert,
public_cert_length);
}
OEMCryptoResult OEMCryptoInterface::OEMCrypto_LoadDRMPrivateKey(
OEMCrypto_SESSION session, OEMCrypto_PrivateKeyType key_type,
const uint8_t* wrapped_rsa_key, size_t wrapped_rsa_key_length) const {
return impl_->LoadDRMPrivateKey(session, key_type, wrapped_rsa_key,
wrapped_rsa_key_length);
}
OEMCryptoResult OEMCryptoInterface::OEMCrypto_GenerateRSASignature(
OEMCrypto_SESSION session, const uint8_t* message, size_t message_length,
uint8_t* signature, size_t* signature_length,
RSA_Padding_Scheme padding_scheme) const {
return impl_->GenerateRSASignature(session, message, message_length,
signature, signature_length,
padding_scheme);
}
OEMCryptoResult OEMCryptoInterface::OEMCrypto_DeriveKeysFromSessionKey(
OEMCrypto_SESSION session, const uint8_t* enc_session_key,
size_t enc_session_key_length, const uint8_t* mac_key_context,
size_t mac_key_context_length, const uint8_t* enc_key_context,
size_t enc_key_context_length) const {
return impl_->DeriveKeysFromSessionKey(
session, enc_session_key, enc_session_key_length, mac_key_context,
mac_key_context_length, enc_key_context, enc_key_context_length);
}
OEMCryptoResult OEMCryptoInterface::OEMCrypto_LoadLicense(
OEMCrypto_SESSION session, const uint8_t* message, size_t message_length,
size_t core_message_length, const uint8_t* signature,
size_t signature_length) const {
return impl_->LoadLicense(session, message, message_length,
core_message_length, signature, signature_length);
}
OEMCryptoResult OEMCryptoInterface::OEMCrypto_LoadRenewal(
OEMCrypto_SESSION session, const uint8_t* message, size_t message_length,
size_t core_message_length, const uint8_t* signature,
size_t signature_length) const {
return impl_->LoadRenewal(session, message, message_length,
core_message_length, signature, signature_length);
}
OEMCryptoResult OEMCryptoInterface::OEMCrypto_LoadCasECMKeys(
OEMCrypto_SESSION session, const uint8_t* message, size_t message_length,
const OEMCrypto_EntitledContentKeyObject* even_key,
const OEMCrypto_EntitledContentKeyObject* odd_key) const {
return impl_->LoadCasECMKeys(session, message, message_length, even_key,
odd_key);
}
OEMCryptoResult OEMCryptoInterface::OEMCrypto_GetHDCPCapability(
OEMCrypto_HDCP_Capability* current, OEMCrypto_HDCP_Capability* max) const {
return impl_->GetHDCPCapability(current, max);
}
OEMCryptoResult OEMCryptoInterface::OEMCrypto_GetDeviceID(
uint8_t* deviceID, size_t* idLength) const {
return impl_->GetDeviceID(deviceID, idLength);
}
OEMCryptoResult OEMCryptoInterface::OEMCrypto_LoadTestKeybox(
const uint8_t* buffer, size_t length) const {
// Optional method. Handle missing method.
if (impl_->LoadTestKeybox == nullptr) {
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
}
return impl_->LoadTestKeybox(buffer, length);
}
const char* OEMCryptoInterface::OEMCrypto_SecurityLevel() const {
return impl_->SecurityLevel();
}
OEMCryptoResult OEMCryptoInterface::OEMCrypto_CreateEntitledKeySession(
OEMCrypto_SESSION oec_session, OEMCrypto_SESSION* key_session) const {
return impl_->CreateEntitledKeySession(oec_session, key_session);
}
OEMCryptoResult OEMCryptoInterface::OEMCrypto_RemoveEntitledKeySession(
OEMCrypto_SESSION key_session) const {
return impl_->RemoveEntitledKeySession(key_session);
}
OEMCryptoResult OEMCryptoInterface::OEMCrypto_ReassociateEntitledKeySession(
OEMCrypto_SESSION key_session, OEMCrypto_SESSION oec_session) const {
return impl_->ReassociateEntitledKeySession(key_session, oec_session);
}
uint32_t OEMCryptoInterface::OEMCrypto_APIVersion() const {
return impl_->APIVersion();
}
OEMCryptoResult OEMCryptoInterface::OEMCrypto_GetOEMKeyToken(
OEMCrypto_SESSION key_session, uint8_t* key_token,
size_t* key_token_length) const {
// Optional method. Handle missing method.
if (impl_->GetOEMKeyToken == nullptr) {
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
}
return impl_->GetOEMKeyToken(key_session, key_token, key_token_length);
}
OEMCryptoResult OEMCryptoInterface::OEMCrypto_GetSignatureHashAlgorithm(
OEMCrypto_SESSION session,
OEMCrypto_SignatureHashAlgorithm* algorithm) const {
// Optional method. Handle missing method.
if (impl_->GetOEMKeyToken == nullptr) {
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
}
return impl_->GetSignatureHashAlgorithm(session, algorithm);
}
} // namespace wvcas

View File

@@ -0,0 +1,368 @@
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine Master
// License Agreement.
#include "policy_engine.h"
#include <limits.h>
#include <sstream>
#include "log.h"
#include "string_conversions.h"
// TODO(vtarasov):
//#include "properties.h"
//#include "cas_event_listener.h"
using video_widevine::License;
namespace {
static constexpr int kPolicyTimerDurationSeconds = 1;
static constexpr int kClockSkewDelta = 5; // seconds
// Use 0 to represent never expired license as specified in EME spec
// (NaN in JS translates to 0 in unix timestamp).
static constexpr int64_t NEVER_EXPIRES = 0;
} // namespace
namespace wvcas {
bool PolicyEngine::CanDecryptContent(const KeyId& key_id) const {
if (license_keys_->IsContentKey(key_id)) {
return license_keys_->CanDecryptContent(key_id);
} else {
LOGE("PolicyEngine::CanDecryptContent Key '%s' not in license.",
wvutil::b2a_hex(key_id).c_str());
return false;
}
}
void PolicyEngine::SetLicense(const License& license) {
license_id_.Clear();
license_id_.CopyFrom(license.id());
policy_.Clear();
license_keys_->SetFromLicense(license);
UpdateLicense(license);
}
void PolicyEngine::UpdateLicense(const License& license) {
if (!license.has_policy()) return;
if (kLicenseStateExpired == license_state_) {
LOGD("PolicyEngine::UpdateLicense: updating an expired license");
}
policy_.MergeFrom(license.policy());
// some basic license validation
// license start time needs to be specified in the initial response
if (!license.has_license_start_time()) return;
// if renewal, discard license if version has not been updated
if (license_state_ != kLicenseStateInitial && policy_.can_play()) {
if (license.id().version() > license_id_.version())
license_id_.CopyFrom(license.id());
else
return;
}
// Update time information
license_start_time_ = license.license_start_time();
next_renewal_time_ = license_start_time_ + policy_.renewal_delay_seconds();
int64_t current_time = GetCurrentTime();
if ((!policy_.can_play() ||
HasLicenseOrPlaybackDurationExpired(current_time)) &&
license_state_ != kLicenseStateExpired) {
license_state_ = kLicenseStateExpired;
NotifyLicenseExpired(license_state_);
NotifyKeysChange(kKeyStatusExpired);
return;
}
// Update state
if (current_time >= license_start_time_) {
license_state_ = kLicenseStateCanPlay;
NotifyKeysChange(kKeyStatusUsable);
} else {
license_state_ = kLicenseStatePending;
NotifyKeysChange(kKeyStatusPending);
}
NotifyExpirationUpdate(current_time);
NotifyRenewalServerUpdate();
}
int64_t PolicyEngine::GetCurrentTime() {
int64_t current_time = clock_->GetCurrentTime();
if (current_time + kClockSkewDelta < last_recorded_current_time_)
current_time = last_recorded_current_time_;
else
last_recorded_current_time_ = current_time;
return current_time;
}
void PolicyEngine::NotifyRenewalServerUpdate() {
if (policy_.renewal_server_url() != renewal_server_url_) {
renewal_server_url_ = policy_.renewal_server_url();
if (event_listener_) {
event_listener_->OnNewRenewalServerUrl(renewal_server_url_);
}
}
}
void PolicyEngine::NotifyLicenseExpired(LicenseState license_state) {
if (event_listener_ && license_state == kLicenseStateExpired) {
event_listener_->OnLicenseExpiration();
}
}
// Apply a key status to the current keys.
// If this represents a new key status, perform a notification callback.
// NOTE: if the new status is kKeyStatusUsable, the HDCP check may result in an
// override to kKeyStatusOutputNotAllowed.
void PolicyEngine::NotifyKeysChange(KeyStatus new_status) {
bool keys_changed;
bool has_new_usable_key = false;
if (new_status == kKeyStatusUsable) {
CheckDeviceHdcpStatus();
}
keys_changed =
license_keys_->ApplyStatusChange(new_status, &has_new_usable_key);
if (event_listener_ && keys_changed) {
KeyStatusMap content_keys;
license_keys_->ExtractKeyStatuses(&content_keys);
event_listener_->OnSessionKeysChange(content_keys, has_new_usable_key);
}
}
bool PolicyEngine::HasLicenseOrPlaybackDurationExpired(int64_t current_time) {
const int64_t expiry_time =
GetExpiryTime(current_time,
/* ignore_soft_enforce_playback_duration */ false);
return expiry_time != NEVER_EXPIRES && expiry_time <= current_time;
}
void PolicyEngine::NotifyExpirationUpdate(int64_t current_time) {
const int64_t expiry_time =
GetExpiryTime(current_time,
/* ignore_soft_enforce_playback_duration */ false);
if (!last_expiry_time_set_ || expiry_time != last_expiry_time_) {
last_expiry_time_ = expiry_time;
if (event_listener_) event_listener_->OnExpirationUpdate(expiry_time);
}
last_expiry_time_set_ = true;
}
int64_t PolicyEngine::GetExpiryTime(
int64_t current_time, bool ignore_soft_enforce_playback_duration) {
if (!HasPlaybackStarted(current_time)) return GetRentalExpiryTime();
const int64_t hard_limit = GetHardLicenseExpiryTime();
if (policy_.playback_duration_seconds() == 0) return hard_limit;
if (!ignore_soft_enforce_playback_duration && !was_expired_on_load_ &&
policy_.soft_enforce_playback_duration()) {
return hard_limit;
}
const int64_t expiry_time =
playback_start_time_ + policy_.playback_duration_seconds();
if (hard_limit == NEVER_EXPIRES) return expiry_time;
return std::min(hard_limit, expiry_time);
}
void PolicyEngine::CheckDeviceHdcpStatusOnTimer(int64_t current_time) {
if (current_time >= next_device_check_) {
CheckDeviceHdcpStatus();
next_device_check_ = current_time + HDCP_DEVICE_CHECK_INTERVAL;
}
}
int64_t PolicyEngine::GetRentalExpiryTime() {
const int64_t hard_limit = GetHardLicenseExpiryTime();
if (policy_.rental_duration_seconds() == 0) return hard_limit;
const int64_t expiry_time =
license_start_time_ + policy_.rental_duration_seconds();
if (hard_limit == NEVER_EXPIRES) return expiry_time;
return std::min(hard_limit, expiry_time);
}
// For the policy time fields checked in the following methods, a value of 0
// indicates that there is no limit to the duration. If the fields are zero
// (including the hard limit) then these methods will return NEVER_EXPIRES.
int64_t PolicyEngine::GetHardLicenseExpiryTime() {
return policy_.license_duration_seconds() > 0
? license_start_time_ + policy_.license_duration_seconds()
: NEVER_EXPIRES;
}
void PolicyEngine::CheckDeviceHdcpStatus() {
if (!license_keys_->Empty()) {
HdcpCapability current_hdcp_level;
HdcpCapability ignored;
if (!crypto_session_->GetHdcpCapabilities(&current_hdcp_level, &ignored)) {
current_hdcp_level = HDCP_NONE;
}
license_keys_->ApplyConstraints(current_resolution_, current_hdcp_level);
}
}
void PolicyEngine::BeginDecryption() {
if (playback_start_time_ == 0) {
switch (license_state_) {
case kLicenseStateCanPlay:
case kLicenseStateNeedRenewal:
case kLicenseStateWaitingLicenseUpdate:
playback_start_time_ = GetCurrentTime();
last_playback_time_ = playback_start_time_;
if (policy_.play_start_grace_period_seconds() == 0)
grace_period_end_time_ = playback_start_time_;
if (policy_.renew_with_usage()) {
license_state_ = kLicenseStateNeedRenewal;
}
NotifyExpirationUpdate(playback_start_time_);
break;
case kLicenseStateInitial:
case kLicenseStatePending:
case kLicenseStateExpired:
NotifyLicenseExpired(license_state_);
break;
default:
break;
}
}
}
void PolicyEngine::OnTimerEvent() {
last_recorded_current_time_ += kPolicyTimerDurationSeconds;
int64_t current_time = GetCurrentTime();
// If we have passed the grace period, the expiration will update.
if (grace_period_end_time_ == 0 && HasPlaybackStarted(current_time)) {
grace_period_end_time_ = playback_start_time_;
NotifyExpirationUpdate(current_time);
}
// License expiration trumps all.
if (HasLicenseOrPlaybackDurationExpired(current_time) &&
license_state_ != kLicenseStateExpired) {
license_state_ = kLicenseStateExpired;
NotifyLicenseExpired(license_state_);
NotifyKeysChange(kKeyStatusExpired);
return;
}
// Check device conditions that affect playability (HDCP, resolution)
CheckDeviceHdcpStatusOnTimer(current_time);
bool renewal_needed = false;
// Test to determine if renewal should be attempted.
switch (license_state_) {
case kLicenseStateCanPlay: {
if (HasRenewalDelayExpired(current_time)) {
renewal_needed = true;
}
// HDCP may change, so force a check.
NotifyKeysChange(kKeyStatusUsable);
break;
}
case kLicenseStateNeedRenewal: {
renewal_needed = true;
break;
}
case kLicenseStateWaitingLicenseUpdate: {
if (HasRenewalRetryIntervalExpired(current_time)) {
renewal_needed = true;
}
break;
}
case kLicenseStatePending: {
if (current_time >= license_start_time_) {
license_state_ = kLicenseStateCanPlay;
NotifyKeysChange(kKeyStatusUsable);
}
break;
}
case kLicenseStateInitial:
case kLicenseStateExpired: {
NotifyLicenseExpired(license_state_);
break;
}
default: {
license_state_ = kLicenseStateExpired;
NotifyLicenseExpired(license_state_);
NotifyKeysChange(kKeyStatusInternalError);
break;
}
}
if (renewal_needed) {
UpdateRenewalRequest(current_time);
if (event_listener_) {
event_listener_->OnSessionRenewalNeeded();
}
}
}
bool PolicyEngine::HasRenewalDelayExpired(int64_t current_time) {
return policy_.can_renew() && (policy_.renewal_delay_seconds() > 0) &&
license_start_time_ + policy_.renewal_delay_seconds() <= current_time;
}
bool PolicyEngine::HasRenewalRetryIntervalExpired(int64_t current_time) {
return policy_.can_renew() &&
(policy_.renewal_retry_interval_seconds() > 0) &&
next_renewal_time_ <= current_time;
}
void PolicyEngine::UpdateRenewalRequest(int64_t current_time) {
license_state_ = kLicenseStateWaitingLicenseUpdate;
next_renewal_time_ = current_time + policy_.renewal_retry_interval_seconds();
}
void PolicyEngine::SetEntitledLicenseKeys(
const std::vector<WidevinePsshData_EntitledKey>& entitled_keys) {
license_keys_->SetEntitledKeys(entitled_keys);
}
std::unique_ptr<wvcas::LicenseKeys> PolicyEngine::CreateLicenseKeys() {
return make_unique<wvcas::LicenseKeys>();
}
std::unique_ptr<wvutil::Clock> PolicyEngine::CreateClock() {
return make_unique<wvutil::Clock>();
}
void PolicyEngine::RestorePlaybackTimes(int64_t playback_start_time,
int64_t last_playback_time,
int64_t grace_period_end_time) {
playback_start_time_ = (playback_start_time > 0) ? playback_start_time : 0;
last_playback_time_ = (last_playback_time > 0) ? last_playback_time : 0;
grace_period_end_time_ = grace_period_end_time;
if (policy_.play_start_grace_period_seconds() != 0) {
// If we are using grace period, we may need to override some of the values
// given to us by OEMCrypto. |grace_period_end_time| will be 0 if the grace
// period has not expired (effectively playback has not begun). Otherwise,
// |grace_period_end_time| contains the playback start time we should use.
playback_start_time_ = grace_period_end_time;
}
const int64_t current_time = GetCurrentTime();
const int64_t expiry_time =
GetExpiryTime(current_time,
/* ignore_soft_enforce_playback_duration */ true);
was_expired_on_load_ =
expiry_time != NEVER_EXPIRES && expiry_time < current_time;
NotifyExpirationUpdate(current_time);
}
} // namespace wvcas

View File

@@ -0,0 +1,931 @@
#include "widevine_cas_api.h"
#include <openssl/rand.h>
#include <openssl/sha.h>
#include <limits>
#include "cas_events.h"
#include "cas_util.h"
#include "license_protocol.pb.h"
#include "log.h"
#include "media_cas.pb.h"
#include "string_conversions.h"
#include "widevine_cas_session_map.h"
constexpr char kBasePathPrefix[] = "/data/vendor/mediacas/IDM/widevine/";
constexpr char kCertFileBase[] = "cert.bin";
constexpr char kLicenseFileNameSuffix[] = ".lic";
namespace {
bool ReadFileFromStorage(wvutil::FileSystem& file_system,
const std::string& filename, std::string* file_data) {
if (nullptr == file_data) {
return false;
}
if (!file_system.Exists(filename)) {
return false;
}
size_t filesize = file_system.FileSize(filename);
if (0 == filesize) {
return false;
}
file_data->resize(filesize);
std::unique_ptr<wvutil::File> file =
file_system.Open(filename, wvutil::FileSystem::kReadOnly);
if (nullptr == file) {
return false;
}
size_t bytes_read = file->Read(&(*file_data)[0], file_data->size());
if (bytes_read != filesize) {
return false;
}
return true;
}
bool RemoveFile(wvutil::FileSystem& file_system, const std::string& filename) {
if (!file_system.Exists(filename)) {
return false;
}
if (!file_system.Remove(filename)) {
return false;
}
return true;
}
bool StoreFile(wvutil::FileSystem& file_system, const std::string& filename,
const std::string& file_data) {
std::unique_ptr<wvutil::File> file(file_system.Open(
filename, wvutil::FileSystem::kTruncate | wvutil::FileSystem::kCreate));
if (nullptr == file) {
return false;
}
size_t bytes_written = file->Write(file_data.data(), file_data.size());
if (bytes_written != file_data.size()) {
return false;
}
return true;
}
std::string GenerateLicenseFilename(const std::string& content_id,
const std::string& provider_id) {
std::string data(content_id + provider_id);
std::string hash;
hash.resize(SHA256_DIGEST_LENGTH);
const unsigned char* input =
reinterpret_cast<const unsigned char*>(data.data());
unsigned char* output = reinterpret_cast<unsigned char*>(&hash[0]);
SHA256(input, data.size(), output);
return std::string(std::string(kBasePathPrefix) + wvutil::b2a_hex(hash) +
std::string(kLicenseFileNameSuffix));
}
std::string GenerateMultiContentLicenseInfo(
const std::string& license_id,
const std::vector<std::string>& content_list) {
std::string message;
if (license_id.empty() || content_list.empty()) {
return message;
}
message.push_back(MultiContentLicenseFieldType::MULTI_CONTENT_LICENSE_ID);
message.push_back((license_id.size() >> 8) & 0xff);
message.push_back(license_id.size() & 0xff);
message.append(license_id);
for (const auto& content_id : content_list) {
message.push_back(
MultiContentLicenseFieldType::MULTI_CONTENT_LICENSE_CONTENT_ID);
message.push_back((content_id.size() >> 8) & 0xff);
message.push_back(content_id.size() & 0xff);
message.append(content_id);
}
return message;
}
std::string GenerateGroupLicenseInfo(const std::string& license_id,
const std::string group_id) {
std::string message;
if (license_id.empty() || group_id.empty()) {
return message;
}
message.push_back(GroupLicenseFieldType::GROUP_LICENSE_ID);
message.push_back((license_id.size() >> 8) & 0xff);
message.push_back(license_id.size() & 0xff);
message.append(license_id);
message.push_back(GroupLicenseFieldType::GROUP_LICENSE_GROUP_ID);
message.push_back((group_id.size() >> 8) & 0xff);
message.push_back(group_id.size() & 0xff);
message.append(group_id);
return message;
}
// Generates a random number between 1 and |range_to|, all inclusive.
uint32_t GetRandom(uint32_t range_to) {
if (range_to <= 1) {
return 1;
}
constexpr uint32_t max_val = std::numeric_limits<uint32_t>::max();
// Keep searching for a random value in a range divisible by |range_to|.
// Worst case we have 1/2 chance to end the loop on each roll.
uint32_t generated;
do {
RAND_bytes(reinterpret_cast<uint8_t*>(&generated), /*len=*/4);
} while (generated >= (max_val - (max_val % range_to)));
return 1 + (generated % range_to);
}
} // namespace
namespace wvcas {
class MediaContext : public CasMediaId {
public:
MediaContext() : CasMediaId() {}
~MediaContext() override {}
MediaContext(const MediaContext&) = delete;
MediaContext& operator=(const MediaContext&) = delete;
const std::string content_id() override { return pssh_.content_id(); }
const std::string provider_id() override { return pssh_.provider(); }
bool is_entitlement_rotation_enabled() override {
return pssh_.has_entitlement_period_index();
}
uint32_t entitlement_period_index() override {
return pssh_.entitlement_period_index();
}
std::string get_init_data() override { return pssh_.SerializeAsString(); }
CasStatus initialize(const std::string& init_data) override {
if (!pssh_.ParseFromString(init_data)) {
return CasStatus(CasStatusCode::kInvalidParameter, "invalid init_data");
}
return CasStatusCode::kNoError;
}
private:
video_widevine::WidevinePsshData pssh_;
};
std::unique_ptr<CasMediaId> CasMediaId::create() {
std::unique_ptr<MediaContext> ctx = make_unique<MediaContext>();
return std::move(ctx);
}
std::shared_ptr<CryptoSession> WidevineCas::getCryptoSession() {
return std::make_shared<CryptoSession>();
}
std::unique_ptr<CasLicense> WidevineCas::getCasLicense() {
return make_unique<CasLicense>();
}
std::unique_ptr<wvutil::FileSystem> WidevineCas::getFileSystem() {
return make_unique<wvutil::FileSystem>();
}
std::shared_ptr<WidevineCasSession> WidevineCas::newCasSession() {
return std::make_shared<WidevineCasSession>();
}
std::unique_ptr<EcmParser> WidevineCas::getEcmParser(const CasEcm& ecm) const {
return EcmParser::Create(ecm);
}
void WidevineCas::OnTimerEvent() {
std::unique_lock<std::mutex> locker(lock_);
if (cas_license_.get() != nullptr) {
cas_license_->OnTimerEvent();
// Delete expired license after firing expired event in policy_engine
if (cas_license_->IsExpired() && (media_id_.get() != nullptr)) {
std::string filename = license_id_ + kLicenseFileNameSuffix;
if (!file_system_->Exists(filename)) {
LOGI("No expired license file stored in disk");
} else {
if (RemoveFile(*file_system_, filename)) {
LOGI("Remove expired license file from disk successfully.");
}
}
}
}
}
CasStatus WidevineCas::initialize(CasEventListener* event_listener) {
std::unique_lock<std::mutex> locker(lock_);
crypto_session_ = getCryptoSession();
// For session name generation.
srand(time(nullptr));
// Setup an oemcrypto session.
CasStatus status = crypto_session_->initialize();
if (!status.ok()) {
LOGE("WidevineCas initialization failed: %d", status.status_code());
return status;
}
file_system_ = getFileSystem();
cas_license_ = getCasLicense();
status = cas_license_->initialize(crypto_session_, event_listener);
if (!status.ok()) {
LOGE("WidevineCas initialization failed: %d", status.status_code());
return status;
}
std::string cert_filename_path(std::string(kBasePathPrefix) +
std::string(kCertFileBase));
// Try to read a certificate if one exists. If any error occurs, just ignore
// it and let new cert file overwrite the existing file.
std::string cert_file;
if (ReadFileFromStorage(*file_system_, cert_filename_path, &cert_file)) {
LOGI("read cert.bin successfully");
if (!HandleStoredDrmCert(cert_file).ok()) {
return CasStatusCode::kNoError;
}
}
event_listener_ = event_listener;
return CasStatusCode::kNoError;
}
// TODO(jfore): Split out the functionality and move the callback out of this
// class.
CasStatus WidevineCas::openSession(WvCasSessionId* sessionId) {
std::unique_lock<std::mutex> locker(lock_);
if (nullptr == sessionId) {
return CasStatus(CasStatusCode::kInvalidParameter,
"missing openSession sessionId");
}
CasSessionPtr session = newCasSession();
CasStatus status =
session->initialize(crypto_session_, event_listener_, sessionId);
if (CasStatusCode::kNoError != status.status_code()) {
return status;
}
WidevineCasSessionMap::instance().AddSession(*sessionId, session);
return CasStatusCode::kNoError;
}
CasStatus WidevineCas::closeSession(const WvCasSessionId& sessionId) {
std::unique_lock<std::mutex> locker(lock_);
CasSessionPtr session =
WidevineCasSessionMap::instance().GetSession(sessionId);
// TODO(jfore): Add a log event if the session doesn't exist and perhaps raise
// an error.`
if (session == nullptr) {
return CasStatus(CasStatusCode::kSessionNotFound, "unknown session id");
}
WidevineCasSessionMap::instance().RemoveSession(sessionId);
return CasStatusCode::kNoError;
}
CasStatus WidevineCas::processEmm(const CasEmm& emm) {
LOGI("WidevineCas::processEmm.");
std::unique_ptr<const EmmParser> emm_parser = getEmmParser(emm);
if (emm_parser == nullptr) {
return CasStatus(CasStatusCode::kInvalidParameter, "Unable to parse emm");
}
if (event_listener_ == nullptr) {
LOGW("processEmm: Event listener is not initialized.");
return CasStatusCode::kNoError;
}
// TODO(b/): Verify signature.
// TODO(b/): Update EMM timer.
const video_widevine::EmmPayload& emm_payload = emm_parser->emm_payload();
// Process fingerprinting info.
std::set<CasData> current_fingerprinting_events_;
for (int i = 0; i < emm_payload.fingerprinting_size(); ++i) {
CasData message =
GenerateFingerprintingEventMessage(emm_payload.fingerprinting(i));
if (message.empty()) {
continue;
}
if (last_fingerprinting_events_.find(message) ==
last_fingerprinting_events_.end()) {
event_listener_->OnFingerprintingUpdated(message);
}
current_fingerprinting_events_.insert(message);
}
last_fingerprinting_events_.clear();
last_fingerprinting_events_.insert(current_fingerprinting_events_.begin(),
current_fingerprinting_events_.end());
// Process service blocking info.
std::set<CasData> current_service_blocking_events_;
for (int i = 0; i < emm_payload.service_blocking_size(); ++i) {
CasData message =
GenerateServiceBlockingEventMessage(emm_payload.service_blocking(i));
if (message.empty()) {
continue;
}
if (last_service_blocking_events_.find(message) ==
last_service_blocking_events_.end()) {
event_listener_->OnServiceBlockingUpdated(message);
}
current_service_blocking_events_.insert(message);
}
last_service_blocking_events_.clear();
last_service_blocking_events_.insert(current_service_blocking_events_.begin(),
current_service_blocking_events_.end());
return CasStatusCode::kNoError;
}
// TODO(jfore): Add unit test to widevine_cas_api_test.cpp that is added in
// another cl.
CasStatus WidevineCas::processEcm(const WvCasSessionId& sessionId,
const CasEcm& ecm) {
LOGD("WidevineCasPlugin::processEcm");
std::unique_lock<std::mutex> locker(lock_);
// If we don't have a license yet, save the ecm and session id.
if (!has_license_) {
// In the case of entitlement key rotation enabled, the caller is expected
// to call processEcm first (before processPrivateData), so we know which
// entitlement period index to request when requesting license.
TryExtractEntitlementPeriodIndex(ecm);
deferred_ecms_.emplace(sessionId, ecm);
return CasStatusCode::kDeferedEcmProcessing;
}
return HandleProcessEcm(sessionId, ecm);
}
CasStatus WidevineCas::HandleProcessEcm(const WvCasSessionId& sessionId,
const CasEcm& ecm) {
if (cas_license_->IsExpired()) {
return CasStatus(CasStatusCode::kCasLicenseError,
"license is expired, unable to process ecm");
}
CasSessionPtr session =
WidevineCasSessionMap::instance().GetSession(sessionId);
if (session == nullptr) {
return CasStatus(CasStatusCode::kSessionNotFound,
"unknown session for processEcm");
}
uint8_t ecm_age_previous = session->GetEcmAgeRestriction();
CasStatus status =
session->processEcm(ecm, parental_control_age_, license_group_id_);
uint8_t ecm_age_current = session->GetEcmAgeRestriction();
if (event_listener_ != nullptr && ecm_age_current != ecm_age_previous) {
event_listener_->OnAgeRestrictionUpdated(sessionId, ecm_age_current);
}
if (media_id_ != nullptr && media_id_->is_entitlement_rotation_enabled()) {
CheckEntitlementPeriodUpdate(session->GetEntitlementPeriodIndex(),
session->GetEntitlementRotationWindowLeft());
}
if (status.ok()) {
cas_license_->BeginDecryption();
}
return status;
}
CasStatus WidevineCas::HandleDeferredECMs() {
for (const auto& deferred_ecm : deferred_ecms_) {
CasStatus status =
HandleProcessEcm(deferred_ecm.first, deferred_ecm.second);
if (!status.ok()) {
return status;
}
}
deferred_ecms_.clear();
return CasStatusCode::kNoError;
}
CasStatus WidevineCas::generateDeviceProvisioningRequest(
std::string* provisioning_request) {
std::unique_lock<std::mutex> locker(lock_);
if (provisioning_request == nullptr) {
return CasStatus(CasStatusCode::kInvalidParameter,
"missing output buffer for provisioning request");
}
return cas_license_->GenerateDeviceProvisioningRequest(provisioning_request);
}
CasStatus WidevineCas::handleProvisioningResponse(const std::string& response) {
if (response.empty()) {
return CasStatus(CasStatusCode::kCasLicenseError,
"empty individualization response");
}
std::string device_file;
std::unique_lock<std::mutex> locker(lock_);
CasStatus status = cas_license_->HandleDeviceProvisioningResponse(
response, &device_certificate_, &wrapped_rsa_key_, &device_file);
if (!status.ok()) {
return status;
}
if (!device_file.empty()) {
std::string cert_filename(std::string(kBasePathPrefix) +
std::string(kCertFileBase));
StoreFile(*file_system_, cert_filename, device_file);
}
return CasStatusCode::kNoError;
}
CasStatus WidevineCas::generateEntitlementRequest(
const std::string& init_data, std::string* entitlement_request,
std::string& license_id) {
media_id_ = CasMediaId::create();
CasStatus status = media_id_->initialize(init_data);
if (!status.ok()) {
return status;
}
std::string filename;
// Backward compatible. If the license_filename is unrequested by app, plugin
// will directly use the single_content_license named "content_id +
// provider_id" by default.
if (requested_license_id_.empty()) {
filename = GenerateLicenseFilename(media_id_->content_id(),
media_id_->provider_id());
} else {
filename = requested_license_id_ + kLicenseFileNameSuffix;
// Clean up the assigned_license_filename for next round use.
requested_license_id_.clear();
}
// An offline license file is successfully loaded.
if (TryReuseStoredLicense(filename)) {
// If license file is expired, don't proceed the request. Also
// delete the stored license file.
std::unique_lock<std::mutex> locker(lock_);
if (cas_license_->IsExpired()) {
if (!RemoveFile(*file_system_, filename)) {
return CasStatus(CasStatusCode::kInvalidLicenseFile,
"unable to remove expired license file from disk");
}
LOGI("Remove expired license file from disk successfully.");
return CasStatus(CasStatusCode::kCasLicenseError,
"license is expired, unable to process emm");
}
license_id =
filename.substr(0, filename.size() - strlen(kLicenseFileNameSuffix));
if (cas_license_->IsGroupLicense()) {
license_group_id_ = cas_license_->GetGroupId();
}
// Save current in use license_id. The purpose is to make the license_id
// available for license removal or license expiration.
license_id_ = license_id;
policy_timer_.Start(this, 1);
has_license_ = true;
return HandleDeferredECMs();
}
if (entitlement_request == nullptr) {
return CasStatus(CasStatusCode::kInvalidParameter,
"missing output buffer for entitlement request");
}
std::unique_lock<std::mutex> locker(lock_);
return cas_license_->GenerateEntitlementRequest(
init_data, device_certificate_, wrapped_rsa_key_, license_type_,
entitlement_request);
}
CasStatus WidevineCas::handleEntitlementResponse(
const std::string& response, std::string& license_id,
std::string& multi_content_license_info, std::string& group_license_info) {
if (response.empty()) {
return CasStatus(CasStatusCode::kCasLicenseError,
"empty entitlement response");
}
if (media_id_ == nullptr) {
return CasStatus(CasStatusCode::kCasLicenseError, "No media id");
}
std::string device_file;
std::unique_lock<std::mutex> locker(lock_);
CasStatus status =
cas_license_->HandleEntitlementResponse(response, &device_file);
if (status.ok()) {
// A license has been successfully loaded. Load any ecms that may have been
// deferred waiting for the license.
if (cas_license_->IsGroupLicense()) {
license_group_id_ = cas_license_->GetGroupId();
}
has_license_ = true;
status = HandleDeferredECMs();
if (!status.ok()) {
return status;
}
policy_timer_.Start(this, 1);
if (!device_file.empty()) {
const std::string license_group_id = cas_license_->GetGroupId();
std::string filename = GenerateLicenseFilename(
license_group_id.empty() ? media_id_->content_id() : license_group_id,
media_id_->provider_id());
StoreFile(*file_system_, filename, device_file);
// license_id will be the filename without ".lic" extension.
license_id =
filename.substr(0, filename.size() - strlen(kLicenseFileNameSuffix));
// Save the license id.
license_id_ = license_id;
// License info is only needed if the license is stored.
if (cas_license_->IsMultiContentLicense()) {
multi_content_license_info = GenerateMultiContentLicenseInfo(
license_id, cas_license_->GetContentIdList());
}
if (cas_license_->IsGroupLicense()) {
group_license_info =
GenerateGroupLicenseInfo(license_id, license_group_id);
}
}
}
return status;
}
CasStatus WidevineCas::generateEntitlementRenewalRequest(
std::string* entitlement_renewal_request) {
if (entitlement_renewal_request == nullptr) {
return CasStatus(CasStatusCode::kInvalidParameter,
"missing output buffer for entitlement renewal request");
}
return cas_license_->GenerateEntitlementRenewalRequest(
device_certificate_, entitlement_renewal_request);
}
CasStatus WidevineCas::handleEntitlementRenewalResponse(
const std::string& response, std::string& license_id) {
if (response.empty()) {
return CasStatus(CasStatusCode::kCasLicenseError,
"empty entitlement renewal response");
}
std::string device_file;
std::unique_lock<std::mutex> locker(lock_);
CasStatus status =
cas_license_->HandleEntitlementRenewalResponse(response, &device_file);
if (!status.ok()) {
return status;
}
if (!device_file.empty() && media_id_ != nullptr) {
const std::string license_group_id = cas_license_->GetGroupId();
std::string filename = GenerateLicenseFilename(
license_group_id.empty() ? media_id_->content_id() : license_group_id,
media_id_->provider_id());
StoreFile(*file_system_, filename, device_file);
// TODO(chelu): The license id should not change, right?
// license_id will be the filename without ".lic" extension.
license_id =
filename.substr(0, filename.size() - strlen(kLicenseFileNameSuffix));
license_id_ = license_id;
}
return CasStatusCode::kNoError;
}
CasStatus WidevineCas::generateEntitlementPeriodUpdateRequest(
const std::string& init_data) {
std::unique_lock<std::mutex> locker(lock_);
next_media_id_ = CasMediaId::create();
CasStatus status = next_media_id_->initialize(init_data);
if (!status.ok()) {
return status;
}
// Setup a new OEMCrypto session.
next_crypto_session_ = getCryptoSession();
status = next_crypto_session_->initialize();
if (!status.ok()) {
LOGE("WidevineCas new oemcrypto session failed: %d", status.status_code());
return status;
}
// Setup a new CasLicense.
next_cas_license_ = getCasLicense();
status = next_cas_license_->initialize(next_crypto_session_, event_listener_);
if (!status.ok()) {
LOGE("WidevineCas new license initialize failed: %d", status.status_code());
return status;
}
std::string entitlement_request;
status = next_cas_license_->GenerateEntitlementRequest(
init_data, device_certificate_, wrapped_rsa_key_, license_type_,
&entitlement_request);
if (!status.ok()) {
LOGE("WidevineCas generate entitlement request failed: %d",
status.status_code());
return status;
}
if (event_listener_ == nullptr) {
LOGE("No event listener");
return CasStatus(CasStatusCode::kUnknownError, "No event listener");
}
event_listener_->OnEntitlementPeriodUpdateNeeded(entitlement_request);
return CasStatusCode::kNoError;
}
CasStatus WidevineCas::handleEntitlementPeriodUpdateResponse(
const std::string& response, std::string& license_id) {
std::unique_lock<std::mutex> locker(lock_);
if (next_media_id_ == nullptr || next_crypto_session_ == nullptr ||
next_cas_license_ == nullptr) {
return CasStatus(CasStatusCode::kInvalidParameter,
"Must generate entitlement switch request first.");
}
// Install the new license.
std::string device_file;
CasStatus status =
next_cas_license_->HandleEntitlementResponse(response, &device_file);
if (!status.ok()) {
LOGE("WidevineCas install new license failed: %d", status.status_code());
return status;
}
// License has been successfully installed. Switch to use it across all
// sessions.
for (const auto& session :
WidevineCasSessionMap::instance().GetAllSessions()) {
status = session->resetCryptoSession(next_crypto_session_);
if (!status.ok()) {
// Some of the sessions may have already been reassociated (unlikely to
// happen). Here we continue process ignoring the errors. Some sessions
// will become unusable.
LOGE("resetCryptoSession failed, error %d: %s", status.status_code(),
status.error_string().c_str());
}
}
// Close the current OEMCrypto session.
crypto_session_->close();
// Apply the new crypto session and cas license.
crypto_session_ = std::move(next_crypto_session_);
cas_license_ = std::move(next_cas_license_);
media_id_ = std::move(next_media_id_);
// Store offline license.
if (!device_file.empty()) {
std::string filename = GenerateLicenseFilename(media_id_->content_id(),
media_id_->provider_id());
StoreFile(*file_system_, filename, device_file);
// license_id will be the filename without ".lic" extension.
license_id =
filename.substr(0, filename.size() - strlen(kLicenseFileNameSuffix));
}
return status;
}
CasStatus WidevineCas::RemoveLicense(const std::string& file_name) {
// Check if the license is in use. If it is, besides removing the license,
// update policy in current license. Else, we just directly remove it.
if (media_id_ == nullptr) {
return CasStatus(CasStatusCode::kCasLicenseError, "No media id");
}
// Remove the license file given the file_name user provides.
if (!RemoveFile(*file_system_, file_name)) {
return CasStatus(CasStatusCode::kInvalidLicenseFile,
"unable to remove license file from disk");
}
LOGI("Remove license file from disk successfully.");
std::string current_license_filename = license_id_ + kLicenseFileNameSuffix;
if (file_name == current_license_filename) {
// Update license policy for the in-used license. Plugin will not allowed to
// play stream, store and renew license unless a new plugin instance is
// created.
std::unique_lock<std::mutex> locker(lock_);
cas_license_->UpdateLicenseForLicenseRemove();
}
return CasStatusCode::kNoError;
}
bool WidevineCas::is_provisioned() const {
return (!(device_certificate_.empty() || wrapped_rsa_key_.empty()));
}
CasStatus WidevineCas::ProcessCAPrivateData(const CasData& private_data,
std::string* init_data) {
if (init_data == nullptr) {
return CasStatus(CasStatusCode::kInvalidParameter,
"missing output buffer for init_data");
}
// Parse provider and content id from CA descriptor.
video_widevine::CaDescriptorPrivateData descriptor;
descriptor.ParseFromArray(private_data.data(), private_data.size());
if (!descriptor.has_content_id() || !descriptor.has_provider()) {
return CasStatus(CasStatusCode::kInvalidParameter,
"unable to parse private data");
}
// Build PSSH of type ENTITLEMENT.
video_widevine::WidevinePsshData pssh;
pssh.set_provider(descriptor.provider());
pssh.set_content_id(descriptor.content_id());
for (int i = 0; i < descriptor.group_ids_size(); ++i) {
pssh.add_group_ids(descriptor.group_ids(i));
}
pssh.set_type(video_widevine::WidevinePsshData::ENTITLEMENT);
if (is_entitlement_rotation_enabled_) {
pssh.set_entitlement_period_index(entitlement_period_index_);
}
pssh.SerializeToString(init_data);
return CasStatusCode::kNoError;
}
CasStatus WidevineCas::ProcessSessionCAPrivateData(
const WvCasSessionId& session_id, const CasData& private_data,
std::string* init_data) {
if (!WidevineCasSessionMap::instance().GetSession(session_id)) {
return CasStatus(CasStatusCode::kCasLicenseError, "invalid session id");
}
return ProcessCAPrivateData(private_data, init_data);
}
CasStatus WidevineCas::GetUniqueID(std::string* buffer) {
return crypto_session_->GetDeviceID(buffer);
}
CasStatus WidevineCas::HandleStoredDrmCert(const std::string& certificate) {
if (certificate.empty()) {
return CasStatus(CasStatusCode::kCasLicenseError, "empty certificate data");
}
CasStatus status = cas_license_->HandleStoredDrmCert(
certificate, &device_certificate_, &wrapped_rsa_key_);
return status;
}
CasStatus WidevineCas::HandleSetParentalControlAge(const CasData& data) {
if (data.empty()) {
return CasStatus(CasStatusCode::kCasLicenseError,
"missing value of parental control min age");
}
parental_control_age_ = data[0];
LOGI("Parental control age set to: %d", parental_control_age_);
return CasStatusCode::kNoError;
}
CasStatus WidevineCas::RecordLicenseId(const std::string& license_id) {
requested_license_id_ = license_id;
LOGI("License id selected is: %s", requested_license_id_.c_str());
return CasStatusCode::kNoError;
}
std::vector<uint8_t> WidevineCas::GenerateFingerprintingEventMessage(
const video_widevine::Fingerprinting& fingerprinting) const {
std::vector<uint8_t> message;
for (int i = 0; i < fingerprinting.channels_size(); ++i) {
const std::string& channel = fingerprinting.channels(i);
message.push_back(
static_cast<uint8_t>(FingerprintingFieldType::FINGERPRINTING_CHANNEL));
message.push_back((channel.size() >> 8) & 0xff);
message.push_back(channel.size() & 0xff);
message.insert(message.end(), channel.begin(), channel.end());
}
if (fingerprinting.has_control()) {
message.push_back(
static_cast<uint8_t>(FingerprintingFieldType::FINGERPRINTING_CONTROL));
const std::string& control = fingerprinting.control();
message.push_back((control.size() >> 8) & 0xff);
message.push_back(control.size() & 0xff);
message.insert(message.end(), control.begin(), control.end());
}
return message;
}
std::vector<uint8_t> WidevineCas::GenerateServiceBlockingEventMessage(
const video_widevine::ServiceBlocking& service_blocking) const {
std::vector<uint8_t> message;
// Process service blocking channels.
for (int i = 0; i < service_blocking.channels_size(); ++i) {
const std::string& channel = service_blocking.channels(i);
message.push_back(static_cast<uint8_t>(
ServiceBlockingFieldType::SERVICE_BLOCKING_CHANNEL));
message.push_back((channel.size() >> 8) & 0xff);
message.push_back(channel.size() & 0xff);
message.insert(message.end(), channel.begin(), channel.end());
}
// Process service blocking device_groups.
for (int i = 0; i < service_blocking.device_groups_size(); ++i) {
const std::string& device_group = service_blocking.device_groups(i);
message.push_back(static_cast<uint8_t>(
ServiceBlockingFieldType::SERVICE_BLOCKING_DEVICE_GROUP));
message.push_back((device_group.size() >> 8) & 0xff);
message.push_back(device_group.size() & 0xff);
message.insert(message.end(), device_group.begin(), device_group.end());
}
// Process service blocking start_time_sec.
if (service_blocking.has_start_time_sec()) {
message.push_back(static_cast<uint8_t>(
ServiceBlockingFieldType::SERVICE_BLOCKING_START_TIME_SECONDS));
// Timestamp is always 8 bytes (64 bits).
message.push_back(0);
message.push_back(8);
for (int i = 0; i < 8; ++i) {
message.push_back((service_blocking.start_time_sec() >> (8 * (7 - i))) &
0xff);
}
}
// Process service blocking end_time_sec.
if (service_blocking.has_end_time_sec()) {
message.push_back(static_cast<uint8_t>(
ServiceBlockingFieldType::SERVICE_BLOCKING_END_TIME_SECONDS));
// Timestamp is always 8 bytes (64 bits).
message.push_back(0);
message.push_back(8);
for (int i = 0; i < 8; ++i) {
message.push_back((service_blocking.end_time_sec() >> (8 * (7 - i))) &
0xff);
}
}
return message;
}
std::unique_ptr<const EmmParser> WidevineCas::getEmmParser(
const CasEmm& emm) const {
return EmmParser::Create(emm);
}
void WidevineCas::TryExtractEntitlementPeriodIndex(const CasEcm& ecm) {
std::unique_ptr<EcmParser> ecm_parser = getEcmParser(ecm);
if (ecm_parser == nullptr) {
LOGE("ECM parser failed for extracting entitlement period index");
return;
}
if (ecm_parser->is_entitlement_rotation_enabled()) {
is_entitlement_rotation_enabled_ = true;
entitlement_period_index_ = ecm_parser->entitlement_period_index();
LOGI("Entitlement key rotation enabled. Current index: %d",
entitlement_period_index_);
}
}
bool WidevineCas::TryReuseStoredLicense(const std::string& filename) {
// Read the file with |filename| from the file system.
std::string license_file;
if (!ReadFileFromStorage(*file_system_, filename, &license_file)) {
return false;
}
// If entitlement rotation is enabled, check if the entitlement period in the
// license is outdated.
if (media_id_->is_entitlement_rotation_enabled()) {
uint32_t stored_index;
CasStatus status = CasLicense::GetEntitlementPeriodIndexFromStoredLicense(
license_file, stored_index);
if (!status.ok()) {
LOGW(
"Failed to retrieve entitlement period index from stored license. "
"code: %d, message: %s",
status.status_code(), status.error_string().c_str());
return false;
}
if (media_id_->entitlement_period_index() != stored_index) {
LOGI("Stored license has mismatch entitlement period index.");
return false;
}
}
// Load the stored license to the session.
CasStatus status =
cas_license_->HandleStoredLicense(wrapped_rsa_key_, license_file);
if (!status.ok()) {
LOGW("Failed to load stored license. code: %d, message: %s",
status.status_code(), status.error_string().c_str());
return false;
}
return true;
}
void WidevineCas::CheckEntitlementPeriodUpdate(uint32_t period_index,
uint32_t window_left) {
if (period_index == media_id_->entitlement_period_index()) {
return;
}
// If the index changed unexpectedly, we request a new license immediately. If
// it is increased by 1, we decide if a new license should be generated based
// on |window_left|.
if (period_index != media_id_->entitlement_period_index() + 1 ||
GetRandom(window_left) == 1) {
video_widevine::WidevinePsshData pssh;
if (!pssh.ParseFromString(media_id_->get_init_data())) {
LOGE("Cannot parse init data");
return;
}
pssh.set_entitlement_period_index(period_index);
generateEntitlementPeriodUpdateRequest(pssh.SerializeAsString());
}
}
void WidevineCas::StopTimer() {
if (policy_timer_.IsRunning()) {
policy_timer_.Stop();
}
}
} // namespace wvcas

View File

@@ -0,0 +1,202 @@
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine Master
// License Agreement.
#include "widevine_cas_session.h"
#include <cas_events.h>
#include <cstring>
#include <memory>
#include "log.h"
#include "media_cas.pb.h"
namespace wvcas {
KeySlot& CasKeySlotData::operator[](KeySlotId slot_id) {
return keys_[static_cast<int>(slot_id)];
}
const KeySlot& CasKeySlotData::operator[](KeySlotId slot_id) const {
return keys_[static_cast<int>(slot_id)];
}
WidevineCasSession::~WidevineCasSession() {
if (crypto_session_ != nullptr) {
crypto_session_->RemoveEntitledKeySession(key_session_id_);
}
}
CasStatus WidevineCasSession::initialize(
std::shared_ptr<CryptoSession> crypto_session,
CasEventListener* event_listener, WvCasSessionId* session_id) {
std::unique_lock<std::mutex> lock(crypto_lock_);
if (crypto_session == nullptr) {
LOGE(
"WidevineCasSession::initialize: missing input parameter "
"crypto_session");
return CasStatus(CasStatusCode::kInvalidParameter,
"missing input parameter crypto_session");
}
crypto_session_ = std::move(crypto_session);
CasStatus status =
crypto_session_->CreateEntitledKeySession(&key_session_id_);
if (!status.ok()) {
return status;
}
status = crypto_session_->GetOEMKeyToken(key_session_id_,
external_key_session_id_);
if (!status.ok()) {
return status;
}
session_id->assign(external_key_session_id_.begin(),
external_key_session_id_.end());
event_listener_ = event_listener;
return CasStatusCode::kNoError;
}
CasStatus WidevineCasSession::resetCryptoSession(
std::shared_ptr<CryptoSession> crypto_session) {
std::unique_lock<std::mutex> lock(crypto_lock_);
if (crypto_session == nullptr) {
return CasStatus(CasStatusCode::kInvalidParameter,
"Can not reset crypto session to null");
}
crypto_session_ = std::move(crypto_session);
return crypto_session_->ReassociateEntitledKeySession(key_session_id_);
}
CasStatus WidevineCasSession::processEcm(const CasEcm& ecm,
uint8_t parental_control_age,
const std::string& license_group_id) {
std::unique_lock<std::mutex> lock(crypto_lock_);
if (ecm != current_ecm_) {
LOGD("WidevineCasSession::processEcm: received new ecm");
std::unique_ptr<EcmParser> ecm_parser = getEcmParser(ecm);
if (ecm_parser == nullptr) {
return CasStatus(CasStatusCode::kInvalidParameter, "invalid ecm");
}
if (!license_group_id.empty() &&
!ecm_parser->set_group_id(license_group_id)) {
return CasStatus(CasStatusCode::kInvalidParameter, "invalid group id");
}
ecm_age_restriction_ = ecm_parser->age_restriction();
// Parental control check.
if (parental_control_age > 0 &&
parental_control_age < ecm_age_restriction_) {
const std::string message(1, parental_control_age);
return CasStatus(CasStatusCode::kAccessDeniedByParentalControl, message);
}
std::vector<uint8_t> message;
if (!ecm_parser->fingerprinting().control().empty()) {
message.push_back(static_cast<uint8_t>(
SessionFingerprintingFieldType::SESSION_FINGERPRINTING_CONTROL));
const std::string control = ecm_parser->fingerprinting().control();
message.push_back((control.size() >> 8) & 0xff);
message.push_back(control.size() & 0xff);
message.insert(message.end(), control.begin(), control.end());
}
if (message != last_fingerprinting_message_) {
last_fingerprinting_message_ = message;
if (event_listener_ == nullptr) {
LOGW("event_listener is null. Fingerprinting info ignored!");
} else {
event_listener_->OnSessionFingerprintingUpdated(
external_key_session_id_, message);
}
}
message.clear();
for (int i = 0; i < ecm_parser->service_blocking().device_groups_size();
++i) {
message.push_back(
static_cast<uint8_t>(SessionServiceBlockingFieldType::
SESSION_SERVICE_BLOCKING_DEVICE_GROUP));
const std::string device_group =
ecm_parser->service_blocking().device_groups(i);
message.push_back((device_group.size() >> 8) & 0xff);
message.push_back(device_group.size() & 0xff);
message.insert(message.end(), device_group.begin(), device_group.end());
}
if (message != last_service_blocking_message_) {
last_service_blocking_message_ = message;
if (event_listener_ == nullptr) {
LOGW("event_listener is null. Service blocking info ignored!");
} else {
event_listener_->OnSessionServiceBlockingUpdated(
external_key_session_id_, message);
}
}
entitlement_period_index_ = ecm_parser->entitlement_period_index();
entitlement_rotation_window_left_ =
ecm_parser->entitlement_rotation_window_left();
bool load_even = false;
bool load_odd = false;
KeySlotId keyslot_id = KeySlotId::kEvenKeySlot;
// Temporary key slots to only have successfully loaded keys in |keys_|.
CasKeySlotData keys;
do {
if (keys_[keyslot_id].wrapped_key !=
ecm_parser->wrapped_key_data(keyslot_id)) {
KeySlot& key = keys[keyslot_id];
key.key_id = ecm_parser->content_key_id(keyslot_id);
key.wrapped_key = ecm_parser->wrapped_key_data(keyslot_id);
key.wrapped_key_iv = ecm_parser->wrapped_key_iv(keyslot_id);
key.entitlement_key_id = ecm_parser->entitlement_key_id(keyslot_id);
key.cipher_mode = ecm_parser->crypto_mode();
key.content_iv = ecm_parser->content_iv(keyslot_id);
if (keyslot_id == KeySlotId::kEvenKeySlot) {
load_even = true;
} else {
load_odd = true;
}
if (key.content_iv.size() == 8) {
key.content_iv.resize(16, 0);
}
}
if (!ecm_parser->rotation_enabled() ||
keyslot_id == KeySlotId::kOddKeySlot) {
break;
}
keyslot_id = KeySlotId::kOddKeySlot;
} while (true);
if (load_even || load_odd) {
CasStatus status = crypto_session_->LoadCasECMKeys(
key_session_id_,
(load_even ? &keys[KeySlotId::kEvenKeySlot] : nullptr),
(load_odd ? &keys[KeySlotId::kOddKeySlot] : nullptr));
if (status.status_code() != CasStatusCode::kNoError) {
LOGE("WidevineCasSession::processEcm: error %d, msg %s",
status.status_code(), status.error_string().c_str());
return status;
}
// Don't update on failure, to not to lose still working key_id.
if (load_even) {
keys_[KeySlotId::kEvenKeySlot] = keys[KeySlotId::kEvenKeySlot];
}
if (load_odd) {
keys_[KeySlotId::kOddKeySlot] = keys[KeySlotId::kOddKeySlot];
}
}
current_ecm_ = ecm;
}
return CasStatusCode::kNoError;
}
std::unique_ptr<EcmParser> WidevineCasSession::getEcmParser(
const CasEcm& ecm) const {
return EcmParser::Create(ecm);
}
const char* WidevineCasSession::securityLevel() {
return crypto_session_->SecurityLevel();
}
} // namespace wvcas

View File

@@ -0,0 +1,47 @@
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine Master
// License Agreement.
#include "widevine_cas_session_map.h"
namespace wvcas {
WidevineCasSessionMap& WidevineCasSessionMap::instance() {
static WidevineCasSessionMap the_cas_session_map;
return the_cas_session_map;
}
bool WidevineCasSessionMap::AddSession(const WvCasSessionId& cas_session_id,
CasSessionPtr session) {
std::unique_lock<std::mutex> lock(lock_);
std::pair<CasSessionMap::iterator, bool> entry =
map_.emplace(cas_session_id, session);
return entry.second;
}
CasSessionPtr WidevineCasSessionMap::GetSession(
const WvCasSessionId& cas_session_id) const {
std::unique_lock<std::mutex> lock(lock_);
CasSessionMap::const_iterator it = map_.find(cas_session_id);
if (it == map_.end()) {
return CasSessionPtr(nullptr);
}
return it->second;
}
void WidevineCasSessionMap::RemoveSession(
const WvCasSessionId& cas_session_id) {
std::unique_lock<std::mutex> lock(lock_);
map_.erase(cas_session_id);
}
std::vector<CasSessionPtr> WidevineCasSessionMap::GetAllSessions() const {
std::unique_lock<std::mutex> lock(lock_);
std::vector<CasSessionPtr> sessions;
for (const auto& session : map_) {
sessions.push_back(session.second);
}
return sessions;
}
} // namespace wvcas

View File

@@ -0,0 +1,95 @@
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine Master
// License Agreement.
#include "widevine_media_cas.h"
#include <memory>
#include "cas_util.h"
#include "crypto_session.h"
#include "utils/Errors.h"
#include "widevine_media_cas_plugin.h"
using android::BAD_VALUE;
using android::OK;
// Widevine Technologies CA system ID.
static constexpr int32_t kWidevineCAID = 0x4AD4;
// New Widevine CAS IDs 0x56C0 to 0x56C9 (all inclusive).
static constexpr int32_t kWidevineNewCasIdLowerBound = 0x56C0;
static constexpr int32_t kWidevineNewCasIdUpperBound = 0x56C9;
// Total number of supported Widevine CAS ids.
static constexpr size_t kWidevineCasIdCount = 11;
static constexpr char kName[] = "WidevineCas";
// Implements extern android::CasFactory *createCasFactory() entry point.
CasFactory* createCasFactory() {
return wvcas::WidevineCasFactory::createCasFactory();
}
namespace wvcas {
/************************ Cas factory implementation *********************/
// The widevine cas implementation of the cas plugin factory.
WidevineCasFactory* WidevineCasFactory::createCasFactory() {
return new WidevineCasFactory();
}
bool WidevineCasFactory::isSystemIdSupported(int32_t CA_system_id) const {
return (CA_system_id == kWidevineCAID) ||
(CA_system_id >= kWidevineNewCasIdLowerBound &&
CA_system_id <= kWidevineNewCasIdUpperBound);
}
status_t WidevineCasFactory::queryPlugins(
std::vector<CasPluginDescriptor>* descriptors) const {
if (nullptr == descriptors) {
return BAD_VALUE;
}
descriptors->clear();
descriptors->reserve(kWidevineCasIdCount);
descriptors->push_back({kWidevineCAID, String8(kName)});
for (int32_t new_id = kWidevineNewCasIdLowerBound;
new_id <= kWidevineNewCasIdUpperBound; ++new_id) {
descriptors->push_back({new_id, String8(kName)});
}
return OK;
}
status_t WidevineCasFactory::createPlugin(int32_t CA_system_id, void* appData,
CasPluginCallback callback,
CasPlugin** plugin) {
if (nullptr == plugin || !isSystemIdSupported(CA_system_id)) {
return BAD_VALUE;
}
std::unique_ptr<WidevineCasPlugin> new_plugin =
make_unique<WidevineCasPlugin>(appData, callback);
status_t status = new_plugin->initialize();
if (status != OK) {
return status;
}
*plugin = new_plugin.release();
return OK;
}
status_t WidevineCasFactory::createPlugin(int32_t CA_system_id, void* appData,
CasPluginCallbackExt callback,
CasPlugin** plugin) {
if (nullptr == plugin || !isSystemIdSupported(CA_system_id)) {
return BAD_VALUE;
}
std::unique_ptr<WidevineCasPlugin> new_plugin =
make_unique<WidevineCasPlugin>(appData, callback);
status_t status = new_plugin->initialize();
if (status != OK) {
return status;
}
*plugin = new_plugin.release();
return OK;
}
} // namespace wvcas

View File

@@ -0,0 +1,600 @@
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine Master
// License Agreement.
#include "widevine_media_cas_plugin.h"
#include <stdlib.h>
#include <time.h>
#include <utils/String8.h>
#include <iostream>
#include <memory>
#include "cas_events.h"
#include "cas_properties.h"
#include "cas_status.h"
#include "cas_types.h"
#include "cas_util.h"
#include "log.h"
#include "media/stagefright/MediaErrors.h"
using android::BAD_VALUE;
using android::INVALID_OPERATION;
using android::OK;
namespace wvcas {
WidevineCasPlugin::WidevineCasPlugin(void* appData, CasPluginCallback callback)
: app_data_(appData),
callback_(callback),
callback_ext_(nullptr),
widevine_cas_api_(make_unique<WidevineCas>()) {}
WidevineCasPlugin::WidevineCasPlugin(void* appData,
CasPluginCallbackExt callback)
: app_data_(appData),
callback_(nullptr),
callback_ext_(callback),
widevine_cas_api_(make_unique<WidevineCas>()) {}
WidevineCasPlugin::~WidevineCasPlugin() { widevine_cas_api_->StopTimer(); }
status_t WidevineCasPlugin::initialize() {
CasStatus status = widevine_cas_api_->initialize(this);
if (!status.ok()) {
return android::ERROR_CAS_UNKNOWN;
}
std::string version;
if (Properties::GetWvCasPluginVersion(version)) {
LOGI("Widevine CAS plugin version: %s", version.c_str());
} else {
LOGW("Failed to get Widevine CAS plugin version.");
}
return OK;
}
bool WidevineCasPlugin::is_provisioned() const {
return widevine_cas_api_->is_provisioned();
}
status_t WidevineCasPlugin::setStatusCallback(
CasPluginStatusCallback /*callback*/) {
// TODO(chelu): support status callback.
return OK;
}
status_t WidevineCasPlugin::setPrivateData(const CasData& privateData) {
// Can get PSSH from multiple streams and from provision call.
// Only need to request a license once.
if (is_emm_request_sent_ || privateData.empty()) {
return OK;
}
CasStatus status =
widevine_cas_api_->ProcessCAPrivateData(privateData, &init_data_);
if (!status.ok()) {
return android::ERROR_CAS_UNKNOWN;
}
if (widevine_cas_api_->is_provisioned()) {
return requestLicense(init_data_);
}
return OK;
}
status_t WidevineCasPlugin::openSession(CasSessionId* sessionId) {
if (nullptr == sessionId) {
return BAD_VALUE;
}
if (!is_provisioned()) {
LOGE("Sessions can only be opened after privisioned.");
return android::ERROR_CAS_NOT_PROVISIONED;
}
WvCasSessionId new_session_id;
CasStatus status = widevine_cas_api_->openSession(&new_session_id);
if (!status.ok()) {
return android::ERROR_CAS_SESSION_NOT_OPENED;
}
sessionId->assign(new_session_id.begin(), new_session_id.end());
// This is the backward compatible 4 bytes session_id.
int32_t session_id_int32 = 0;
if (new_session_id.size() == 4) {
// CasSessionId is expected to be in little endian order.
for (int i = new_session_id.size() - 1; i >= 0; --i) {
session_id_int32 = (session_id_int32 << 8) | new_session_id[i];
}
}
// Not a session event, so CasSessionId in callback is null.
CallBack(app_data_, CAS_SESSION_ID, session_id_int32, sessionId->data(),
sessionId->size(), nullptr);
return OK;
}
status_t WidevineCasPlugin::openSession(uint32_t intent, uint32_t mode,
CasSessionId* sessionId) {
// TODO(chelu): return error on unsupported intent/mode.
return openSession(sessionId);
}
status_t WidevineCasPlugin::closeSession(const CasSessionId& sessionId) {
CasStatus status = widevine_cas_api_->closeSession(sessionId);
if (!status.ok()) {
return android::ERROR_CAS_SESSION_NOT_OPENED;
}
return OK;
}
status_t WidevineCasPlugin::setSessionPrivateData(const CasSessionId& sessionId,
const CasData& privateData) {
// Can get PSSH from multiple streams and from provision call.
// Only need to request a license once.
if (is_emm_request_sent_ || privateData.empty()) {
return OK;
}
// Doesn't matter which session, CA descriptor applies to all of them.
CasStatus status = widevine_cas_api_->ProcessSessionCAPrivateData(
sessionId, privateData, &init_data_);
if (!status.ok()) {
return android::ERROR_CAS_SESSION_NOT_OPENED;
}
if (widevine_cas_api_->is_provisioned()) {
return requestLicense(init_data_);
}
return OK;
}
status_t WidevineCasPlugin::processEcm(const CasSessionId& sessionId,
const CasEcm& ecm) {
LOGI("WidevineCasPlugin::processEcm");
CasStatus status = widevine_cas_api_->processEcm(sessionId, ecm);
if (!status.ok()) {
CasData error(status.error_string().begin(), status.error_string().end());
switch (status.status_code()) {
case CasStatusCode::kDeferedEcmProcessing:
return android::ERROR_CAS_DECRYPT_UNIT_NOT_INITIALIZED;
case CasStatusCode::kAccessDeniedByParentalControl:
CallBack(reinterpret_cast<void*>(app_data_),
ACCESS_DENIED_BY_PARENTAL_CONTROL, /*arg=*/0, &error[0],
error.size(), &sessionId);
return android::ERROR_CAS_DECRYPT;
default:
CallBack(reinterpret_cast<void*>(app_data_), CAS_ERROR,
static_cast<int32_t>(status.status_code()), &error[0],
error.size(), &sessionId);
// TODO(jfore): Can this error value be more specific? Is it because we
// don't have a license? Or the key ids don't match? Or sunspots?
return android::ERROR_CAS_UNKNOWN;
}
}
return OK;
}
status_t WidevineCasPlugin::processEmm(const CasEmm& emm) {
LOGI("WidevineCasPlugin::processEmm");
if (!widevine_cas_api_->processEmm(emm).ok()) {
return android::ERROR_CAS_UNKNOWN;
}
return OK;
}
status_t WidevineCasPlugin::sendEvent(int32_t event, int32_t arg,
const CasData& eventData) {
CasStatus status = processEvent(event, arg, eventData, /*sessionId=*/nullptr);
if (status.status_code() != CasStatusCode::kNoError) {
CasData error(status.error_string().begin(), status.error_string().end());
CallBack(reinterpret_cast<void*>(app_data_), CAS_ERROR,
static_cast<int32_t>(status.status_code()), &error[0],
error.size(), nullptr);
return android::ERROR_CAS_UNKNOWN;
}
return OK;
}
status_t WidevineCasPlugin::sendSessionEvent(const CasSessionId& sessionId,
int32_t event, int32_t arg,
const CasData& eventData) {
CasStatus status = processEvent(event, arg, eventData, &sessionId);
if (status.status_code() != CasStatusCode::kNoError) {
CasData error(status.error_string().begin(), status.error_string().end());
CallBack(reinterpret_cast<void*>(app_data_), CAS_ERROR,
static_cast<int32_t>(status.status_code()), &error[0],
error.size(), &sessionId);
return android::ERROR_CAS_UNKNOWN;
}
return OK;
}
status_t WidevineCasPlugin::provision(const String8& provisionString) {
// Store |provisionString| for future use. If |provisionString| is not empty
// the value takes priority over data in CA descriptor.
init_data_ = std::string(provisionString.c_str(),
provisionString.c_str() + provisionString.length());
if (is_provisioned()) {
CallBack(reinterpret_cast<void*>(app_data_), INDIVIDUALIZATION_COMPLETE, 0,
nullptr, 0, nullptr);
if (!init_data_.empty()) {
return requestLicense(init_data_);
}
return OK;
}
std::string provisioning_request;
CasStatus status = widevine_cas_api_->generateDeviceProvisioningRequest(
&provisioning_request);
if (!status.ok()) {
return INVALID_OPERATION;
}
if (!provisioning_request.empty()) {
std::vector<uint8_t> callback_data(provisioning_request.begin(),
provisioning_request.end());
CallBack(reinterpret_cast<void*>(app_data_), INDIVIDUALIZATION_REQUEST, 0,
callback_data.data(), callback_data.size(), nullptr);
}
return OK;
}
status_t WidevineCasPlugin::requestLicense(const std::string& init_data) {
std::string signed_license_request;
std::string license_id;
CasStatus status = widevine_cas_api_->generateEntitlementRequest(
init_data, &signed_license_request, license_id);
if (!status.ok()) {
LOGE("WidevineCas generate entitlement request failed: %d",
status.status_code());
return INVALID_OPERATION;
}
// If populated we need to send |signed_license_request| to the server.
// Otherwise signal license complete.
if (!signed_license_request.empty()) {
CallBack(reinterpret_cast<void*>(app_data_), LICENSE_REQUEST,
LICENSE_REQUEST,
reinterpret_cast<uint8_t*>(&signed_license_request[0]),
signed_license_request.size(), nullptr);
} else {
CallBack(reinterpret_cast<void*>(app_data_), LICENSE_CAS_READY,
LICENSE_CAS_READY, reinterpret_cast<uint8_t*>(&license_id[0]),
license_id.size(), nullptr);
}
is_emm_request_sent_ = true;
return OK;
}
status_t WidevineCasPlugin::refreshEntitlements(
int32_t /*refreshType*/, const CasData& /*refreshData*/) {
return OK;
}
CasStatus WidevineCasPlugin::processEvent(int32_t event, int32_t arg,
const CasData& eventData,
const CasSessionId* sessionId) {
switch (event) {
case INDIVIDUALIZATION_RESPONSE:
return HandleIndividualizationResponse(eventData);
case TEST_FOR_ECHO:
CallBack(reinterpret_cast<void*>(app_data_), ECHO, 0, nullptr, 0,
sessionId);
break;
case LICENSE_RESPONSE:
return HandleEntitlementResponse(eventData);
case LICENSE_RENEWAL_RESPONSE:
return HandleEntitlementRenewalResponse(eventData);
case QUERY_UNIQUE_ID:
return HandleUniqueIDQuery();
case SET_PARENTAL_CONTROL_AGE:
return HandleSetParentalControlAge(eventData);
case LICENSE_REMOVAL:
return HandleLicenseRemoval(eventData);
case ASSIGN_LICENSE_ID:
return HandleAssignLicenseID(eventData);
case QUERY_WV_CAS_PLUGIN_VERSION:
return HandlePluginVersionQuery();
case LICENSE_ENTITLEMENT_PERIOD_UPDATE_RESPONSE:
return HandleEntitlementPeriodUpdateResponse(eventData);
default:
return CasStatusCode::kUnknownEvent;
}
return CasStatusCode::kNoError;
}
CasStatus WidevineCasPlugin::HandleIndividualizationResponse(
const CasData& response) {
if (response.empty()) {
return CasStatus(CasStatusCode::kCasLicenseError,
"empty individualization response");
}
std::string resp_string(response.begin(), response.end());
CasStatus status = widevine_cas_api_->handleProvisioningResponse(resp_string);
if (!status.ok()) {
return status;
}
CallBack(reinterpret_cast<void*>(app_data_), INDIVIDUALIZATION_COMPLETE, 0,
nullptr, 0, nullptr);
if (!init_data_.empty() && !is_emm_request_sent_) {
LOGD("Making license request with provisioned PSSH");
if (requestLicense(init_data_) != OK) {
return CasStatus(CasStatusCode::kCasLicenseError,
"failed to generate license request");
}
}
return CasStatusCode::kNoError;
}
CasStatus WidevineCasPlugin::HandleEntitlementResponse(
const CasData& response) {
if (response.empty()) {
return CasStatus(CasStatusCode::kCasLicenseError, "empty emm response");
}
std::string resp_string(response.begin(), response.end());
std::string license_id;
std::string multi_content_license_info;
std::string group_license_info;
CasStatus status = widevine_cas_api_->handleEntitlementResponse(
resp_string, license_id, multi_content_license_info, group_license_info);
if (!status.ok()) {
return status;
}
CallBack(reinterpret_cast<void*>(app_data_), LICENSE_CAS_READY,
LICENSE_CAS_READY, reinterpret_cast<uint8_t*>(&license_id[0]),
license_id.size(), nullptr);
if (!multi_content_license_info.empty()) {
CallBack(reinterpret_cast<void*>(app_data_), MULTI_CONTENT_LICENSE_INFO,
MULTI_CONTENT_LICENSE_INFO,
reinterpret_cast<uint8_t*>(&multi_content_license_info[0]),
multi_content_license_info.size(), nullptr);
}
if (!group_license_info.empty()) {
CallBack(reinterpret_cast<void*>(app_data_), GROUP_LICENSE_INFO,
GROUP_LICENSE_INFO,
reinterpret_cast<uint8_t*>(&group_license_info[0]),
group_license_info.size(), nullptr);
}
return CasStatusCode::kNoError;
}
CasStatus WidevineCasPlugin::HandleEntitlementRenewalResponse(
const CasData& response) {
if (response.empty()) {
return CasStatus(CasStatusCode::kCasLicenseError, "empty emm response");
}
std::string resp_string(response.begin(), response.end());
std::string license_id;
CasStatus status = widevine_cas_api_->handleEntitlementRenewalResponse(
resp_string, license_id);
if (!status.ok()) {
return status;
}
CallBack(reinterpret_cast<void*>(app_data_), LICENSE_CAS_RENEWAL_READY,
LICENSE_CAS_RENEWAL_READY,
reinterpret_cast<uint8_t*>(&license_id[0]), license_id.size(),
nullptr);
return CasStatusCode::kNoError;
}
CasStatus WidevineCasPlugin::HandleEntitlementPeriodUpdateResponse(
const CasData& response) {
if (response.empty()) {
return CasStatus(CasStatusCode::kCasLicenseError, "empty license response");
}
std::string resp_string(response.begin(), response.end());
std::string license_id;
CasStatus status = widevine_cas_api_->handleEntitlementPeriodUpdateResponse(
resp_string, license_id);
if (!status.ok()) {
return status;
}
CallBack(
reinterpret_cast<void*>(app_data_), LICENSE_ENTITLEMENT_PERIOD_UPDATED,
LICENSE_ENTITLEMENT_PERIOD_UPDATED,
reinterpret_cast<uint8_t*>(&license_id[0]), license_id.size(), nullptr);
return CasStatusCode::kNoError;
}
CasStatus WidevineCasPlugin::HandleUniqueIDQuery() {
std::string buffer;
CasStatus status = widevine_cas_api_->GetUniqueID(&buffer);
if (!status.ok()) {
CasData error(status.error_string().begin(), status.error_string().end());
CallBack(reinterpret_cast<void*>(app_data_), CAS_ERROR,
static_cast<int32_t>(status.status_code()), &error[0],
error.size(), nullptr);
return status;
} else {
CallBack(reinterpret_cast<void*>(app_data_), UNIQUE_ID,
static_cast<int32_t>(0), reinterpret_cast<uint8_t*>(&buffer[0]),
buffer.size(), nullptr);
}
return status;
}
CasStatus WidevineCasPlugin::HandleSetParentalControlAge(const CasData& data) {
return widevine_cas_api_->HandleSetParentalControlAge(data);
}
CasStatus WidevineCasPlugin::HandleLicenseRemoval(const CasData& license_id) {
if (license_id.empty()) {
return CasStatus(CasStatusCode::kInvalidParameter, "empty license id");
}
std::string license_id_str(license_id.begin(), license_id.end());
std::string file_name = license_id_str + ".lic";
CasStatus status = widevine_cas_api_->RemoveLicense(file_name);
if (!status.ok()) {
CasData error(status.error_string().begin(), status.error_string().end());
CallBack(reinterpret_cast<void*>(app_data_), CAS_ERROR,
static_cast<int32_t>(status.status_code()), &error[0],
error.size(), nullptr);
return status;
}
CallBack(reinterpret_cast<void*>(app_data_), LICENSE_REMOVED, LICENSE_REMOVED,
reinterpret_cast<uint8_t*>(&license_id_str[0]),
license_id_str.size(), nullptr);
return CasStatusCode::kNoError;
}
CasStatus WidevineCasPlugin::HandleAssignLicenseID(
const wvcas::CasData& license_id) {
if (license_id.empty()) {
return CasStatus(CasStatusCode::kInvalidParameter, "empty license id");
}
std::string license_id_str(license_id.begin(), license_id.end());
CasStatus status = widevine_cas_api_->RecordLicenseId(license_id_str);
if (!status.ok()) {
return status;
}
CallBack(reinterpret_cast<void*>(app_data_), LICENSE_ID_ASSIGNED,
LICENSE_ID_ASSIGNED, reinterpret_cast<uint8_t*>(&license_id_str[0]),
license_id_str.size(), nullptr);
return CasStatusCode::kNoError;
}
CasStatus WidevineCasPlugin::HandlePluginVersionQuery() {
std::string version;
if (!Properties::GetWvCasPluginVersion(version)) {
return CasStatus(CasStatusCode::kUnknownError,
"unable to get plugin version");
}
CallBack(reinterpret_cast<void*>(app_data_), WV_CAS_PLUGIN_VERSION,
static_cast<int32_t>(0), reinterpret_cast<uint8_t*>(&version[0]),
version.size(), /*sessionId=*/nullptr);
return CasStatusCode::kNoError;
}
void WidevineCasPlugin::OnSessionRenewalNeeded() {
LOGI("OnSessionRenewalNeeded");
std::string renewal_request;
CasStatus status =
widevine_cas_api_->generateEntitlementRenewalRequest(&renewal_request);
if (!status.ok()) {
LOGE("unable to generate a license renewal request: %s",
status.error_string().c_str());
return;
}
CallBack(reinterpret_cast<void*>(app_data_), LICENSE_RENEWAL_REQUEST,
LICENSE_RENEWAL_REQUEST,
reinterpret_cast<uint8_t*>(&renewal_request[0]),
renewal_request.size(), nullptr);
}
// TODO(jfore): Hook up usage of the key status once the license service
// populates policy information and drop the status logging. b/129482318
void WidevineCasPlugin::OnSessionKeysChange(const KeyStatusMap& keys_status,
bool has_new_usable_key) {
LOGI("OnSessionKeysChange: Has new usable key : %s",
(has_new_usable_key ? "true" : "false"));
for (auto& key : keys_status) {
LOGI("%d", key.second);
}
}
// Send the license expiration timestamp via this existing callback to app.
// Callback will be triggered once license is installed or license expiration
// time getting update.
// TODO(b/163427255): Should we combine with license_id?
void WidevineCasPlugin::OnExpirationUpdate(int64_t new_expiry_time_seconds) {
LOGI("OnExpirationUpdate");
CallBack(reinterpret_cast<void*>(app_data_), LICENSE_NEW_EXPIRY_TIME,
LICENSE_NEW_EXPIRY_TIME,
reinterpret_cast<uint8_t*>(&new_expiry_time_seconds), 8, nullptr);
}
void WidevineCasPlugin::OnNewRenewalServerUrl(
const std::string& renewal_server_url) {
CasData url(renewal_server_url.begin(), renewal_server_url.end());
CallBack(reinterpret_cast<void*>(app_data_), LICENSE_RENEWAL_URL, 0, &url[0],
url.size(), nullptr);
}
void WidevineCasPlugin::OnLicenseExpiration() {
LOGI("OnLicenseExpiration");
CallBack(reinterpret_cast<void*>(app_data_),
android::ERROR_CAS_LICENSE_EXPIRED,
android::ERROR_CAS_LICENSE_EXPIRED, nullptr, 0, nullptr);
}
void WidevineCasPlugin::OnAgeRestrictionUpdated(const WvCasSessionId& sessionId,
uint8_t ecm_age_restriction) {
LOGI("OnAgeRestrictionUpdated");
CallBack(reinterpret_cast<void*>(app_data_), AGE_RESTRICTION_UPDATED,
/*arg=*/0, &ecm_age_restriction, 1, &sessionId);
}
void WidevineCasPlugin::OnSessionFingerprintingUpdated(
const WvCasSessionId& sessionId, const CasData& fingerprinting) {
LOGI("OnSessionFingerprintingUpdated");
CallBack(reinterpret_cast<void*>(app_data_),
SESSION_FINGERPRINTING_INFO, /*arg=*/
0,
fingerprinting.empty() ? nullptr
: const_cast<uint8_t*>(&fingerprinting[0]),
fingerprinting.size(), &sessionId);
}
void WidevineCasPlugin::OnSessionServiceBlockingUpdated(
const WvCasSessionId& sessionId, const CasData& service_blocking) {
LOGI("OnSessionServiceBlockingUpdated");
CallBack(reinterpret_cast<void*>(app_data_),
SESSION_SERVICE_BLOCKING_INFO, /*arg=*/
0,
service_blocking.empty()
? nullptr
: const_cast<uint8_t*>(&service_blocking[0]),
service_blocking.size(), &sessionId);
}
void WidevineCasPlugin::OnFingerprintingUpdated(const CasData& fingerprinting) {
if (fingerprinting.empty()) {
return;
}
LOGI("OnFingerprintingUpdated");
CallBack(reinterpret_cast<void*>(app_data_), FINGERPRINTING_INFO, /*arg=*/0,
const_cast<uint8_t*>(&fingerprinting[0]), fingerprinting.size(),
nullptr);
}
void WidevineCasPlugin::OnServiceBlockingUpdated(
const CasData& service_blocking) {
if (service_blocking.empty()) {
return;
}
LOGI("OnServiceBlockingUpdated");
CallBack(reinterpret_cast<void*>(app_data_), SERVICE_BLOCKING_INFO, /*arg=*/0,
const_cast<uint8_t*>(&service_blocking[0]), service_blocking.size(),
nullptr);
}
void WidevineCasPlugin::OnEntitlementPeriodUpdateNeeded(
const std::string& signed_license_request) {
LOGI("OnEntitlementPeriodUpdateNeeded");
if (!signed_license_request.empty()) {
CallBack(reinterpret_cast<void*>(app_data_),
LICENSE_ENTITLEMENT_PERIOD_UPDATE_REQUEST,
LICENSE_ENTITLEMENT_PERIOD_UPDATE_REQUEST,
const_cast<uint8_t*>(
reinterpret_cast<const uint8_t*>(&signed_license_request[0])),
signed_license_request.size(), nullptr);
}
}
void WidevineCasPlugin::CallBack(void* appData, int32_t event, int32_t arg,
uint8_t* data, size_t size,
const CasSessionId* sessionId) const {
if (callback_ext_ != nullptr) {
callback_ext_(appData, event, arg, data, size, sessionId);
} else {
callback_(appData, event, arg, data, size);
}
}
} // namespace wvcas