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:
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
76
plugin/include/ecm_parser_v2.h
Normal file
76
plugin/include/ecm_parser_v2.h
Normal 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
|
||||
60
plugin/include/ecm_parser_v3.h
Normal file
60
plugin/include/ecm_parser_v3.h
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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
|
||||
|
||||
182
plugin/src/ecm_parser_v2.cpp
Normal file
182
plugin/src/ecm_parser_v2.cpp
Normal 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
|
||||
174
plugin/src/ecm_parser_v3.cpp
Normal file
174
plugin/src/ecm_parser_v3.cpp
Normal 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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user