Change order of loading certificates from pk7 cert

-------------
Add libcurl to media_cas_packager_sdk. libcurl will later be used by a key fetcher to retrieve entitlement key from License Server using a HTTP request.

-------------
Add a function named parsehelper to parse DCSL from the key smith response.

-------------
Move wv_cas_key_fetcher to media_cas_packager_sdk so partners can use it request entitlement keys from License Server.

-------------
Add pkcs7 write method to x509_cert.cc

-------------
Update boringssl_repo to latest in master-with-bazel

-------------
Add a TsPacket class to media_cas_packager_sdk to allow the construction of a ECM TS packet in the SDK.

-------------
Move InsertEcm() from our internal CAS directory to the media_cas_packager_sdk, to be used to build a ECM TS packet by the SDK.

-------------
Add METADATA in common folder

-------------
Refactoring of certificate verification into DrmRootCertificate.

-------------
Extend the default duration of leaf certificates.

-------------
Fix moe_test

-------------
Add a new method to WvCasEcm to allow partner to create a TS packet carrying the generated ECM.

-------------
Change from SHA1 to SHA256 for Cast certificates

-------------
Update crypto mode enumeration to match WV ECM document

-------------
Fix the way we set the validity dates

-------------
Move exported_root/util/status to common/ to prepare for util::Status migration

Also added constructor/operator to copy from/to util::Status.

-------------
Add GenerateDCSLrequest function to certificate_util.h.

-------------
Fix build break

-------------
Allow 'table_id' (in the section header) be specified by caller of SDK method WvCasEcm::GenerateTsPacket().

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=224535399
This commit is contained in:
Fang Yu
2018-12-07 10:16:38 -08:00
parent fb96918196
commit 121d554c20
63 changed files with 4834 additions and 560 deletions

View File

@@ -120,6 +120,17 @@ cc_library(
deps = ["//util:status"],
)
cc_library(
name = "mpeg2ts",
hdrs = [
"mpeg2ts.h",
],
deps = [
"//base",
"@abseil_repo//absl/base:core_headers",
],
)
cc_library(
name = "simulcrypt",
srcs = ["simulcrypt.cc"],
@@ -149,11 +160,60 @@ cc_test(
)
cc_library(
name = "util",
srcs = ["util.cc"],
hdrs = ["util.h"],
name = "ts_packet",
srcs = [
"ts_packet.cc",
],
hdrs = [
"mpeg2ts.h",
"ts_packet.h",
],
deps = [
":mpeg2ts",
"//base",
"@abseil_repo//absl/base:core_headers",
"@abseil_repo//absl/strings",
"//util:status",
"//common:string_util",
],
)
cc_test(
name = "ts_packet_test",
size = "small",
srcs = ["ts_packet_test.cc"],
deps = [
":mpeg2ts",
":ts_packet",
"//base",
"//testing:gunit_main",
],
)
cc_library(
name = "util",
srcs = ["util.cc"],
hdrs = [
"mpeg2ts.h",
"util.h",
],
deps = [
":mpeg2ts",
":ts_packet",
"//base",
"@abseil_repo//absl/base:core_headers",
"//util:status",
],
)
cc_test(
name = "util_test",
size = "small",
srcs = [
"util_test.cc",
],
deps = [
":util",
"//testing:gunit_main",
],
)

View File

@@ -140,12 +140,7 @@ util::Status CasEcm::Initialize(const std::string& content_id,
"Parameter content_iv_size must be kIvSize8 or kIvSize16."};
}
if (ecm_init_parameters.crypto_mode == CryptoMode::kCryptoModeUnspecified) {
return {util::error::INVALID_ARGUMENT, "Invalid crypto mode."};
} else {
crypto_mode_ = ecm_init_parameters.crypto_mode;
}
crypto_mode_ = ecm_init_parameters.crypto_mode;
content_id_ = content_id;
content_provider_ = content_provider;
paired_keys_required_ = ecm_init_parameters.key_rotation_enabled;
@@ -414,9 +409,6 @@ std::string CasEcm::SerializeEcm(const std::vector<EntitledKeyInfo*>& keys) {
generation());
std::bitset<kNumBitsDecryptModeField> decrypt_mode(
static_cast<int>(crypto_mode()));
if (decrypt_mode.to_string() == "00") {
LOG(FATAL) << "Invalid decrypt mode \"00\"";
}
std::bitset<kNumBitsRotationEnabledField> rotation_enabled(
RotationFieldValue(paired_keys_required()));
std::bitset<kNumBitsWrappedKeyIvSizeField> wrapped_key_iv_size(

View File

@@ -0,0 +1,40 @@
////////////////////////////////////////////////////////////////////////////////
// Copyright 2018 Google LLC.
//
// This software is licensed under the terms defined in the Widevine Master
// License Agreement. For a copy of this agreement, please contact
// widevine-licensing@google.com.
////////////////////////////////////////////////////////////////////////////////
#ifndef MEDIA_CAS_PACKAGER_SDK_INTERNAL_MPEG2TS_H_
#define MEDIA_CAS_PACKAGER_SDK_INTERNAL_MPEG2TS_H_
#include <cstddef>
#include <cstdint>
#include "base/macros.h"
namespace widevine {
namespace cas {
// MPEG2 TS Program ID (13 bits).
typedef uint16_t ProgramId;
// MPEG2 TS Continuity Counter (4 bits).
typedef uint8_t ContinuityCounter;
constexpr ProgramId kInvalidPid = 0x2000;
constexpr size_t kTsPacketSize = 188;
constexpr size_t kTsPacketHeaderSize = 4;
constexpr size_t kMaxTsPayloadSize = kTsPacketSize - kTsPacketHeaderSize;
constexpr uint8_t kTsPacketSyncByte = 0x47;
constexpr uint8_t kTsPacketTableId80 = 0x80;
constexpr uint8_t kTsPacketTableId81 = 0x81;
} // namespace cas
} // namespace widevine
#endif // MEDIA_CAS_PACKAGER_SDK_INTERNAL_MPEG2TS_H_

View File

@@ -0,0 +1,77 @@
////////////////////////////////////////////////////////////////////////////////
// Copyright 2018 Google LLC.
//
// This software is licensed under the terms defined in the Widevine Master
// License Agreement. For a copy of this agreement, please contact
// widevine-licensing@google.com.
////////////////////////////////////////////////////////////////////////////////
#include "media_cas_packager_sdk/internal/ts_packet.h"
#include <bitset>
#include "glog/logging.h"
#include "absl/strings/str_cat.h"
#include "util/status.h"
#include "common/string_util.h"
namespace widevine {
namespace cas {
util::Status TsPacket::Write(std::string* output) const {
DCHECK(output);
output->resize(kTsPacketSize);
// TsPacket header.
std::bitset<8> sync_byte(kTsPacketSyncByte);
std::bitset<1> transport_error_indication(transport_error_indication_);
std::bitset<1> payload_unit_start_indicator(payload_unit_start_indicator_);
std::bitset<1> transport_priority(transport_priority_);
std::bitset<13> pid(pid_);
std::bitset<2> transport_scrambling_control(transport_scrambling_control_);
std::bitset<2> adaptation_field_control(adaptation_field_control_);
std::bitset<4> continuity_counter(continuity_counter_);
if (adaptation_field_control_ & kAdaptationFieldOnly) {
return util::Status(util::error::INTERNAL,
"TsPacket does NOT handle adaptation field yet");
}
// Converts header bitset to string.
std::string header_bitset = absl::StrCat(
sync_byte.to_string(), transport_error_indication.to_string(),
payload_unit_start_indicator.to_string(), transport_priority.to_string(),
pid.to_string(), transport_scrambling_control.to_string(),
adaptation_field_control.to_string(), continuity_counter.to_string());
if (header_bitset.size() != 4 * 8) {
return util::Status(util::error::INTERNAL,
absl::StrCat("TS packet header bitset incorret size: ",
header_bitset.size()));
}
std::string serialized_header;
util::Status status = string_util::BitsetStringToBinaryString(
header_bitset, &serialized_header);
if (!status.ok()) {
return util::Status(util::error::INTERNAL,
"Failed to convert TS packet header bitset to std::string");
}
// Write TsPacket payload.
if (payload_.size() != CalculatePayloadSize()) {
return util::Status(
util::error::INVALID_ARGUMENT,
absl::StrCat("Incorrect payload size: ", payload_.size()));
}
// Return header + payload as a TS packet.
*output = serialized_header + payload_;
return util::OkStatus();
}
int32_t TsPacket::CalculatePayloadSize() const {
int32_t adaptation_length = 0;
return kMaxTsPayloadSize - adaptation_length;
}
} // namespace cas
} // namespace widevine

View File

@@ -0,0 +1,117 @@
////////////////////////////////////////////////////////////////////////////////
// Copyright 2018 Google LLC.
//
// This software is licensed under the terms defined in the Widevine Master
// License Agreement. For a copy of this agreement, please contact
// widevine-licensing@google.com.
////////////////////////////////////////////////////////////////////////////////
//
// TS Packet is the basic unit of data in a transport stream, and a transport
// stream is merely a sequence of packets, without any global header. Each
// packet starts with a sync byte and a header, that may be followed with
// optional additional headers; the rest of the packet consists of payload.
// All header fields are read as big-endian. Packets are 188 bytes in length,
// but the communication medium may add additional information:
// Forward error correction is added by ISDB & DVB (16 bytes) and ATSC
// (20 bytes),[4] while the M2TS format prefixes packets with a 4-byte
// copyright and timestamp tag.
//
// TsPacket class is used to read data from the input stream or serialize data
// into the output stream. In contrast to other classes/interfaces in
// video/file, TsPacket does not use bidirectional BinaryIO interface, because
// BinaryIO is 10-15% slower than BitReader/BitWriter, and for entity like
// TsPacket this difference is tangible.
//
// TsPacket is a simple class. It does not know how to automatically update
// AdaptationField::length and AdaptationFieldExtension::length. In order
// to initialize TsPacket correctly, user has to set all its fields including
// length. Length calculation helpers are provided, though:
// * AdaptationFieldExtension::CalculateLength()
// * AdaptationField::CalculateLength()
// * TsPacket::PayloadSize()
#ifndef MEDIA_CAS_PACKAGER_SDK_INTERNAL_TS_PACKET_H_
#define MEDIA_CAS_PACKAGER_SDK_INTERNAL_TS_PACKET_H_
#include <memory>
#include <string>
#include <cstdint>
#include "base/macros.h"
#include "absl/strings/string_view.h"
#include "util/status.h"
#include "media_cas_packager_sdk/internal/mpeg2ts.h"
namespace widevine {
namespace cas {
// NOTE(user): This class does not handle "adaptation field" or
// "adaptation field extension" yet.
// NOTE(user): This class does not support reading a TS packet yet.
class TsPacket {
public:
enum AdaptationFieldControl {
kAFCReserved = 0x00,
kPayloadOnly = 0x01,
kAdaptationFieldOnly = 0x02,
kAdaptationFieldAndPayload = 0x03
};
TsPacket() = default;
TsPacket(const TsPacket&) = delete;
TsPacket& operator=(const TsPacket&) = delete;
virtual ~TsPacket() = default;
// Writes the packet into the provided output. Returns kOk on success.
virtual util::Status Write(std::string* output) const;
// Returns the size of payload data for the current TS packet configuration.
int32_t CalculatePayloadSize() const;
// Getters.
bool transport_error_indication() const {
return transport_error_indication_;
}
bool payload_unit_start_indicator() const {
return payload_unit_start_indicator_;
}
bool transport_priority() const { return transport_priority_; }
uint32_t pid() const { return pid_; }
uint32_t transport_scrambling_control() const {
return transport_scrambling_control_;
}
uint32_t adaptation_field_control() const { return adaptation_field_control_; }
uint32_t continuity_counter() const { return continuity_counter_; }
const std::string& payload() const { return payload_; }
// Setters.
void set_transport_error_indication(bool v) {
transport_error_indication_ = v;
}
void set_payload_unit_start_indicator(bool v) {
payload_unit_start_indicator_ = v;
}
void set_transport_priority(bool v) { transport_priority_ = v; }
void set_pid(uint32_t v) { pid_ = v; }
void set_transport_scrambling_control(uint32_t v) {
transport_scrambling_control_ = v;
}
void set_adaptation_field_control(uint32_t v) { adaptation_field_control_ = v; }
void set_continuity_counter(uint32_t v) { continuity_counter_ = v; }
void set_payload(const std::string& p) { payload_ = p; }
private:
bool transport_error_indication_ = false;
bool payload_unit_start_indicator_ = false;
bool transport_priority_ = false;
uint32_t pid_ = kInvalidPid;
uint32_t transport_scrambling_control_ = 0;
uint32_t adaptation_field_control_ = 0;
uint32_t continuity_counter_ = 0;
std::string payload_;
};
} // namespace cas
} // namespace widevine
#endif // MEDIA_CAS_PACKAGER_SDK_INTERNAL_TS_PACKET_H_

View File

@@ -0,0 +1,84 @@
////////////////////////////////////////////////////////////////////////////////
// Copyright 2018 Google LLC.
//
// This software is licensed under the terms defined in the Widevine Master
// License Agreement. For a copy of this agreement, please contact
// widevine-licensing@google.com.
////////////////////////////////////////////////////////////////////////////////
#include "media_cas_packager_sdk/internal/ts_packet.h"
#include <string>
#include "base/macros.h"
#include "testing/gmock.h"
#include "testing/gunit.h"
#include "media_cas_packager_sdk/internal/mpeg2ts.h"
namespace widevine {
namespace cas {
class TsPacketTest : public ::testing::Test {
public:
TsPacketTest() = default;
protected:
void AddStuffing(std::string* bytes, int payload_size) {
*bytes += std::string(kTsPacketSize - bytes->size() - payload_size, 0xFF);
}
void ExpectWriteMatches(TsPacket* packet, const std::string& expected) {
std::string bytes;
ASSERT_OK(packet->Write(&bytes));
EXPECT_EQ(expected, bytes);
}
private:
DISALLOW_COPY_AND_ASSIGN(TsPacketTest);
};
TEST_F(TsPacketTest, Packet1) {
TsPacket ts;
ts.set_transport_error_indication(true);
ts.set_payload_unit_start_indicator(false);
ts.set_transport_priority(true);
ts.set_pid(0x123);
ts.set_transport_scrambling_control(3);
ts.set_adaptation_field_control(TsPacket::kPayloadOnly);
ts.set_continuity_counter(0x7);
const std::string payload(ts.CalculatePayloadSize(), 0xFF);
ts.set_payload(payload);
std::string bytes = std::string("\x47\xA1\x23\xD7", 4) + payload;
ExpectWriteMatches(&ts, bytes);
EXPECT_EQ(kMaxTsPayloadSize, ts.CalculatePayloadSize());
}
TEST_F(TsPacketTest, Packet2) {
TsPacket ts;
ts.set_transport_error_indication(false);
ts.set_payload_unit_start_indicator(true);
ts.set_transport_priority(false);
ts.set_pid(0x345);
ts.set_transport_scrambling_control(1);
ts.set_adaptation_field_control(TsPacket::kPayloadOnly);
ts.set_continuity_counter(0x4);
const std::string payload(ts.CalculatePayloadSize(), 0xFF);
ts.set_payload(payload);
std::string bytes = std::string("\x47\x43\x45\x54", 4) + payload;
ExpectWriteMatches(&ts, bytes);
EXPECT_EQ(184, ts.CalculatePayloadSize());
}
TEST_F(TsPacketTest, BlankPacket184BytePayload) {
TsPacket ts;
ts.set_pid(0x0);
ts.set_adaptation_field_control(TsPacket::kPayloadOnly);
const std::string payload(ts.CalculatePayloadSize(), 0xFF);
ts.set_payload(payload);
std::string bytes = std::string("\x47\x00\x00\x10", 4) + payload;
ExpectWriteMatches(&ts, bytes);
EXPECT_EQ(184, ts.CalculatePayloadSize());
}
} // namespace cas
} // namespace widevine

View File

@@ -9,12 +9,23 @@
#include "media_cas_packager_sdk/internal/util.h"
#include <netinet/in.h>
#include <stddef.h>
#include <string.h>
#include "glog/logging.h"
#include "media_cas_packager_sdk/internal/ts_packet.h"
namespace widevine {
namespace cas {
namespace {
constexpr size_t kSectionHeaderSize = 4;
constexpr size_t kTsFillValue = 0xFF;
ContinuityCounter Increment(ContinuityCounter continuity_counter) {
return ++continuity_counter & 0xf;
}
} // namespace
void BigEndianToHost16(uint16_t* destination, const void* source) {
DCHECK(destination);
@@ -24,5 +35,56 @@ void BigEndianToHost16(uint16_t* destination, const void* source) {
*destination = ntohs(big_endian_number);
}
util::Status InsertEcmAsTsPacket(const std::string& ecm, ProgramId pid,
uint8_t table_id, ContinuityCounter* cc,
uint8_t* buffer, ssize_t* bytes_modified) {
DCHECK(cc);
DCHECK(buffer);
DCHECK(bytes_modified);
DCHECK_EQ(*bytes_modified % kTsPacketSize, 0);
// Create a TS payload of 184 bytes (Max TS size - ts header length).
CHECK_LE(ecm.size(), kMaxTsPayloadSize);
std::string payload(kMaxTsPayloadSize, kTsFillValue);
// Reference https://en.wikipedia.org/wiki/Program-specific_information
static constexpr uint8_t kPointerField = '\x00';
// Here are the 8 bits covers multiple fields from
// "section syntax indicator" to the first two bits of "section length".
// We always set the first two bits of "section length" to 0 because
// the data follows (i.e. the ECM) cannot be more than 180 bytes, so
// the remaining 8 bits in "section length" is sufficient to store its
// length.
static constexpr uint8_t kSyntaxtIndicatorToLength = '\x70';
uint8_t ecm_length = ecm.size();
// Section header.
memcpy(&payload.at(0), &kPointerField, 1);
memcpy(&payload.at(1), &table_id, 1);
memcpy(&payload.at(2), &kSyntaxtIndicatorToLength, 1);
memcpy(&payload.at(3), &ecm_length, 1);
// ECM follows the header.
memcpy(&payload.at(kSectionHeaderSize), ecm.data(), ecm.size());
// Wrap the data with a TS header.
TsPacket ecm_packet;
ecm_packet.set_payload_unit_start_indicator(true);
ecm_packet.set_pid(pid);
ecm_packet.set_payload(payload);
ecm_packet.set_adaptation_field_control(0x1);
ecm_packet.set_continuity_counter(*cc);
*cc = Increment(*cc);
// And write the packet.
std::string ecm_ts_packet;
util::Status status = ecm_packet.Write(&ecm_ts_packet);
if (!status.ok()) {
return status;
}
memcpy(buffer + *bytes_modified, ecm_ts_packet.data(), ecm_ts_packet.size());
*bytes_modified += ecm_ts_packet.size();
return util::OkStatus();
}
} // namespace cas
} // namespace widevine

View File

@@ -9,7 +9,12 @@
#ifndef MEDIA_CAS_PACKAGER_SDK_INTERNAL_UTIL_H_
#define MEDIA_CAS_PACKAGER_SDK_INTERNAL_UTIL_H_
#include <sys/types.h>
#include <string>
#include <cstdint>
#include "util/status.h"
#include "media_cas_packager_sdk/internal/mpeg2ts.h"
namespace widevine {
namespace cas {
@@ -19,6 +24,28 @@ namespace cas {
// the result in |destination|.
void BigEndianToHost16(uint16_t* destination, const void* source);
// Packages an ECM as a TS packet and inserts it into a buffer.
// Args:
// - |ecm| is the serialized ECM.
// - |pid| is the PID used for ECMs in the TS header.
// - |table_id| is the table ID byte put in the section header, it should be
// either 0x80 or 0x81. Changing table ID from 0x80 or 0x81 or
// 0x81 to 0x80 is used to singal to the client that the key contained
// in the ECM has changed. In other words, if you are building an ECM
// with a new key that was not in any previous ECM, you should flip the
// table ID so the client knows this is an important ECM it should process.
// - |cc| is the continuity counter to use in the header, which will also be
// incremented by this function.
// - |buffer| is the output buffer. Must be big enough to hold an additional
// 188 bytes.
// - |bytes_modified| the number of bytes which have already been modified in
// the |buffer| and is used as an offset.
// |bytes_modified| will be incremented by 188 if insertion of ECM into
// |buffer| is successful.
util::Status InsertEcmAsTsPacket(const std::string& ecm, ProgramId pid,
uint8_t table_id, ContinuityCounter* cc,
uint8_t* buffer, ssize_t* bytes_modified);
} // namespace cas
} // namespace widevine

View File

@@ -0,0 +1,77 @@
////////////////////////////////////////////////////////////////////////////////
// Copyright 2018 Google LLC.
//
// This software is licensed under the terms defined in the Widevine Master
// License Agreement. For a copy of this agreement, please contact
// widevine-licensing@google.com.
////////////////////////////////////////////////////////////////////////////////
#include "media_cas_packager_sdk/internal/util.h"
#include <string.h>
#include "testing/gmock.h"
#include "testing/gunit.h"
namespace {
// ECM payload data taken from a CETS encrypted file at Google Fiber
// default_key_id = 0000000000000002, random_iv = 0x30EB, 0xDAF4, 0xC66D, 0x57DC
constexpr char kEcmPayload[] = {'\x5F', '\x08', '\x30', '\x30', '\x30', '\x30',
'\x30', '\x30', '\x30', '\x30', '\x30', '\x30',
'\x30', '\x30', '\x30', '\x30', '\x30', '\x32',
'\x41', '\x70', '\x30', '\xEB', '\xDA', '\xF4',
'\xC6', '\x6D', '\x57', '\xDC'};
// ECM packet for Video PID (TSheader + payload) taken from a CETS encrypted
// file, payload_unit_start = 1, adaptation_field_control = 1, cc = 0
constexpr char kExpectedEcmPacket[] = {
// TS header.
'\x47', '\x5F', '\xFD', '\x10',
// Section header.
'\x00', '\x80', '\x70', '\x1C',
// ECM.
'\x5F', '\x08', '\x30', '\x30', '\x30', '\x30', '\x30', '\x30', '\x30',
'\x30', '\x30', '\x30', '\x30', '\x30', '\x30', '\x30', '\x30', '\x32',
'\x41', '\x70', '\x30', '\xEB', '\xDA', '\xF4', '\xC6', '\x6D', '\x57',
'\xDC',
// Padding.
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF'};
} // namespace
namespace widevine {
namespace cas {
TEST(InsertEcmAsTsPacketTest, BasicHappyPath) {
// declare variables used by InsertEcmAsTsPacket()
ContinuityCounter ecm_cc_ = 0;
ProgramId video_ecm_pid_ = 0x1FFD;
uint8_t buffer[188]; // output ts packet size
ssize_t output_bytes_modified = 0;
// convert kEcmPayload[] into a std::string to be accepted by the method
std::string ecm_data(kEcmPayload);
EXPECT_OK(InsertEcmAsTsPacket(ecm_data, video_ecm_pid_, kTsPacketTableId80,
&ecm_cc_, buffer, &output_bytes_modified));
EXPECT_EQ(0, memcmp(kExpectedEcmPacket, buffer, sizeof(buffer)));
EXPECT_EQ(1, ecm_cc_);
EXPECT_EQ(188, output_bytes_modified);
}
} // namespace cas
} // namespace widevine

View File

@@ -43,6 +43,7 @@ cc_library(
"//util:status",
"//media_cas_packager_sdk/internal:ecm",
"//media_cas_packager_sdk/internal:ecm_generator",
"//media_cas_packager_sdk/internal:key_fetcher",
"//protos/public:media_cas_encryption_proto",
"//protos/public:media_cas_proto",
],
@@ -93,15 +94,17 @@ cc_library(
deps = [
":wv_cas_types",
"//base",
"@abseil_repo//absl/base:core_headers",
"@abseil_repo//absl/memory",
"@abseil_repo//absl/strings",
"@abseil_repo//absl/base:core_headers", # buildcleaner: keep
"@abseil_repo//absl/memory", # buildcleaner: keep
"@abseil_repo//absl/strings", # buildcleaner: keep
"//util:status",
"//common:crypto_util",
"//example:constants",
"//media_cas_packager_sdk/internal:ecm",
"//media_cas_packager_sdk/internal:ecm_generator",
"//media_cas_packager_sdk/internal:fixed_key_fetcher",
"//media_cas_packager_sdk/internal:mpeg2ts",
"//media_cas_packager_sdk/internal:util",
],
)
@@ -114,6 +117,44 @@ cc_test(
":wv_cas_types",
"//testing:gunit_main",
"@abseil_repo//absl/strings",
"//media_cas_packager_sdk/internal:mpeg2ts",
],
)
cc_library(
name = "wv_cas_key_fetcher",
srcs = [
"wv_cas_key_fetcher.cc",
],
hdrs = [
"wv_cas_key_fetcher.h",
],
deps = [
"//base",
"@abseil_repo//absl/base:core_headers",
"@abseil_repo//absl/strings",
"//third_party/curl",
"//util:status",
"//common:signature_util",
"//media_cas_packager_sdk/internal:key_fetcher",
"//protos/public:media_cas_encryption_proto",
],
)
cc_test(
name = "wv_cas_key_fetcher_test",
size = "small",
srcs = [
"wv_cas_key_fetcher_test.cc",
],
deps = [
":wv_cas_key_fetcher",
"//base",
"//external:protobuf",
"//testing:gunit_main",
"@abseil_repo//absl/strings",
"//util:status",
"//protos/public:media_cas_encryption_proto",
],
)

View File

@@ -21,6 +21,8 @@
#include "media_cas_packager_sdk/internal/ecm.h"
#include "media_cas_packager_sdk/internal/ecm_generator.h"
#include "media_cas_packager_sdk/internal/fixed_key_fetcher.h"
#include "media_cas_packager_sdk/internal/mpeg2ts.h"
#include "media_cas_packager_sdk/internal/util.h"
#include "media_cas_packager_sdk/public/wv_cas_types.h"
namespace widevine {
@@ -312,5 +314,21 @@ WvCasStatus WvCasEcm::GenerateSingleKeyEcm(
return OK;
}
WvCasStatus WvCasEcm::GenerateTsPacket(const std::string& ecm, uint16_t pid,
uint8_t table_id,
uint8_t* continuity_counter,
uint8_t* packet) {
ssize_t bytes_modified = 0;
util::Status status = InsertEcmAsTsPacket(
ecm, pid, table_id, continuity_counter, packet, &bytes_modified);
if (!status.ok() || bytes_modified != kTsPacketSize) {
memset(packet, 0, kTsPacketSize);
LOG(ERROR) << "Failed to generate TS packet: " << status;
return INTERNAL;
}
return OK;
}
} // namespace cas
} // namespace widevine

View File

@@ -9,7 +9,7 @@
#ifndef MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_ECM_H_
#define MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_ECM_H_
#include <stddef.h>
#include <cstddef>
#include <list>
#include <map>
#include <string>
@@ -111,6 +111,29 @@ class WvCasEcm {
const char* const even_entitlement_key_id,
const char* const even_entitlement_key, std::string* ecm) const;
// Generate a TS packet with the given |ecm| as payload.
//
// Args (all pointer parameters must be not nullptr):
// - |ecm| serialized ECM, e.g., generated by GenerateEcm() method above
// - |pid| program ID for the ECM stream
// - |table_id| is the table ID byte put in the section header, it should be
// either 0x80 or 0x81. Changing table ID from 0x80 or 0x81 or
// 0x81 to 0x80 is used to singal to the client that the key contained
// in the ECM has changed. In other words, if you are building an ECM
// with a new key that was not in any previous ECM, you should flip the
// table ID so the client knows this is an important ECM it should process.
// - |continuity_counter| continuity_counter for the ECM packet,
// it will be incremented, only last 4 bits are used
// - |packet| a buffer of size at least 188 bytes to be used to return
// the generated TS packet
//
// Returns:
// - A status indicating whether there was any error during processing
virtual WvCasStatus GenerateTsPacket(const std::string& ecm, uint16_t pid,
uint8_t table_id,
uint8_t* continuity_counter,
uint8_t* packet);
private:
bool initialized_ = false;
int content_iv_size_;

View File

@@ -11,6 +11,7 @@
#include "testing/gunit.h"
#include "absl/strings/escaping.h"
#include "absl/strings/str_cat.h"
#include "media_cas_packager_sdk/internal/mpeg2ts.h"
#include "media_cas_packager_sdk/public/wv_cas_types.h"
using ::testing::Test;
@@ -18,6 +19,7 @@ using ::testing::Test;
namespace widevine {
namespace cas {
namespace {
const char kEvenKey[] = "even_key........"; // 16 bytes
const char kCsaEvenKey[] = "even_key"; // 8 bytes
const char kCsaEvenKeyWithNul[] = {'\x01', '\x00', '\x00', '\x00',
@@ -26,9 +28,9 @@ const char kEvenContentIv8Bytes[] = "even_iv."; // 8 bytes
const char kEvenContentIv16Bytes[] = "even_iv........."; // 16 bytes
const char kEvenEntitlementKeyId[] = "even_ent_key_id."; // 16 bytes
const char kEvenEntitlementKey[] =
"even_entitlement_key............"; // 32 bytes
const char kOddKey[] = "odd_key........."; // 16 bytes
const char kCsaOddKey[] = "odd_key."; // 8 bytes
"even_entitlement_key............"; // 32 bytes
const char kOddKey[] = "odd_key........."; // 16 bytes
const char kCsaOddKey[] = "odd_key."; // 8 bytes
const char kCsaOddKeyWithNul[] = {'\x00', '\x02', '\x00', '\x00',
'\x00', '\x00', '\x02', '\x00'};
const char kOddContentIv8Bytes[] = "odd_iv.."; // 8 bytes
@@ -37,6 +39,46 @@ const char kOddEntitlementKeyId[] = "odd_ent_key_id.."; // 16 bytes
const char kOddEntitlementKey[] =
"odd_entitlement_key............."; // 32 bytes
// ECM payload data taken from a CETS encrypted file at Google Fiber
// default_key_id = 0000000000000002, random_iv = 0x30EB, 0xDAF4, 0xC66D, 0x57DC
constexpr char kEcmPayload[] = {'\x5F', '\x08', '\x30', '\x30', '\x30', '\x30',
'\x30', '\x30', '\x30', '\x30', '\x30', '\x30',
'\x30', '\x30', '\x30', '\x30', '\x30', '\x32',
'\x41', '\x70', '\x30', '\xEB', '\xDA', '\xF4',
'\xC6', '\x6D', '\x57', '\xDC'};
// ECM packet for Video PID (TSheader + payload) taken from a CETS encrypted
// file, payload_unit_start = 1, adaptation_field_control = 1, cc = 0
constexpr char kExpectedEcmPacket[] = {
// TS header.
'\x47', '\x5F', '\xFD', '\x10',
// Section header.
'\x00', '\x80', '\x70', '\x1C',
// ECM.
'\x5F', '\x08', '\x30', '\x30', '\x30', '\x30', '\x30', '\x30', '\x30',
'\x30', '\x30', '\x30', '\x30', '\x30', '\x30', '\x30', '\x30', '\x32',
'\x41', '\x70', '\x30', '\xEB', '\xDA', '\xF4', '\xC6', '\x6D', '\x57',
'\xDC',
// Padding.
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF'};
} // namespace
class WvCasEcmTest : public Test {
protected:
WvCasEcmTest() {}
@@ -193,7 +235,7 @@ TEST_F(WvCasEcmTest, GenerateEcm_16BytesContentIv_Cbc_Success) {
/* odd_entitlement_key= */ kOddEntitlementKey, &actual_ecm));
EXPECT_EQ(
"4ad40105c06576656e5f656e745f6b65795f69642e3d1798a8729c0a316583bd514cf952"
"4ad40101c06576656e5f656e745f6b65795f69642e3d1798a8729c0a316583bd514cf952"
"a94350a82ce961f90b1008c9cdce343b2827827aeb1ba30292c0061d80cf50ce7f657665"
"6e5f69762e2e2e2e2e2e2e2e2e6f64645f656e745f6b65795f69642e2e34cd74b6b99888"
"9aad0e71b44bdd8c0e03e31ea68ab80a6ee79f59f0936bc6aa64fe976b6a4a5db2dc7e3e"
@@ -216,7 +258,7 @@ TEST_F(WvCasEcmTest, GenerateSingleKeyEcm_16BytesContentIv_Cbc_Success) {
/* even_entitlement_key= */ kEvenEntitlementKey, &actual_ecm));
EXPECT_EQ(
"4ad40104c06576656e5f656e745f6b65795f69642e3d1798a8729c0a316583bd514cf952"
"4ad40100c06576656e5f656e745f6b65795f69642e3d1798a8729c0a316583bd514cf952"
"a94350a82ce961f90b1008c9cdce343b2827827aeb1ba30292c0061d80cf50ce7f657665"
"6e5f69762e2e2e2e2e2e2e2e2e",
absl::BytesToHexString(actual_ecm));
@@ -242,7 +284,7 @@ TEST_F(WvCasEcmTest, GenerateEcm_8BytesContentIv_Csa_Success) {
/* odd_entitlement_key= */ kOddEntitlementKey, &actual_ecm));
EXPECT_EQ(
"4ad40107806576656e5f656e745f6b65795f69642ee9c009a9d6a07c7bc3ca82f39c1f10"
"4ad40105806576656e5f656e745f6b65795f69642ee9c009a9d6a07c7bc3ca82f39c1f10"
"e6b5f391e74f120cfb876efba02d7f506f0ef185b3398096111dafd86ff7d395a2657665"
"6e5f69762e6f64645f656e745f6b65795f69642e2e7f655fe61e99e89e03ac23df98cc02"
"1cf21dfe9637c72c3480727ab18332d4ee219e81b8f34c9df2704b0595501832736f6464"
@@ -265,7 +307,7 @@ TEST_F(WvCasEcmTest, GenerateSingleKeyEcm_8BytesContentIv_Csa_Success) {
/* even_entitlement_key= */ kEvenEntitlementKey, &actual_ecm));
EXPECT_EQ(
"4ad40106806576656e5f656e745f6b65795f69642ee9c009a9d6a07c7bc3ca82f39c1f10"
"4ad40104806576656e5f656e745f6b65795f69642ee9c009a9d6a07c7bc3ca82f39c1f10"
"e6b5f391e74f120cfb876efba02d7f506f0ef185b3398096111dafd86ff7d395a2657665"
"6e5f69762e",
absl::BytesToHexString(actual_ecm));
@@ -291,7 +333,7 @@ TEST_F(WvCasEcmTest, GenerateEcm_8BytesContentIv_Csa_NulCharInKey_Success) {
/* odd_entitlement_key= */ kOddEntitlementKey, &actual_ecm));
EXPECT_EQ(
"4ad40107806576656e5f656e745f6b65795f69642e71ffb7d9500261db7a92974405c2cf"
"4ad40105806576656e5f656e745f6b65795f69642e71ffb7d9500261db7a92974405c2cf"
"d0b085eb9a85a57dbdb799158e829996988524bf3b3cfe01b28d4474f85ec2991d657665"
"6e5f69762e6f64645f656e745f6b65795f69642e2e874aab870ffba640875a4521d3cd57"
"02f26d0f9c7e9c69d7059c9ad42b091ec1f151aaa190536f4f330edebe84fe5a786f6464"
@@ -315,11 +357,36 @@ TEST_F(WvCasEcmTest,
/* even_entitlement_key= */ kEvenEntitlementKey, &actual_ecm));
EXPECT_EQ(
"4ad40106806576656e5f656e745f6b65795f69642e71ffb7d9500261db7a92974405c2cf"
"4ad40104806576656e5f656e745f6b65795f69642e71ffb7d9500261db7a92974405c2cf"
"d0b085eb9a85a57dbdb799158e829996988524bf3b3cfe01b28d4474f85ec2991d657665"
"6e5f69762e",
absl::BytesToHexString(actual_ecm));
}
TEST_F(WvCasEcmTest, GenerateTsPacket_TableId80) {
std::string ecm(kEcmPayload);
ProgramId pid = 0x1FFD;
ContinuityCounter cc = 0;
uint8_t packet[188];
EXPECT_EQ(OK, wv_cas_ecm_.GenerateTsPacket(ecm, pid, kTsPacketTableId80, &cc,
packet));
EXPECT_EQ(0, memcmp(kExpectedEcmPacket, packet, sizeof(packet)));
EXPECT_EQ(1, cc);
}
TEST_F(WvCasEcmTest, GenerateTsPacket_TableId81) {
std::string ecm(kEcmPayload);
ProgramId pid = 0x1FFD;
ContinuityCounter cc = 0;
uint8_t packet[188];
EXPECT_EQ(OK, wv_cas_ecm_.GenerateTsPacket(ecm, pid, kTsPacketTableId81, &cc,
packet));
char expected_ecm[188];
memcpy(expected_ecm, kExpectedEcmPacket, 188);
expected_ecm[5] = '\x81';
EXPECT_EQ(0, memcmp(expected_ecm, packet, sizeof(packet)));
EXPECT_EQ(1, cc);
}
} // namespace cas
} // namespace widevine

View File

@@ -0,0 +1,160 @@
////////////////////////////////////////////////////////////////////////////////
// Copyright 2018 Google LLC.
//
// This software is licensed under the terms defined in the Widevine Master
// License Agreement. For a copy of this agreement, please contact
// widevine-licensing@google.com.
////////////////////////////////////////////////////////////////////////////////
#include "media_cas_packager_sdk/public/wv_cas_key_fetcher.h"
#include <stddef.h>
#include <string.h>
#include <string>
#include "gflags/gflags.h"
#include "glog/logging.h"
#include "net/proto2/util/public/json_util.h"
#include "absl/strings/escaping.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "curl/curl.h"
#include "curl/easy.h"
#include "util/status.h"
#include "common/signature_util.h"
#include "protos/public/media_cas_encryption.pb.h"
using google::protobuf::util::JsonPrintOptions;
using google::protobuf::util::JsonStringToMessage;
using google::protobuf::util::MessageToJsonString;
DEFINE_string(
license_server, "",
"HTTP URL to the license server for making CAS encryption request");
DEFINE_string(signing_provider, "",
"Name of the provider signing the CAS encryption request");
DEFINE_string(signing_key, "",
"AES key (in hex) for signing the CAS encryption request");
DEFINE_string(signing_iv, "",
"AES iv (in hex) for signing the CAS encryption request");
namespace widevine {
namespace cas {
util::Status WvCasKeyFetcher::RequestEntitlementKey(
const std::string& request_string, std::string* signed_response_string) {
if (FLAGS_signing_provider.empty() || FLAGS_signing_key.empty() ||
FLAGS_signing_iv.empty()) {
return util::Status(
util::error::INVALID_ARGUMENT,
"Flag 'signing_provider', 'signing_key' or 'signing_iv' is empty");
}
// Processes request.
CasEncryptionRequest request;
request.ParseFromString(request_string);
std::string request_json;
JsonPrintOptions print_options;
// Set this option so that the json output is
// {"content_id":"MjExNDA4NDQ=", ...
// instead of
// {"contentId":"MjExNDA4NDQ=", ...
print_options.preserve_proto_field_names = true;
// NOTE: MessageToJsonString will automatically converts 'bytes' type fields
// to base64. For example content ID '21140844' becomes 'MjExNDA4NDQ='.
if (!MessageToJsonString(request, &request_json, print_options).ok()) {
return util::Status(util::error::INTERNAL,
"Failed to convert request message to json.");
}
LOG(INFO) << "Json CasEncryptionRequest: " << request_json;
// Creates signed request.
SignedCasEncryptionRequest signed_request;
signed_request.set_request(request_json);
std::string signature;
if (!signature_util::GenerateAesSignature(
request_json, absl::HexStringToBytes(FLAGS_signing_key),
absl::HexStringToBytes(FLAGS_signing_iv), &signature)
.ok()) {
return util::Status(util::error::INTERNAL, "Failed to sign the request.");
}
signed_request.set_signature(signature);
signed_request.set_signer(FLAGS_signing_provider);
std::string signed_request_json;
// NOTE: MessageToJsonString will automatically converts the 'request' and
// 'signature' fields in SignedCasEncryptionRequest to base64, because they
// are of type 'bytes'.
if (!MessageToJsonString(signed_request, &signed_request_json).ok()) {
return util::Status(util::error::INTERNAL,
"Failed to convert signed request message to json.");
}
LOG(INFO) << "Json SignedCasEncryptionRequest: " << signed_request_json;
// Makes HTTP request against License Server.
std::string http_response_json;
util::Status status =
MakeHttpRequest(signed_request_json, &http_response_json);
if (!status.ok()) {
return status;
}
LOG(INFO) << "Json HTTP response: " << http_response_json;
HttpResponse http_response;
if (!JsonStringToMessage(http_response_json, &http_response).ok()) {
return util::Status(util::error::INTERNAL,
"Failed to convert http response json to message.");
}
// Processes signed response.
// TODO(b/114741232): Seems we are getting CasEncryptionResponse back instead
// of SignedCasEncryptionResponse.
LOG(INFO) << "Json CasEncryptionResponse: " << http_response.response();
CasEncryptionResponse response;
if (!JsonStringToMessage(http_response.response(), &response).ok()) {
return util::Status(util::error::INTERNAL,
"Failed to convert response json to message.");
}
SignedCasEncryptionResponse signed_response;
signed_response.set_response(response.SerializeAsString());
signed_response.SerializeToString(signed_response_string);
return util::OkStatus();
}
size_t AppendToString(void* ptr, size_t size, size_t count, std::string* output) {
const absl::string_view data(static_cast<char*>(ptr), size * count);
absl::StrAppend(output, data);
return data.size();
}
util::Status WvCasKeyFetcher::MakeHttpRequest(
const std::string& signed_request_json, std::string* http_response_json) const {
CHECK(http_response_json);
if (FLAGS_license_server.empty()) {
return util::Status(util::error::INVALID_ARGUMENT,
"Flag 'license_server' is empty");
}
CURL* curl;
CURLcode curl_code;
curl = curl_easy_init();
if (curl) {
curl_easy_setopt(curl, CURLOPT_URL, FLAGS_license_server.c_str());
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, signed_request_json.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEDATA, http_response_json);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &AppendToString);
// If we don't provide POSTFIELDSIZE, libcurl will strlen() by itself.
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE,
(int64_t)strlen(signed_request_json.c_str()));
curl_code = curl_easy_perform(curl);
if (curl_code != CURLE_OK) {
return util::Status(util::error::INTERNAL,
"curl_easy_perform() failed: " +
std::string(curl_easy_strerror(curl_code)));
}
curl_easy_cleanup(curl);
} else {
return util::Status(util::error::INTERNAL, "curl_easy_init() failed");
}
return util::OkStatus();
}
} // namespace cas
} // namespace widevine

View File

@@ -0,0 +1,58 @@
////////////////////////////////////////////////////////////////////////////////
// Copyright 2018 Google LLC.
//
// This software is licensed under the terms defined in the Widevine Master
// License Agreement. For a copy of this agreement, please contact
// widevine-licensing@google.com.
////////////////////////////////////////////////////////////////////////////////
#ifndef MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_KEY_FETCHER_H_
#define MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_KEY_FETCHER_H_
#include <string>
#include "base/commandlineflags_declare.h"
#include "util/status.h"
#include "media_cas_packager_sdk/internal/key_fetcher.h"
DECLARE_string(license_server);
DECLARE_string(signing_provider);
DECLARE_string(signing_key);
DECLARE_string(signing_iv);
namespace widevine {
namespace cas {
// WV CAS KeyFetcher. Performs the communication with the Widevine License
// Server to get entitlement keys on behalf of a WvCasEcm object.
class WvCasKeyFetcher : public KeyFetcher {
public:
WvCasKeyFetcher() = default;
WvCasKeyFetcher(const WvCasKeyFetcher&) = delete;
WvCasKeyFetcher& operator=(const WvCasKeyFetcher&) = delete;
virtual ~WvCasKeyFetcher() = default;
// Get entitlement keys from the license server. Send a
// SignedCasEncryptionRequest message to the license server, receive a
// SignedCasEncryptionResponse and return it to the caller.
// Args:
// |request_string| a serialized CasEncryptionRequest message, produced
// by WvCasEcm::Initialize().
// |signed_response_string| a serialized SignedCasEncryptionResponse
// message. It should be passed into
// widevine::cas::Ecm::ProcessCasEncryptionResponse().
virtual util::Status RequestEntitlementKey(const std::string& request_string,
std::string* signed_response_string);
protected:
// Makes a HTTP request to License Server for entitlement key(s).
// Returns the HTTP response in Json format in |http_response_json|.
// Protected visibility to support unit testing.
virtual util::Status MakeHttpRequest(const std::string& signed_request_json,
std::string* http_response_json) const;
};
} // namespace cas
} // namespace widevine
#endif // MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_KEY_FETCHER_H_

View File

@@ -0,0 +1,109 @@
////////////////////////////////////////////////////////////////////////////////
// Copyright 2018 Google LLC.
//
// This software is licensed under the terms defined in the Widevine Master
// License Agreement. For a copy of this agreement, please contact
// widevine-licensing@google.com.
////////////////////////////////////////////////////////////////////////////////
#include "media_cas_packager_sdk/public/wv_cas_key_fetcher.h"
#include "base/commandlineflags_declare.h"
#include "glog/logging.h"
#include "google/protobuf/text_format.h"
#include "testing/gmock.h"
#include "testing/gunit.h"
#include "absl/strings/escaping.h"
#include "util/status.h"
#include "protos/public/media_cas_encryption.pb.h"
using testing::_;
using testing::DoAll;
using testing::Return;
using testing::SetArgumentPointee;
namespace {
const char kSigningProvider[] = "widevine_test";
const char kSingingKey[] =
"1ae8ccd0e7985cc0b6203a55855a1034afc252980e970ca90e5202689f947ab9";
const char kSingingIv[] = "d58ce954203b7c9a9a9d467f59839249";
const char kCasEncryptionRequest[] =
"content_id: \"21140844\" "
"provider: \"widevine\" "
"track_types: \"SD\" "
"key_rotation: false";
const char kSignedCasEncryptionRequest[] =
"{\"request\":"
"\"eyJjb250ZW50X2lkIjoiTWpFeE5EQTRORFE9IiwicHJvdmlkZXIiOiJ3aWRldmluZSIsIn"
"RyYWNrX3R5cGVzIjpbIlNEIl0sImtleV9yb3RhdGlvbiI6ZmFsc2V9\",\"signature\":"
"\"JyTnKEy1w98HRP1lL78+OIEiqtIXyfCN8iudvNXIWhw=\",\"signer\":\"widevine_"
"test\"}";
const char kHttpResponse[] =
"{\"response\":"
"\"eyJzdGF0dXMiOiJPSyIsImNvbnRlbnRfaWQiOiJNakV4TkRBNE5EUT0iLCJlbnRpdGxlbWVu"
"dF9rZXlzIjpbeyJrZXlfaWQiOiJNUGFndXhNb1hNNkUxUzhEOUF3RkNBPT0iLCJrZXkiOiJoZ1"
"JycmdqeUg4NjQycjY3VHd0OHJ1cU5MUGNMRmtKcWRVSUROdm5GZDBNPSIsInRyYWNrX3R5cGUi"
"OiJTRCIsImtleV9zbG90IjoiU0lOR0xFIn1dfQ==\"}";
} // namespace
namespace widevine {
namespace cas {
class MockWvCasKeyFetcher : public WvCasKeyFetcher {
public:
MockWvCasKeyFetcher() : WvCasKeyFetcher() {}
~MockWvCasKeyFetcher() override {}
MOCK_CONST_METHOD2(MakeHttpRequest,
util::Status(const std::string& signed_request_json,
std::string* http_response_json));
};
class WvCasKeyFetcherTest : public ::testing::Test {
public:
WvCasKeyFetcherTest() {}
void SetUp() override {
FLAGS_signing_provider = kSigningProvider;
FLAGS_signing_key = kSingingKey;
FLAGS_signing_iv = kSingingIv;
CHECK(
google::protobuf::TextFormat::ParseFromString(kCasEncryptionRequest, &request_));
response_.set_status(CasEncryptionResponse::OK);
response_.set_content_id("21140844");
CasEncryptionResponse::KeyInfo* entitlement_keys =
response_.add_entitlement_keys();
CHECK(absl::Base64Unescape("MPaguxMoXM6E1S8D9AwFCA==",
entitlement_keys->mutable_key_id()));
CHECK(absl::Base64Unescape("hgRrrgjyH8642r67Twt8ruqNLPcLFkJqdUIDNvnFd0M=",
entitlement_keys->mutable_key()));
entitlement_keys->set_track_type("SD");
entitlement_keys->set_key_slot(CasEncryptionResponse::KeyInfo::SINGLE);
}
protected:
MockWvCasKeyFetcher mock_key_fetcher_;
CasEncryptionRequest request_;
CasEncryptionResponse response_;
};
TEST_F(WvCasKeyFetcherTest, TestRequestEntitlementKey) {
EXPECT_CALL(mock_key_fetcher_,
MakeHttpRequest(kSignedCasEncryptionRequest, _))
.WillOnce(DoAll(SetArgumentPointee<1>(std::string(kHttpResponse)),
Return(util::OkStatus())));
std::string actual_signed_response;
EXPECT_OK(mock_key_fetcher_.RequestEntitlementKey(
request_.SerializeAsString(), &actual_signed_response));
SignedCasEncryptionResponse expected_signed_response;
expected_signed_response.set_response(response_.SerializeAsString());
EXPECT_EQ(expected_signed_response.SerializeAsString(),
actual_signed_response);
}
} // namespace cas
} // namespace widevine

View File

@@ -8,6 +8,7 @@
#include "media_cas_packager_sdk/public/wv_cas_types.h"
#include "glog/logging.h"
#include "base/macros.h"
namespace widevine {
@@ -37,34 +38,38 @@ std::string GetWvCasStatusMessage(WvCasStatus status) {
return kWvCasStatusMessage[status];
}
std::string CryptoModeToString(CryptoMode mode) {
switch (mode) {
case CryptoMode::kAesCtr: {
return "kAesCtr";
}
case CryptoMode::kAesCbc: {
return "kAesCbc";
}
case CryptoMode::kDvbCsa2: {
return "kDvbCsa2";
}
default: {
return "kCryptoModeUnspecified";
}
// Numeric value of crypto mode is the index into strings array.
static const char* kCrypoModeStrings[] = {
"AesCbc",
"AesCtr",
"DvbCsa2",
};
bool CryptoModeToString(CryptoMode mode, std::string* str) {
if (str == nullptr) {
return false;
}
int mode_idx = static_cast<int>(mode);
if (mode_idx >= 0 && mode_idx < arraysize(kCrypoModeStrings)) {
*str = kCrypoModeStrings[mode_idx];
return true;
}
LOG(ERROR) << "Invalid crypto mode: " << mode_idx;
return false;
}
CryptoMode StringToCryptoMode(std::string str) {
if (str == "kAesCtr") {
return CryptoMode::kAesCtr;
bool StringToCryptoMode(const std::string& str, CryptoMode* mode) {
if (mode == nullptr) {
return false;
}
if (str == "kAesCbc") {
return CryptoMode::kAesCbc;
for (int i = 0; i < arraysize(kCrypoModeStrings); ++i) {
if (str.compare(kCrypoModeStrings[i]) == 0) {
*mode = static_cast<CryptoMode>(i);
return true;
}
}
if (str == "kDvbCsa2") {
return CryptoMode::kDvbCsa2;
}
return CryptoMode::kCryptoModeUnspecified;
LOG(ERROR) << "Invalid crypto mode: " << str;
return false;
}
} // namespace cas

View File

@@ -50,17 +50,19 @@ enum WvCasStatus {
// Returns the message std::string for the given WvCasStatus.
std::string GetWvCasStatusMessage(WvCasStatus status);
// Crypto mode for encryption / decryption.
// Crypto mode for encryption / decryption. ENUM value should be consistent with
// https://docs.google.com/document/d/1A5vflf8tbKyUheV-xsvfxFqB6YyNLNdsGXYx8ZnhjfY/edit#heading=h.ej4ts3lifoio
enum class CryptoMode : int {
kCryptoModeUnspecified = 0,
kAesCbc = 0,
kAesCtr = 1,
kAesCbc = 2,
kDvbCsa2 = 3
kDvbCsa2 = 2,
};
std::string CryptoModeToString(CryptoMode mode);
// Returns false if mode is not a valid CryptoMode.
bool CryptoModeToString(CryptoMode mode, std::string* str);
CryptoMode StringToCryptoMode(std::string str);
// Returns false if str is not a valid CryptoMode.
bool StringToCryptoMode(const std::string& str, CryptoMode* mode);
} // namespace cas
} // namespace widevine

View File

@@ -25,19 +25,25 @@ TEST(WvCasTypesTest, GetWvCasStatusMessage) {
}
TEST(WvCasTypesTest, CryptoModeToString) {
EXPECT_EQ("kAesCtr", CryptoModeToString(CryptoMode::kAesCtr));
EXPECT_EQ("kAesCbc", CryptoModeToString(CryptoMode::kAesCbc));
EXPECT_EQ("kDvbCsa2", CryptoModeToString(CryptoMode::kDvbCsa2));
EXPECT_EQ("kCryptoModeUnspecified",
CryptoModeToString(CryptoMode::kCryptoModeUnspecified));
std::string crypto_mode;
ASSERT_TRUE(CryptoModeToString(CryptoMode::kAesCtr, &crypto_mode));
EXPECT_EQ("AesCtr", crypto_mode);
ASSERT_TRUE(CryptoModeToString(CryptoMode::kAesCbc, &crypto_mode));
EXPECT_EQ("AesCbc", crypto_mode);
ASSERT_TRUE(CryptoModeToString(CryptoMode::kDvbCsa2, &crypto_mode));
EXPECT_EQ("DvbCsa2", crypto_mode);
EXPECT_FALSE(CryptoModeToString(static_cast<CryptoMode>(-1), &crypto_mode));
}
TEST(WvCasTypesTest, StringToCryptoMode) {
EXPECT_EQ(CryptoMode::kAesCtr, StringToCryptoMode("kAesCtr"));
EXPECT_EQ(CryptoMode::kAesCbc, StringToCryptoMode("kAesCbc"));
EXPECT_EQ(CryptoMode::kDvbCsa2, StringToCryptoMode("kDvbCsa2"));
EXPECT_EQ(CryptoMode::kCryptoModeUnspecified,
StringToCryptoMode("wrong crypto mode"));
CryptoMode crypto_mode;
ASSERT_TRUE(StringToCryptoMode("AesCtr", &crypto_mode));
EXPECT_EQ(CryptoMode::kAesCtr, crypto_mode);
ASSERT_TRUE(StringToCryptoMode("AesCbc", &crypto_mode));
EXPECT_EQ(CryptoMode::kAesCbc, crypto_mode);
ASSERT_TRUE(StringToCryptoMode("DvbCsa2", &crypto_mode));
EXPECT_EQ(CryptoMode::kDvbCsa2, crypto_mode);
EXPECT_FALSE(StringToCryptoMode("invalid crypto mode", &crypto_mode));
}
} // namespace cas