Regular update

Plugin:
1. Process ECM v3 and send fingerprinting/service_blocking events
2. Rmove unused function Ctr128Add
3. Add support for ECM v3

OEMCrypto:
1. Update API description of OEMCrypto_LoadCasECMKeys
2. Fix android build files for ODK
3. Load content keys to shared memory
4. Move KCB check to LoadCasKeys call
5. Support even/odd content keys to share entitlement key
This commit is contained in:
Lu Chen
2021-01-05 10:16:26 -08:00
parent 66d8498d2c
commit 00785b2ccd
38 changed files with 2234 additions and 747 deletions

View File

@@ -10,6 +10,8 @@ cc_library_static {
"src/widevine_media_cas.cpp",
"src/cas_license.cpp",
"src/ecm_parser.cpp",
"src/ecm_parser_v2.cpp",
"src/ecm_parser_v3.cpp",
"src/license_key_status.cpp",
"src/oemcrypto_interface.cpp",
"src/policy_engine.cpp",

View File

@@ -11,6 +11,8 @@
#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 {
@@ -48,9 +50,41 @@ typedef enum {
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 SESSION_FINGERPRINTING_CONTROL event.
typedef enum {
FINGERPRINTING_CONTROL = 0,
} SessionFingerprintingFieldType;
// Types used inside an SESSION_SERVICE_BLOCKING_GROUPS event.
typedef enum {
SERVICE_BLOCKING_DEVICE_GROUP = 0,
} SessionServiceBlockingFieldType;
#endif // CAS_EVENTS_H

View File

@@ -110,6 +110,13 @@ class CasLicense : public wvutil::TimerHandler, public wvcas::CasEventListener {
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;
// Query to see if the license is expired.
virtual bool IsExpired() const;

View File

@@ -89,6 +89,14 @@ class CasEventListener {
virtual void OnAgeRestrictionUpdated(const WvCasSessionId& sessionId,
uint8_t ecm_age_restriction) = 0;
// Notifies listeners of new fingerprinting info.
virtual void OnSessionFingerprintingUpdated(
const WvCasSessionId& sessionId, const CasData& fingerprinting) = 0;
// Notifies listeners of new service blocking info.
virtual void OnSessionServiceBlockingUpdated(
const WvCasSessionId& sessionId, const CasData& service_blocking) = 0;
CasEventListener(const CasEventListener&) = delete;
CasEventListener& operator=(const CasEventListener&) = delete;
};

View File

@@ -9,56 +9,42 @@
#include <vector>
#include "cas_types.h"
#include "media_cas.pb.h"
namespace wvcas {
enum class KeySlotId { kEvenKeySlot, kOddKeySlot };
struct EcmKeyData;
// EcmParser allows random access to the fields of an ECM.
// The only validation performed is to ensure that the ecm
// passed in is large enough to hold a single key entry.
class EcmParser {
protected:
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 bool create(const CasEcm& ecm,
std::unique_ptr<const EcmParser>* parser);
static std::unique_ptr<const EcmParser> Create(const CasEcm& ecm);
// Accessor methods.
virtual uint8_t version() const;
virtual uint8_t sequence_count() const;
virtual CryptoMode crypto_mode() const;
virtual bool rotation_enabled() const;
virtual size_t content_iv_size() const;
virtual uint8_t age_restriction() const;
virtual const std::vector<uint8_t> entitlement_key_id(KeySlotId id) const;
virtual const std::vector<uint8_t> content_key_id(KeySlotId id) const;
virtual const std::vector<uint8_t> wrapped_key_data(KeySlotId id) const;
virtual const std::vector<uint8_t> wrapped_key_iv(KeySlotId id) const;
virtual const std::vector<uint8_t> content_iv(KeySlotId id) const;
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;
EcmParser(const EcmParser&) = delete;
EcmParser& operator=(const EcmParser&) = delete;
private:
// Constructs an EcmParser using |ecm|.
explicit EcmParser(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_;
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;
};
} // namespace wvcas

View File

@@ -0,0 +1,76 @@
// 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<const 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;
// 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 ""; }
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,60 @@
// 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<const 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 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;
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_;
};
} // namespace wvcas
#endif // ECM_PARSER_V3_H

View File

@@ -7,11 +7,13 @@
#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 {
@@ -45,7 +47,7 @@ class WidevineCasSession {
virtual ~WidevineCasSession();
CasStatus initialize(std::shared_ptr<CryptoSession> crypto_session,
uint32_t* session_id);
CasEventListener* event_listener, uint32_t* session_id);
// Get the current key information. This method will be used by a descrambler
// plugin to obtain the current key information.
@@ -80,6 +82,13 @@ class WidevineCasSession {
std::shared_ptr<CryptoSession> crypto_session_;
// Id of the entitled key session in OEMCrypto associated with this session.
uint32_t 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_;
};
} // namespace wvcas

View File

@@ -112,6 +112,11 @@ class WidevineCasPlugin : public CasPlugin, public CasEventListener {
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;
// Choose to use |callback_| or |callback_ext_| to send back information.
// |sessionId| is ignored if |callback_ext_| is null,

View File

@@ -1018,4 +1018,20 @@ void CasLicense::OnAgeRestrictionUpdated(const WvCasSessionId& sessionId,
event_listener_->OnAgeRestrictionUpdated(sessionId, ecm_age_restriction);
}
}
void CasLicense::OnSessionFingerprintingUpdated(const WvCasSessionId& sessionId,
const CasData& fingerprinting) {
if (event_listener_ != nullptr) {
event_listener_->OnSessionFingerprintingUpdated(sessionId, fingerprinting);
}
}
void CasLicense::OnSessionServiceBlockingUpdated(
const WvCasSessionId& sessionId, const CasData& service_blocking) {
if (event_listener_ != nullptr) {
event_listener_->OnSessionServiceBlockingUpdated(sessionId,
service_blocking);
}
}
} // namespace wvcas

View File

@@ -85,39 +85,6 @@ void FillEntitledContentKeyObjectFromKeyData(
dest->cipher_mode = CipherModeFromKeyData(src.cipher_mode);
}
// Increment the IV based on the number of encrypted block. IV size is assumed
// to be 16 bytes.
static constexpr uint32_t kIvSizeBytesBytes = 16;
static constexpr uint32_t kCencIvSize = 8;
static const uint32_t kAesBlockSizeBytes = 16;
bool Ctr128Add(size_t block_count, uint8_t* counter) {
if (counter == nullptr) return false;
if (0 == block_count) return true;
uint8_t carry = 0;
uint8_t n = kIvSizeBytesBytes - 1;
// Update the counter one byte at a time.
while (n >= kCencIvSize) {
// Grab a single byte of the block_count.
uint32_t temp = block_count & 0xff;
// Add the corresponding byte from the counter value.
temp += counter[n];
// Add in the carry.
temp += carry;
// Write back the updated counter byte and set the carry value as needed.
counter[n] = temp & 0xff;
carry = (temp & 0x100) ? 1 : 0;
// Update block_count and set the counter index for the next byte.
block_count = block_count >> 8;
n--;
// Early exit if nothing to do.
if (!block_count && !carry) {
break;
}
}
return true;
}
} // namespace
shared_mutex CryptoLock::static_field_mutex_;

View File

@@ -4,29 +4,17 @@
#include "ecm_parser.h"
#include <arpa/inet.h>
#include <algorithm>
#include "ecm_parser_v2.h"
#include "ecm_parser_v3.h"
#include "log.h"
namespace wvcas {
namespace {
// ECM constants
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);
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;
// 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;
@@ -43,8 +31,6 @@ constexpr size_t kSectionHeaderSize = 3;
constexpr size_t kSectionHeaderWithPointerSize = 4;
constexpr uint8_t kPointerFieldZero = 0x00;
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) {
@@ -65,183 +51,34 @@ int find_ecm_start_index(const CasEcm& cas_ecm) {
} // 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.
EcmParser::EcmParser(const CasEcm& ecm) : ecm_(ecm) {}
size_t EcmParser::key_data_size() const {
return sizeof(EcmKeyData) + content_iv_size() - kContentKeyMaxIVSizeBytes;
}
bool EcmParser::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* EcmParser::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 EcmParser::create(const CasEcm& cas_ecm,
std::unique_ptr<const EcmParser>* parser) {
if (parser == nullptr) {
return false;
}
std::unique_ptr<const EcmParser> EcmParser::Create(const CasEcm& cas_ecm) {
// Detect and strip optional section header.
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;
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());
// Reconfirm ecm data should start with valid Widevine CAS ID.
uint16_t cas_id_val = ntohs(*reinterpret_cast<const uint16_t*>(ecm.data()));
// 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 false;
return nullptr;
}
// Using 'new' to access a non-public constructor.
std::unique_ptr<const EcmParser> new_parser =
std::unique_ptr<const EcmParser>(new EcmParser(ecm));
if (!new_parser->is_valid_size()) {
return false;
if (ecm[kVersionIndex] <= 2) {
std::unique_ptr<const EcmParserV2> parser;
if (!EcmParserV2::create(ecm, &parser)) {
return nullptr;
}
return parser;
} else {
return EcmParserV3::Create(ecm);
}
*parser = std::move(new_parser);
return true;
}
uint8_t EcmParser::version() const {
const EcmDescriptor* ecm = reinterpret_cast<const EcmDescriptor*>(&ecm_[0]);
return ecm->version;
}
uint8_t EcmParser::sequence_count() const {
if (version() == 1) {
const EcmDescriptor* ecm = reinterpret_cast<const EcmDescriptor*>(&ecm_[0]);
return (ecm->flags_cipher_rotation & kSequenceCountMask) >> 3;
}
return 0;
}
CryptoMode EcmParser::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 EcmParser::rotation_enabled() const {
const EcmDescriptor* ecm = reinterpret_cast<const EcmDescriptor*>(&ecm_[0]);
return (ecm->flags_cipher_rotation & kRotationFlag);
}
size_t EcmParser::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 EcmParser::age_restriction() const {
if (version() == 1) {
return 0;
}
const EcmDescriptor* ecm = reinterpret_cast<const EcmDescriptor*>(&ecm_[0]);
return (ecm->flags_iv_age & kAgeRestrictionMask) >> 1;
}
const std::vector<uint8_t> EcmParser::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;
}
const std::vector<uint8_t> EcmParser::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;
}
const std::vector<uint8_t> EcmParser::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;
}
const std::vector<uint8_t> EcmParser::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;
}
const std::vector<uint8_t> EcmParser::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,182 @@
// 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<const EcmParserV2>* parser) {
if (parser == nullptr) {
return false;
}
// Using 'new' to access a non-public constructor.
auto new_parser =
std::unique_ptr<const 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,174 @@
// 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::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)) {}
std::unique_ptr<const EcmParserV3> EcmParserV3::Create(const CasEcm& cas_ecm) {
if (cas_ecm.size() <= kEcmHeaderSize) {
LOGE("ECM is too short. Size: %u", 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<const 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 &&
!ecm_payload_.odd_key_data().entitlement_key_id().empty()
? ecm_payload_.odd_key_data()
: ecm_payload_.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
? ecm_payload_.odd_key_data()
: ecm_payload_.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 &&
!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 &&
!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();
}
} // namespace wvcas

View File

@@ -190,7 +190,7 @@ CasStatus WidevineCas::openSession(WvCasSessionId* sessionId) {
CasSessionPtr session = newCasSession();
CasStatus status = session->initialize(
crypto_session_, reinterpret_cast<uint32_t*>(sessionId));
crypto_session_, event_listener_, reinterpret_cast<uint32_t*>(sessionId));
if (CasStatusCode::kNoError != status.status_code()) {
return status;
}

View File

@@ -3,10 +3,13 @@
// 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 {
@@ -25,7 +28,8 @@ WidevineCasSession::~WidevineCasSession() {
}
CasStatus WidevineCasSession::initialize(
std::shared_ptr<CryptoSession> crypto_session, uint32_t* session_id) {
std::shared_ptr<CryptoSession> crypto_session,
CasEventListener* event_listener, uint32_t* session_id) {
if (crypto_session == nullptr || session_id == nullptr) {
LOGE("WidevineCasSession::initialize: missing input parameters");
return CasStatus(CasStatusCode::kInvalidParameter,
@@ -34,6 +38,7 @@ CasStatus WidevineCasSession::initialize(
crypto_session_ = std::move(crypto_session);
crypto_session_->CreateEntitledKeySession(&key_session_id_);
*session_id = key_session_id_;
event_listener_ = event_listener;
return CasStatusCode::kNoError;
}
@@ -49,7 +54,7 @@ CasStatus WidevineCasSession::processEcm(const CasEcm& ecm,
if (ecm != current_ecm_) {
LOGD("WidevineCasSession::processEcm: received new ecm");
std::unique_ptr<const EcmParser> ecm_parser = getEcmParser(ecm);
if (!ecm_parser) {
if (ecm_parser == nullptr) {
return CasStatus(CasStatusCode::kInvalidParameter, "invalid ecm");
}
@@ -61,6 +66,46 @@ CasStatus WidevineCasSession::processEcm(const CasEcm& ecm,
return CasStatus(CasStatusCode::kAccessDeniedByParentalControl, message);
}
std::vector<uint8_t> message;
if (!ecm_parser->fingerprinting().control().empty()) {
message.push_back(static_cast<uint8_t>(
SessionFingerprintingFieldType::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(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::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(key_session_id_,
message);
}
}
bool load_even = false;
bool load_odd = false;
@@ -118,11 +163,7 @@ CasStatus WidevineCasSession::processEcm(const CasEcm& ecm,
std::unique_ptr<const EcmParser> WidevineCasSession::getEcmParser(
const CasEcm& ecm) const {
std::unique_ptr<const EcmParser> new_ecm_parser;
if (!EcmParser::create(ecm, &new_ecm_parser)) {
return std::unique_ptr<const EcmParser>();
}
return new_ecm_parser;
return EcmParser::Create(ecm);
}
const char* WidevineCasSession::securityLevel() {

View File

@@ -461,6 +461,33 @@ void WidevineCasPlugin::OnAgeRestrictionUpdated(const WvCasSessionId& sessionId,
sessionId, &ecm_age_restriction, 1, &android_session_id);
}
void WidevineCasPlugin::OnSessionFingerprintingUpdated(
const WvCasSessionId& sessionId, const CasData& fingerprinting) {
LOGI("OnSessionFingerprintingUpdated");
const CasSessionId& android_session_id =
widevineSessionIdToAndroid(sessionId);
CallBack(reinterpret_cast<void*>(app_data_),
SESSION_FINGERPRINTING_INFO, /*arg=*/
0,
fingerprinting.empty() ? nullptr
: const_cast<uint8_t*>(&fingerprinting[0]),
fingerprinting.size(), &android_session_id);
}
void WidevineCasPlugin::OnSessionServiceBlockingUpdated(
const WvCasSessionId& sessionId, const CasData& service_blocking) {
LOGI("OnSessionServiceBlockingUpdated");
const CasSessionId& android_session_id =
widevineSessionIdToAndroid(sessionId);
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(), &android_session_id);
}
void WidevineCasPlugin::CallBack(void* appData, int32_t event, int32_t arg,
uint8_t* data, size_t size,
const CasSessionId* sessionId) const {