Regular sync.

Changes include:
1. Fix refreshkeys when handling renewal response.
2. Change ECM start detect method.
3. Fix signing key truncation.
4. Reformat C++ code.
5. Return license_id in LICENSE_CAS_READY payload.
6. Expose OEMCrypto API version in the license request.
7. Add support for newly added widevine cas ids.
8. Store content iv and encryption mode info to entitled key.
9. Upgrade ODK library to 16.4.
This commit is contained in:
huihli
2020-10-21 11:16:23 -07:00
parent 0f6db6f751
commit 2feec02df2
39 changed files with 703 additions and 546 deletions

View File

@@ -15,8 +15,6 @@ class CasMediaId {
virtual CasStatus initialize(const std::string& init_data) = 0;
virtual const std::string content_id() = 0;
virtual const std::string provider_id() = 0;
virtual const int group_ids_size() = 0;
virtual const std::string group_id() = 0;
};
} // namespace wvcas

View File

@@ -28,6 +28,7 @@ enum class CasStatusCode : int32_t {
kDeferedEcmProcessing = 14,
kAccessDeniedByParentalControl = 15,
kUnknownEvent = 16,
kOEMCryptoVersionMismatch = 17,
};
class CasStatus {

View File

@@ -7,9 +7,9 @@
#include <memory>
#if __cplusplus >= 201402L || \
(defined __cpp_lib_make_unique && __cpp_lib_make_unique >= 201304L) || \
(defined(_MSC_VER) && _MSC_VER >= 1900)
#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>
@@ -18,4 +18,4 @@ std::unique_ptr<T> make_unique(Args&&... args) {
}
#endif
#endif //CAS_UTIL_H_
#endif // CAS_UTIL_H_

View File

@@ -9,15 +9,15 @@ namespace wvcas {
class CryptoKey {
public:
CryptoKey() {};
~CryptoKey() {};
CryptoKey(){};
~CryptoKey(){};
const std::string& key_id() const { return key_id_; }
const std::string& key_data() const { return key_data_; }
const std::string& key_data_iv() const { return key_data_iv_; }
const std::string& key_control() const { return key_control_; }
const std::string& key_control_iv() const { return key_control_iv_; }
const std::string& entitlement_key_id() const {return entitlement_key_id_;}
const std::string& entitlement_key_id() const { return entitlement_key_id_; }
const std::string& track_label() const { return track_label_; }
void set_key_id(const std::string& key_id) { key_id_ = key_id; }
void set_key_data(const std::string& key_data) { key_data_ = key_data; }

View File

@@ -37,7 +37,7 @@ typedef OEMCrypto_HDCP_Capability HdcpCapability;
class CryptoLock {
public:
CryptoLock() {};
CryptoLock(){};
// These methods should be used to take the various CryptoSession mutexes in
// preference to taking the mutexes directly.
//
@@ -75,18 +75,19 @@ class CryptoLock {
// 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>
template <class Func>
static auto WithStaticFieldWriteLock(const char* tag, Func body)
-> decltype(body());
template<class Func>
-> decltype(body());
template <class Func>
static auto WithStaticFieldReadLock(const char* tag, Func body)
-> decltype(body());
template<class Func>
-> decltype(body());
template <class Func>
static auto WithOecWriteLock(const char* tag, Func body) -> decltype(body());
template<class Func>
template <class Func>
static auto WithOecReadLock(const char* tag, Func body) -> decltype(body());
template<class Func>
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
@@ -161,9 +162,10 @@ class CryptoInterface {
OEMCrypto_SESSION session, const uint8_t* message, size_t message_length,
const OEMCrypto_EntitledCasKeyObject* even_key,
const OEMCrypto_EntitledCasKeyObject* odd_key);
virtual OEMCryptoResult OEMCrypto_SelectKey(
OEMCrypto_SESSION session, const uint8_t* content_key_id,
size_t content_key_id_length, OEMCryptoCipherMode cipher_mode);
virtual OEMCryptoResult OEMCrypto_SelectKey(OEMCrypto_SESSION session,
const uint8_t* content_key_id,
size_t content_key_id_length,
OEMCryptoCipherMode cipher_mode);
virtual OEMCryptoResult OEMCrypto_GetHDCPCapability(
OEMCrypto_HDCP_Capability* current, OEMCrypto_HDCP_Capability* max);
virtual OEMCryptoResult OEMCrypto_RefreshKeys(
@@ -174,10 +176,10 @@ class CryptoInterface {
size_t* idLength);
virtual const char* OEMCrypto_SecurityLevel();
virtual OEMCryptoResult OEMCrypto_CreateEntitledKeySession(
OEMCrypto_SESSION session,
OEMCrypto_SESSION* entitled_key_session_id);
OEMCrypto_SESSION session, OEMCrypto_SESSION* entitled_key_session_id);
virtual OEMCryptoResult OEMCrypto_RemoveEntitledKeySession(
OEMCrypto_SESSION entitled_key_session_id);
virtual uint32_t OEMCrypto_APIVersion();
// This is the factory method used to enable the oemcrypto interface.
static OEMCryptoResult create(std::unique_ptr<CryptoInterface>* init) {
@@ -190,7 +192,7 @@ class CryptoInterface {
// 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>
template <typename CI>
static OEMCryptoResult create(std::unique_ptr<CryptoInterface>* init,
CI* oemcrypto_interface) {
return create_internal(oemcrypto_interface, init);
@@ -299,6 +301,7 @@ class CryptoSession {
OEMCrypto_SESSION* entitled_key_session_id);
virtual CasStatus RemoveEntitledKeySession(
OEMCrypto_SESSION entitled_key_session_id);
virtual CasStatus APIVersion(uint32_t* api_version);
CryptoSession(const CryptoSession&) = delete;
CryptoSession& operator=(const CryptoSession&) = delete;

View File

@@ -41,12 +41,13 @@ struct InputStreamParams {
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){}
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
@@ -95,8 +96,8 @@ class OEMCryptoInterface {
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;
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,
@@ -142,6 +143,7 @@ class OEMCryptoInterface {
OEMCrypto_SESSION oec_session, OEMCrypto_SESSION* key_session);
virtual OEMCryptoResult OEMCrypto_RemoveEntitledKeySession(
OEMCrypto_SESSION key_session);
virtual uint32_t OEMCrypto_APIVersion() const;
OEMCryptoInterface(const OEMCryptoInterface&) = delete;
OEMCryptoInterface& operator=(const OEMCryptoInterface&) = delete;

View File

@@ -49,12 +49,13 @@ class WidevineCas : public wvutil::TimerHandler {
// 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);
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.
virtual CasStatus handleEntitlementResponse(
const std::string& response, std::string& license_id);
virtual CasStatus handleEntitlementResponse(const std::string& response,
std::string& license_id);
// Generates an entitlement license request in |entitlement_request| for the
// media described in |init_data|.

View File

@@ -14,8 +14,8 @@ using android::CasPlugin;
using android::CasPluginCallback;
using android::CasPluginCallbackExt;
using android::CasPluginStatusCallback;
using android::status_t;
using android::CasSessionId;
using android::status_t;
using android::String8;
namespace wvcas {
@@ -91,8 +91,7 @@ class WidevineCasPlugin : public CasPlugin, public CasEventListener {
private:
virtual std::shared_ptr<CryptoSession> getCryptoSession();
// |sessionId| is nullptr if the event is not a session event.
virtual CasStatus processEvent(int32_t event,
int32_t arg,
virtual CasStatus processEvent(int32_t event, int32_t arg,
const CasData& eventData,
const CasSessionId* sessionId);
virtual CasStatus HandleIndividualizationResponse(const CasData& response);
@@ -116,12 +115,8 @@ class WidevineCasPlugin : public CasPlugin, public CasEventListener {
// Choose to use |callback_| or |callback_ext_| to send back information.
// |sessionId| is ignored if |callback_ext_| is null,
void CallBack(void* appData,
int32_t event,
int32_t arg,
uint8_t* data,
size_t size,
const CasSessionId* sessionId) const;
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_;

View File

@@ -2,13 +2,14 @@
// source code may only be used and distributed under the Widevine Master
// License Agreement.
#include "cas_license.h"
#include <openssl/sha.h>
#include <sys/time.h>
#include <sstream>
#include <vector>
#include "cas_license.h"
#include "cas_properties.h"
#include "cas_util.h"
#include "crypto_key.h"
@@ -41,6 +42,7 @@ using video_widevine::SignedMessage;
using video_widevine::SignedProvisioningMessage;
static constexpr size_t kMacKeySizeBytes = 32;
static constexpr size_t kMacKeyPaddingSizeBytes = 16;
static constexpr size_t kIvSizeBytes = 16;
static constexpr size_t kCertificateDataSizeBytes = 4 * 1024;
static constexpr uint32_t kRsaSignatureSizeBytes = 256;
@@ -52,11 +54,11 @@ namespace wvcas {
// module.
namespace {
static constexpr char kKeyCompanyName[] = "company_name";
static constexpr char kKeyModelName[] = "model_name";
static constexpr char kKeyArchitectureName[] = "architecture_name";
static constexpr char kKeyDeviceName[] = "device_name";
static constexpr char kKeyProductName[] = "product_name";
constexpr char kKeyCompanyName[] = "company_name";
constexpr char kKeyModelName[] = "model_name";
constexpr char kKeyArchitectureName[] = "architecture_name";
constexpr char kKeyDeviceName[] = "device_name";
constexpr char kKeyProductName[] = "product_name";
// TODO(jfore): These variables are currently unused and are flagged as build
// errors in android. These values will be used in a future cl.
@@ -412,8 +414,12 @@ CasStatus CasLicense::GenerateEntitlementRequest(
// TODO(jfore): Anti rollback support?
// TODO(jfore): Read from oemcrypto and fail if the version is < 14.
client_capabilities->set_oem_crypto_api_version(14);
uint32_t api_version = 0;
status = crypto_session_->APIVersion(&api_version);
if (!status.ok()) {
return status;
}
client_capabilities->set_oem_crypto_api_version(api_version);
// TODO(jfore): Handle hdcp capabilities.
@@ -532,7 +538,8 @@ CasStatus CasLicense::HandleStoredLicense(const std::string& wrapped_rsa_key,
return CasStatus(CasStatusCode::kInvalidLicenseFile,
"unable to parse license renewal response");
}
// Disable the expiration callback when renewal file is a part of license file.
// Disable the expiration callback when renewal file is a part of license
// file.
is_renewal_in_license_file_ = true;
}
@@ -741,21 +748,24 @@ CasStatus CasLicense::InstallLicense(const std::string& session_key,
for (int i = 0; i < license.key_size(); ++i) {
if (license.key(i).type() == License_KeyContainer::SIGNING) {
mac_key_iv_str.assign(license.key(i).iv());
// Strip off PKCS#5 padding
mac_key_str.assign(license.key(i).key().data(), kMacKeySizeBytes);
mac_key_str.assign(license.key(i).key());
}
}
if (license.policy().can_renew() ||
(mac_key_iv_str.size() != 0 || mac_key_str.size() != 0)) {
// In V2.1 license protocol, the expected mac key size is 80 bytes: 32 bytes
// server key; 32 bytes client key; 16 bytes of padding.
if (mac_key_iv_str.size() != kIvSizeBytes ||
mac_key_str.size() != kMacKeySizeBytes) {
mac_key_str.size() != 2 * kMacKeySizeBytes + kMacKeyPaddingSizeBytes) {
std::ostringstream err_string;
err_string << "mac key/iv size error (key/iv size expected: "
<< kMacKeySizeBytes << "/" << kIvSizeBytes
<< " actual: " << mac_key_str.size() << "/"
<< 2 * kMacKeySizeBytes + kMacKeyPaddingSizeBytes << "/"
<< kIvSizeBytes << " actual: " << mac_key_str.size() << "/"
<< mac_key_iv_str.size();
return CasStatus(CasStatusCode::kCryptoSessionError, err_string.str());
}
// Strip off the padding.
mac_key_str.resize(2 * kMacKeySizeBytes);
}
std::vector<CryptoKey> key_array = ExtractEntitlementKeys(license);
@@ -972,40 +982,39 @@ void CasLicense::UpdateLicenseForLicenseRemove() {
}
void CasLicense::OnLicenseExpiration() {
if(event_listener_ && IsExpired() && !is_renewal_in_license_file_) {
event_listener_->OnLicenseExpiration();
if (event_listener_ && IsExpired() && !is_renewal_in_license_file_) {
event_listener_->OnLicenseExpiration();
}
}
void CasLicense::OnNewRenewalServerUrl(
const std::string& renewal_server_url) {
if(event_listener_) {
void CasLicense::OnNewRenewalServerUrl(const std::string& renewal_server_url) {
if (event_listener_) {
event_listener_->OnNewRenewalServerUrl(renewal_server_url);
}
}
void CasLicense::OnSessionRenewalNeeded() {
if(event_listener_) {
if (event_listener_) {
event_listener_->OnSessionRenewalNeeded();
}
}
void CasLicense::OnSessionKeysChange(const wvcas::KeyStatusMap& keys_status,
bool has_new_usable_key) {
if(event_listener_) {
if (event_listener_) {
event_listener_->OnSessionKeysChange(keys_status, has_new_usable_key);
}
}
void CasLicense::OnExpirationUpdate(int64_t new_expiry_time_seconds) {
if(event_listener_) {
if (event_listener_) {
event_listener_->OnExpirationUpdate(new_expiry_time_seconds);
}
}
void CasLicense::OnAgeRestrictionUpdated(const WvCasSessionId& sessionId,
uint8_t ecm_age_restriction) {
if(event_listener_) {
uint8_t ecm_age_restriction) {
if (event_listener_) {
event_listener_->OnAgeRestrictionUpdated(sessionId, ecm_age_restriction);
}
}

View File

@@ -11,6 +11,7 @@
#include "cas_util.h"
#include "log.h"
static const uint32_t kExpectedOEMCryptoVersion = 16;
namespace wvcas {
@@ -53,11 +54,11 @@ void FillEntitledContentKeyObjectFromKeyData(
if (nullptr == dest) {
return;
}
std::string entitlement_key_id
(src.entitlement_key_id.begin(), src.entitlement_key_id.end());
std::string entitlement_key_id(src.entitlement_key_id.begin(),
src.entitlement_key_id.end());
std::string content_key_id(src.key_id.begin(), src.key_id.end());
std::string
content_key_data_iv(src.wrapped_key_iv.begin(), src.wrapped_key_iv.end());
std::string content_key_data_iv(src.wrapped_key_iv.begin(),
src.wrapped_key_iv.end());
std::string content_key_data(src.wrapped_key.begin(), src.wrapped_key.end());
std::string content_iv(src.content_iv.begin(), src.content_iv.end());
@@ -392,6 +393,12 @@ OEMCryptoResult CryptoInterface::OEMCrypto_RemoveEntitledKeySession(
});
}
uint32_t CryptoInterface::OEMCrypto_APIVersion() {
return lock_->WithOecReadLock("APIVersion", [&] {
return oemcrypto_interface_->OEMCrypto_APIVersion();
});
}
OEMCryptoResult CryptoInterface::create_internal(
OEMCryptoInterface* oemcrypto_interface,
std::unique_ptr<CryptoInterface>* init) {
@@ -438,9 +445,8 @@ CryptoInterface::~CryptoInterface() {
lock_->WithStaticFieldWriteLock("Terminate", [&] {
if (session_count_ > 0) {
if (--session_count_ == 0) {
lock_->WithOecWriteLock("Terminate", [&] {
oemcrypto_interface_->OEMCrypto_Terminate();
});
lock_->WithOecWriteLock(
"Terminate", [&] { oemcrypto_interface_->OEMCrypto_Terminate(); });
}
}
});
@@ -1087,6 +1093,8 @@ CasStatus CryptoSession::RefreshKeys(const std::string& message,
if (!key.key_control().empty()) {
key_object.key_control_iv.offset =
GetOffset(message, key.key_control_iv());
key_object.key_control_iv.length = key.key_control_iv().length();
key_object.key_control.offset = GetOffset(message, key.key_control());
key_object.key_control.length = key.key_control().length();
}
}
@@ -1180,4 +1188,23 @@ CasStatus CryptoSession::RemoveEntitledKeySession(
return CasStatus::OkStatus();
}
CasStatus CryptoSession::APIVersion(uint32_t* api_version) {
if (!crypto_interface_) {
return CasStatus(CasStatusCode::kCryptoSessionError,
"missing crypto interface");
}
uint32_t oemcrypto_api_version = crypto_interface_->OEMCrypto_APIVersion();
if (oemcrypto_api_version != kExpectedOEMCryptoVersion) {
std::ostringstream err_string;
err_string << "OEMCrypto_APIVersion returned: " << oemcrypto_api_version
<< ". While the correct API version should be: "
<< kExpectedOEMCryptoVersion;
return CasStatus(CasStatusCode::kOEMCryptoVersionMismatch,
err_string.str());
}
*api_version = oemcrypto_api_version;
return CasStatus::OkStatus();
}
} // namespace wvcas

View File

@@ -5,35 +5,63 @@
#include "ecm_parser.h"
#include <arpa/inet.h>
#include <algorithm>
#include "log.h"
namespace wvcas {
namespace {
// ECM constants
static constexpr uint8_t kSequenceCountMask = 0xF8; // Mode bits 3..7
static constexpr uint8_t kCryptoModeFlags = (0x3 << 1); // Mode bits 1..2
static constexpr uint8_t kCryptoModeFlagsV2 = (0xF << 1); // Mode bits 1..4
static constexpr uint8_t kRotationFlag = (0x1 << 0); // Mode bit 0
static constexpr uint8_t kContentIVSizeFlag = (0x1 << 6);
static constexpr uint8_t kAgeRestrictionMask = (0x1F << 1);
constexpr uint8_t kSequenceCountMask = 0xF8; // Mode bits 3..7
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);
static constexpr size_t kEntitlementKeyIDSizeBytes = 16;
static constexpr size_t kContentKeyIDSizeBytes = 16;
static constexpr size_t kContentKeyDataSizeBytes = 16;
static constexpr size_t kWrappedKeyIVSizeBytes = 16;
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.
static constexpr size_t kContentKeyMaxIVSizeBytes = 16;
static constexpr uint16_t kWidevineCaId = 0x4AD4;
constexpr size_t kContentKeyMaxIVSizeBytes = 16;
// 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.
static constexpr uint16_t kSectionHeader1 = 0x80;
static constexpr uint16_t kSectionHeader2 = 0x81;
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;
static constexpr size_t kMaxTsPayloadSizeBytes = 184;
constexpr size_t kMaxTsPayloadSizeBytes = 184;
// 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
@@ -92,29 +120,27 @@ const EcmKeyData* EcmParser::key_slot_data(KeySlotId id) const {
bool EcmParser::create(const CasEcm& cas_ecm,
std::unique_ptr<const EcmParser>* parser) {
if (nullptr == parser || (cas_ecm.size() < sizeof(EcmDescriptor))) {
if (parser == nullptr) {
return false;
}
// Detect and strip optional section header.
// Use the index of kWidevineCaId to identify the header size.
const CasEcm::const_iterator loc_WVCaId_firstByte =
std::find(cas_ecm.begin(), cas_ecm.end(), kWidevineCaId >> 8);
const CasEcm::const_iterator loc_WVCaId_secondByte =
std::find(cas_ecm.begin(), cas_ecm.end(), kWidevineCaId & 0xFF);
int offset = find_ecm_start_index(cas_ecm);
if (offset < 0 || (static_cast<int>(cas_ecm.size()) - offset <
static_cast<int>(sizeof(EcmDescriptor)))) {
return false;
}
if (loc_WVCaId_firstByte >= cas_ecm.end() - 1 ||
loc_WVCaId_firstByte + 1 != loc_WVCaId_secondByte) {
return false;
}
const CasEcm& ecm = std::vector<uint8_t>(loc_WVCaId_firstByte, cas_ecm.end());
if (ecm.size() < sizeof(EcmDescriptor)) {
return false;
}
//reconfirm ecm data should start with kWidevineCaId
uint16_t ca_id_val = ntohs(*reinterpret_cast<const uint16_t*>(ecm.data()));
if (ca_id_val != kWidevineCaId) {
const CasEcm ecm(cas_ecm.begin() + offset, cas_ecm.end());
// Reconfirm ecm data should start with valid Widevine CAS ID.
uint16_t cas_id_val = ntohs(*reinterpret_cast<const uint16_t*>(ecm.data()));
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 false;
}
// Using 'new' to access a non-public constructor.
std::unique_ptr<const EcmParser> new_parser =
std::unique_ptr<const EcmParser>(new EcmParser(ecm));

View File

@@ -151,8 +151,8 @@ bool LicenseKeys::MeetsConstraints(const KeyId& key_id) {
}
}
void LicenseKeys::ApplyConstraints(
uint32_t new_resolution, HdcpCapability new_hdcp_level) {
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);
@@ -321,8 +321,8 @@ bool LicenseKeyStatus::ApplyStatusChange(KeyStatus new_status,
// 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) {
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_);

View File

@@ -118,6 +118,7 @@ class OEMCryptoInterface::Impl {
OEMCrypto_SESSION oec_session, OEMCrypto_SESSION* key_session);
typedef OEMCryptoResult (*RemoveEntitledKeySession_t)(
OEMCrypto_SESSION key_session);
typedef uint32_t (*APIVersion_t)();
Initialize_t Initialize = nullptr;
Terminate_t Terminate = nullptr;
@@ -148,6 +149,7 @@ class OEMCryptoInterface::Impl {
SecurityLevel_t SecurityLevel = nullptr;
CreateEntitledKeySession_t CreateEntitledKeySession = nullptr;
RemoveEntitledKeySession_t RemoveEntitledKeySession = nullptr;
APIVersion_t APIVersion = nullptr;
private:
bool initialize() {
@@ -191,6 +193,7 @@ class OEMCryptoInterface::Impl {
LOAD_SYM(SecurityLevel);
LOAD_SYM(CreateEntitledKeySession);
LOAD_SYM(RemoveEntitledKeySession);
LOAD_SYM(APIVersion);
// Optional methods that may be available.
TRY_LOAD_SYM(LoadTestKeybox);
@@ -408,4 +411,7 @@ OEMCryptoResult OEMCryptoInterface::OEMCrypto_RemoveEntitledKeySession(
return impl_->RemoveEntitledKeySession(key_session);
}
uint32_t OEMCryptoInterface::OEMCrypto_APIVersion() const {
return impl_->APIVersion();
}
} // namespace wvcas

View File

@@ -5,6 +5,7 @@
#include "policy_engine.h"
#include <limits.h>
#include <sstream>
#include "log.h"
@@ -111,8 +112,8 @@ void PolicyEngine::NotifyRenewalServerUpdate() {
}
}
void PolicyEngine::NotifyLicenseExpired(LicenseState license_state){
if(event_listener_ && license_state == kLicenseStateExpired) {
void PolicyEngine::NotifyLicenseExpired(LicenseState license_state) {
if (event_listener_ && license_state == kLicenseStateExpired) {
event_listener_->OnLicenseExpiration();
}
}

View File

@@ -1,3 +1,5 @@
#include "widevine_cas_api.h"
#include <openssl/sha.h>
#include "ca_descriptor.pb.h"
@@ -6,11 +8,11 @@
#include "license_protocol.pb.h"
#include "log.h"
#include "string_conversions.h"
#include "widevine_cas_api.h"
#include "widevine_cas_session_map.h"
static constexpr char kBasePathPrefix[] = "/data/vendor/mediacas/IDM/widevine/";
static constexpr char kCertFileBase[] = "cert.bin";
constexpr char kBasePathPrefix[] = "/data/vendor/mediacas/IDM/widevine/";
constexpr char kCertFileBase[] = "cert.bin";
constexpr char kLicenseFileNameSuffix[] = ".lic";
namespace {
bool ReadFileFromStorage(wvutil::FileSystem& file_system,
@@ -42,7 +44,7 @@ bool RemoveFile(wvutil::FileSystem& file_system, const std::string& filename) {
if (!file_system.Exists(filename)) {
return false;
}
if(!file_system.Remove(filename)){
if (!file_system.Remove(filename)) {
return false;
}
return true;
@@ -62,9 +64,9 @@ bool StoreFile(wvutil::FileSystem& file_system, const std::string& filename,
return true;
}
std::string GenerateLicenseFilename(const std::string& id_data_from_media_id,
std::string GenerateLicenseFilename(const std::string& content_id,
const std::string& provider_id) {
std::string data(id_data_from_media_id + provider_id);
std::string data(content_id + provider_id);
std::string hash;
hash.resize(SHA256_DIGEST_LENGTH);
const unsigned char* input =
@@ -72,7 +74,7 @@ std::string GenerateLicenseFilename(const std::string& id_data_from_media_id,
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(".lic"));
std::string(kLicenseFileNameSuffix));
}
} // namespace
@@ -87,13 +89,6 @@ class MediaContext : public CasMediaId {
const std::string content_id() override { return pssh_.content_id(); }
const std::string provider_id() override { return pssh_.provider(); }
const int group_ids_size() override { return pssh_.group_ids_size(); }
const std::string group_id() override {
if (group_ids_size() > 0) {
return pssh_.group_ids(0);
}
return "";
}
CasStatus initialize(const std::string& init_data) override {
if (!pssh_.ParseFromString(init_data)) {
@@ -134,21 +129,15 @@ void WidevineCas::OnTimerEvent() {
// Delete expired license after firing expired event in policy_engine
if (cas_license_->IsExpired() && (media_id_.get() != nullptr)) {
std::string filename;
if (media_id_->group_ids_size() > 0) {
filename = GenerateLicenseFilename(media_id_->group_id(),
media_id_->provider_id());
} else {
filename = GenerateLicenseFilename(media_id_->content_id(),
media_id_->provider_id());
}
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.");
}
std::string filename = GenerateLicenseFilename(media_id_->content_id(),
media_id_->provider_id());
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.");
}
}
}
}
}
@@ -306,21 +295,16 @@ CasStatus WidevineCas::handleProvisioningResponse(const std::string& response) {
}
CasStatus WidevineCas::generateEntitlementRequest(
const std::string& init_data, std::string* entitlement_request) {
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 license_file;
std::string filename;
if (media_id_->group_ids_size() > 0) {
filename = GenerateLicenseFilename(media_id_->group_id(),
media_id_->provider_id());
} else {
filename = GenerateLicenseFilename(media_id_->content_id(),
media_id_->provider_id());
}
std::string filename = GenerateLicenseFilename(media_id_->content_id(),
media_id_->provider_id());
if (ReadFileFromStorage(*file_system_, filename, &license_file)) {
CasStatus status =
cas_license_->HandleStoredLicense(wrapped_rsa_key_, license_file);
@@ -329,14 +313,16 @@ CasStatus WidevineCas::generateEntitlementRequest(
// delete the stored license file.
std::unique_lock<std::mutex> locker(lock_);
if (cas_license_->IsExpired()) {
if(!RemoveFile(*file_system_, filename)) {
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 is expired, unable to process emm");
}
license_id =
filename.substr(0, filename.size() - strlen(kLicenseFileNameSuffix));
policy_timer_.Start(this, 1);
has_license_ = true;
return HandleDeferredECMs();
@@ -355,7 +341,7 @@ CasStatus WidevineCas::generateEntitlementRequest(
}
CasStatus WidevineCas::handleEntitlementResponse(const std::string& response,
std::string& license_id) {
std::string& license_id) {
if (response.empty()) {
return CasStatus(CasStatusCode::kCasLicenseError,
"empty entitlement response");
@@ -380,18 +366,12 @@ CasStatus WidevineCas::handleEntitlementResponse(const std::string& response,
}
if (!device_file.empty()) {
std::string filename;
if (media_id_->group_ids_size() > 0) {
filename = GenerateLicenseFilename(media_id_->group_id(),
media_id_->provider_id());
} else {
filename = GenerateLicenseFilename(media_id_->content_id(),
media_id_->provider_id());
}
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() - std::string(".lic").size());
license_id =
filename.substr(0, filename.size() - strlen(kLicenseFileNameSuffix));
}
}
return status;
@@ -421,47 +401,34 @@ CasStatus WidevineCas::handleEntitlementRenewalResponse(
return status;
}
if (!device_file.empty()) {
std::string filename;
if (media_id_->group_ids_size() > 0) {
filename = GenerateLicenseFilename(media_id_->group_id(),
media_id_->provider_id());
} else {
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() - std::string(".lic").size());
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() - std::string(".lic").size());
}
return CasStatusCode::kNoError;
}
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 (nullptr == media_id_.get() ) {
return CasStatus(CasStatusCode::kCasLicenseError, "No media id");
if (nullptr == media_id_.get()) {
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,
return CasStatus(CasStatusCode::kInvalidLicenseFile,
"unable to remove license file from disk");
}
LOGI("Remove license file from disk successfully.");
std::string used_license_filename;
if (media_id_->group_ids_size() > 0) {
used_license_filename = GenerateLicenseFilename(media_id_->group_id(),
media_id_->provider_id());
} else {
used_license_filename = GenerateLicenseFilename(media_id_->content_id(),
media_id_->provider_id());
}
std::string used_license_filename = GenerateLicenseFilename(
media_id_->content_id(), media_id_->provider_id());
if (file_name.compare(used_license_filename) == 0) {
// 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.
// play stream, store and renew license unless a new plugin instance is
// created.
std::unique_lock<std::mutex> locker(lock_);
cas_license_->UpdateLicenseForLicenseRemove();
}
@@ -479,7 +446,7 @@ CasStatus WidevineCas::ProcessCAPrivateData(const CasData& private_data,
return CasStatus(CasStatusCode::kInvalidParameter,
"missing output buffer for init_data");
}
// Parse provider, content id and group id from CA descriptor.
// 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()) {
@@ -491,10 +458,6 @@ CasStatus WidevineCas::ProcessCAPrivateData(const CasData& private_data,
video_widevine::WidevinePsshData pssh;
pssh.set_provider(descriptor.provider());
pssh.set_content_id(descriptor.content_id());
// group id is optional in ca_descriptor.
if (descriptor.has_group_id()) {
pssh.add_group_ids(descriptor.group_id());
}
pssh.set_type(video_widevine::WidevinePsshData::ENTITLEMENT);
pssh.SerializeToString(init_data);
return CasStatusCode::kNoError;

View File

@@ -15,8 +15,13 @@ using android::BAD_VALUE;
using android::OK;
// Widevine Technologies CA system ID.
static const int32_t kWidevineCAID = 0x4AD4;
static const char kName[] = "WidevineCas";
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() {
@@ -33,10 +38,9 @@ WidevineCasFactory* WidevineCasFactory::createCasFactory() {
}
bool WidevineCasFactory::isSystemIdSupported(int32_t CA_system_id) const {
if (kWidevineCAID != CA_system_id) {
return false;
}
return true;
return (CA_system_id == kWidevineCAID) ||
(CA_system_id >= kWidevineNewCasIdLowerBound &&
CA_system_id <= kWidevineNewCasIdUpperBound);
}
status_t WidevineCasFactory::queryPlugins(
@@ -45,7 +49,12 @@ status_t WidevineCasFactory::queryPlugins(
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;
}

View File

@@ -42,11 +42,10 @@ WvCasSessionId androidSessionIdToWidevine(
return wv_session_id;
}
CasSessionId widevineSessionIdToAndroid(
const WvCasSessionId& wv_session_id) {
CasSessionId widevineSessionIdToAndroid(const WvCasSessionId& wv_session_id) {
auto wv_session_id_begin = reinterpret_cast<const uint8_t*>(&wv_session_id);
CasSessionId android_session_id
(wv_session_id_begin, wv_session_id_begin + sizeof(wv_session_id));
CasSessionId android_session_id(wv_session_id_begin,
wv_session_id_begin + sizeof(wv_session_id));
return android_session_id;
}
@@ -157,8 +156,8 @@ status_t WidevineCasPlugin::processEcm(const CasSessionId& sessionId,
return android::ERROR_CAS_DECRYPT_UNIT_NOT_INITIALIZED;
case CasStatusCode::kAccessDeniedByParentalControl:
CallBack(reinterpret_cast<void*>(app_data_),
ACCESS_DENIED_BY_PARENTAL_CONTROL, wv_session_id,
&error[0], error.size(), &sessionId);
ACCESS_DENIED_BY_PARENTAL_CONTROL, wv_session_id, &error[0],
error.size(), &sessionId);
return android::ERROR_CAS_DECRYPT;
default:
CallBack(reinterpret_cast<void*>(app_data_), CAS_ERROR,
@@ -191,8 +190,7 @@ status_t WidevineCasPlugin::sendEvent(int32_t event, int32_t arg,
}
status_t WidevineCasPlugin::sendSessionEvent(const CasSessionId& sessionId,
int32_t event,
int32_t arg,
int32_t event, int32_t arg,
const CasData& eventData) {
CasStatus status = processEvent(event, arg, eventData, &sessionId);
if (status.status_code() != CasStatusCode::kNoError) {
@@ -240,8 +238,9 @@ status_t WidevineCasPlugin::provision(const String8& provisionString) {
status_t WidevineCasPlugin::requestLicense(const std::string& init_data) {
std::string signed_license_request;
std::string license_id;
CasStatus status = widevine_cas_.generateEntitlementRequest(
init_data, &signed_license_request);
init_data, &signed_license_request, license_id);
if (!status.ok()) {
return INVALID_OPERATION;
}
@@ -255,7 +254,8 @@ status_t WidevineCasPlugin::requestLicense(const std::string& init_data) {
signed_license_request.size(), nullptr);
} else {
CallBack(reinterpret_cast<void*>(app_data_), LICENSE_CAS_READY,
LICENSE_CAS_READY, nullptr, 0, nullptr);
LICENSE_CAS_READY, reinterpret_cast<uint8_t*>(&license_id[0]),
license_id.size(), nullptr);
}
is_emm_request_sent_ = true;
return OK;
@@ -270,8 +270,7 @@ std::shared_ptr<CryptoSession> WidevineCasPlugin::getCryptoSession() {
return std::make_shared<CryptoSession>();
}
CasStatus WidevineCasPlugin::processEvent(int32_t event,
int32_t arg,
CasStatus WidevineCasPlugin::processEvent(int32_t event, int32_t arg,
const CasData& eventData,
const CasSessionId* sessionId) {
switch (event) {
@@ -329,8 +328,8 @@ CasStatus WidevineCasPlugin::HandleEntitlementResponse(
}
std::string resp_string(response.begin(), response.end());
std::string license_id;
CasStatus
status = widevine_cas_.handleEntitlementResponse(resp_string, license_id);
CasStatus status =
widevine_cas_.handleEntitlementResponse(resp_string, license_id);
if (!status.ok()) {
return status;
}
@@ -352,11 +351,9 @@ CasStatus WidevineCasPlugin::HandleEntitlementRenewalResponse(
if (!status.ok()) {
return status;
}
CallBack(reinterpret_cast<void*>(app_data_),
CallBack(reinterpret_cast<void*>(app_data_), LICENSE_CAS_RENEWAL_READY,
LICENSE_CAS_RENEWAL_READY,
LICENSE_CAS_RENEWAL_READY,
reinterpret_cast<uint8_t*>(&license_id[0]),
license_id.size(),
reinterpret_cast<uint8_t*>(&license_id[0]), license_id.size(),
nullptr);
return CasStatusCode::kNoError;
}
@@ -384,8 +381,7 @@ CasStatus WidevineCasPlugin::HandleSetParentalControlAge(const CasData& data) {
CasStatus WidevineCasPlugin::HandleLicenseRemoval(const CasData& license_id) {
if (license_id.empty()) {
return CasStatus(CasStatusCode::kInvalidParameter,
"empty license id");
return CasStatus(CasStatusCode::kInvalidParameter, "empty license id");
}
std::string license_id_str(license_id.begin(), license_id.end());
@@ -398,12 +394,9 @@ CasStatus WidevineCasPlugin::HandleLicenseRemoval(const CasData& license_id) {
error.size(), nullptr);
return status;
}
CallBack(reinterpret_cast<void*>(app_data_),
LICENSE_REMOVED,
LICENSE_REMOVED,
CallBack(reinterpret_cast<void*>(app_data_), LICENSE_REMOVED, LICENSE_REMOVED,
reinterpret_cast<uint8_t*>(&license_id_str[0]),
license_id_str.size(),
nullptr);
license_id_str.size(), nullptr);
return CasStatusCode::kNoError;
}
@@ -450,10 +443,7 @@ void WidevineCasPlugin::OnLicenseExpiration() {
LOGI("OnLicenseExpiration");
CallBack(reinterpret_cast<void*>(app_data_),
android::ERROR_CAS_LICENSE_EXPIRED,
android::ERROR_CAS_LICENSE_EXPIRED,
nullptr,
0,
nullptr);
android::ERROR_CAS_LICENSE_EXPIRED, nullptr, 0, nullptr);
}
void WidevineCasPlugin::OnAgeRestrictionUpdated(const WvCasSessionId& sessionId,
@@ -464,11 +454,8 @@ void WidevineCasPlugin::OnAgeRestrictionUpdated(const WvCasSessionId& sessionId,
sessionId, &ecm_age_restriction, 1, &android_session_id);
}
void WidevineCasPlugin::CallBack(void* appData,
int32_t event,
int32_t arg,
uint8_t* data,
size_t size,
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);