Refactor KeySource::FetchKeys to use EME init data types

Change-Id: I517ea992a8868d7382988a8837f4f05df9f974d7
This commit is contained in:
Kongqun Yang 2017-04-04 12:43:41 -07:00 committed by KongQun Yang
parent 9d101f85e3
commit fb0790e1d5
14 changed files with 151 additions and 158 deletions

View File

@ -15,18 +15,8 @@ namespace media {
FixedKeySource::~FixedKeySource() {}
Status FixedKeySource::FetchKeys(const std::vector<uint8_t>& pssh_box) {
// Do nothing for fixed key encryption/decryption.
return Status::OK;
}
Status FixedKeySource::FetchKeys(
const std::vector<std::vector<uint8_t>>& key_ids) {
// Do nothing for fixed key encryption/decryption.
return Status::OK;
}
Status FixedKeySource::FetchKeys(uint32_t asset_id) {
Status FixedKeySource::FetchKeys(EmeInitDataType init_data_type,
const std::vector<uint8_t>& init_data) {
// Do nothing for fixed key encryption/decryption.
return Status::OK;
}

View File

@ -30,10 +30,8 @@ class FixedKeySource : public KeySource {
/// @name KeySource implementation overrides.
/// @{
Status FetchKeys(const std::vector<uint8_t>& pssh_box) override;
Status FetchKeys(const std::vector<std::vector<uint8_t>>& key_ids) override;
Status FetchKeys(uint32_t asset_id) override;
Status FetchKeys(EmeInitDataType init_data_type,
const std::vector<uint8_t>& init_data) override;
Status GetKey(TrackType track_type, EncryptionKey* key) override;
Status GetKey(const std::vector<uint8_t>& key_id,
EncryptionKey* key) override;

View File

@ -16,6 +16,21 @@
namespace shaka {
namespace media {
/// Encrypted media init data types. It is extended from:
/// https://www.w3.org/TR/eme-initdata-registry/#registry.
enum class EmeInitDataType {
UNKNOWN,
/// One or multiple PSSH boxes.
CENC,
/// WebM init data is basically KeyId.
WEBM,
/// JSON formatted key ids.
KEYIDS,
/// Widevine classic asset id.
WIDEVINE_CLASSIC,
MAX = WIDEVINE_CLASSIC
};
struct EncryptionKey {
EncryptionKey();
~EncryptionKey();
@ -43,22 +58,12 @@ class KeySource {
KeySource();
virtual ~KeySource();
/// Fetch keys for CENC from the key server.
/// @param pssh_box The entire PSSH box for the content to be decrypted
/// Fetch keys based on the specified encrypted media init data.
/// @param init_data_type specifies the encrypted media init data type.
/// @param init_data contains the init data.
/// @return OK on success, an error status otherwise.
virtual Status FetchKeys(const std::vector<uint8_t>& pssh_box) = 0;
/// Fetch keys for CENC from the key server.
/// @param key_ids the key IDs for the keys to fetch from the server.
/// @return OK on success, an error status otherwise.
virtual Status FetchKeys(
const std::vector<std::vector<uint8_t>>& key_ids) = 0;
/// Fetch keys for WVM decryption from the key server.
/// @param asset_id is the Widevine Classic asset ID for the content to be
/// decrypted.
/// @return OK on success, an error status otherwise.
virtual Status FetchKeys(uint32_t asset_id) = 0;
virtual Status FetchKeys(EmeInitDataType init_data_type,
const std::vector<uint8_t>& init_data) = 0;
/// Get encryption key of the specified track type.
/// @param track_type is the type of track for which retrieving the key.

View File

@ -310,19 +310,9 @@ Status PlayReadyKeySource::FetchKeysWithProgramIdentifier(
return Status::OK;
}
Status PlayReadyKeySource::FetchKeys(const std::vector<uint8_t>& pssh_box) {
// Does nothing for playready encryption/decryption.
return Status::OK;
}
Status PlayReadyKeySource::FetchKeys(
const std::vector<std::vector<uint8_t>>& key_ids) {
// Does nothing for playready encryption/decryption.
return Status::OK;
}
Status PlayReadyKeySource::FetchKeys(uint32_t asset_id) {
// Does nothing for playready encryption/decryption.
Status PlayReadyKeySource::FetchKeys(EmeInitDataType init_data_type,
const std::vector<uint8_t>& init_data) {
// Do nothing for playready encryption/decryption.
return Status::OK;
}

View File

@ -40,10 +40,8 @@ class PlayReadyKeySource : public KeySource {
/// @name KeySource implementation overrides.
/// @{
Status FetchKeys(const std::vector<uint8_t>& pssh_box) override;
Status FetchKeys(const std::vector<std::vector<uint8_t>>& key_ids) override;
Status FetchKeys(uint32_t asset_id) override;
Status FetchKeys(EmeInitDataType init_data_type,
const std::vector<uint8_t>& init_data) override;
Status GetKey(TrackType track_type, EncryptionKey* key) override;
Status GetKey(const std::vector<uint8_t>& key_id,
EncryptionKey* key) override;

View File

@ -14,6 +14,7 @@
#include "packager/base/json/json_writer.h"
#include "packager/media/base/fixed_key_source.h"
#include "packager/media/base/http_key_fetcher.h"
#include "packager/media/base/network_util.h"
#include "packager/media/base/producer_consumer_queue.h"
#include "packager/media/base/protection_system_specific_info.h"
#include "packager/media/base/rcheck.h"
@ -41,6 +42,18 @@ const int kDefaultCryptoPeriodCount = 10;
const int kGetKeyTimeoutInSeconds = 5 * 60; // 5 minutes.
const int kKeyFetchTimeoutInSeconds = 60; // 1 minute.
std::vector<uint8_t> StringToBytes(const std::string& string) {
return std::vector<uint8_t>(string.begin(), string.end());
}
std::vector<uint8_t> WidevinePsshFromKeyId(
const std::vector<std::vector<uint8_t>>& key_ids) {
media::WidevinePsshData widevine_pssh_data;
for (const std::vector<uint8_t>& key_id : key_ids)
widevine_pssh_data.add_key_id(key_id.data(), key_id.size());
return StringToBytes(widevine_pssh_data.SerializeAsString());
}
bool Base64StringToBytes(const std::string& base64_string,
std::vector<uint8_t>* bytes) {
DCHECK(bytes);
@ -147,61 +160,63 @@ Status WidevineKeySource::FetchKeys(const std::vector<uint8_t>& content_id,
return FetchKeysInternal(!kEnableKeyRotation, 0, false);
}
Status WidevineKeySource::FetchKeys(const std::vector<uint8_t>& pssh_box) {
Status WidevineKeySource::FetchKeys(EmeInitDataType init_data_type,
const std::vector<uint8_t>& init_data) {
std::vector<uint8_t> pssh_data;
uint32_t asset_id = 0;
switch (init_data_type) {
case EmeInitDataType::CENC: {
const std::vector<uint8_t> widevine_system_id(
kWidevineSystemId, kWidevineSystemId + arraysize(kWidevineSystemId));
ProtectionSystemSpecificInfo info;
if (!info.Parse(pssh_box.data(), pssh_box.size()))
return Status(error::PARSER_FAILURE, "Error parsing the PSSH box.");
std::vector<ProtectionSystemSpecificInfo> protection_systems_info;
if (!ProtectionSystemSpecificInfo::ParseBoxes(
init_data.data(), init_data.size(), &protection_systems_info)) {
return Status(error::PARSER_FAILURE, "Error parsing the PSSH boxes.");
}
for (const auto& info: protection_systems_info) {
// Use Widevine PSSH if available otherwise construct a Widevine PSSH
// from the first available key ids.
if (info.system_id() == widevine_system_id) {
pssh_data = info.pssh_data();
break;
} else if (pssh_data.empty() && !info.key_ids().empty()) {
pssh_data = WidevinePsshFromKeyId(info.key_ids());
// Continue to see if there is any Widevine PSSH. The KeyId generated
// PSSH is only used if a Widevine PSSH could not be found.
continue;
}
}
if (pssh_data.empty())
return Status(error::INVALID_ARGUMENT, "No supported PSSHs found.");
break;
}
case EmeInitDataType::WEBM:
pssh_data = WidevinePsshFromKeyId({init_data});
break;
case EmeInitDataType::WIDEVINE_CLASSIC:
if (init_data.size() < sizeof(asset_id))
return Status(error::INVALID_ARGUMENT, "Invalid asset id.");
asset_id = ntohlFromBuffer(init_data.data());
break;
default:
LOG(ERROR) << "Init data type " << static_cast<int>(init_data_type)
<< " not supported.";
return Status(error::INVALID_ARGUMENT, "Unsupported init data type.");
}
const bool widevine_classic =
init_data_type == EmeInitDataType::WIDEVINE_CLASSIC;
base::AutoLock scoped_lock(lock_);
request_dict_.Clear();
std::string pssh_data_base64_string;
BytesToBase64String(info.pssh_data(), &pssh_data_base64_string);
request_dict_.SetString("pssh_data", pssh_data_base64_string);
return FetchKeysInternal(!kEnableKeyRotation, 0, false);
} else if (!info.key_ids().empty()) {
// This is not a Widevine PSSH box. Try making the request for the key-IDs.
// Even if this is a different key-system, it should still work. Either
// the server will not recognize it and return an error, or it will
// recognize it and the key must be correct (or the content is bad).
return FetchKeys(info.key_ids());
} else {
return Status(error::NOT_FOUND, "No key IDs given in PSSH box.");
}
}
Status WidevineKeySource::FetchKeys(
const std::vector<std::vector<uint8_t>>& key_ids) {
base::AutoLock scoped_lock(lock_);
request_dict_.Clear();
std::string pssh_data_base64_string;
// Generate Widevine PSSH data from the key-IDs.
WidevinePsshData widevine_pssh_data;
for (size_t i = 0; i < key_ids.size(); i++) {
widevine_pssh_data.add_key_id(key_ids[i].data(), key_ids[i].size());
}
const std::string serialized_string = widevine_pssh_data.SerializeAsString();
std::vector<uint8_t> pssh_data(serialized_string.begin(),
serialized_string.end());
BytesToBase64String(pssh_data, &pssh_data_base64_string);
request_dict_.SetString("pssh_data", pssh_data_base64_string);
return FetchKeysInternal(!kEnableKeyRotation, 0, false);
}
Status WidevineKeySource::FetchKeys(uint32_t asset_id) {
base::AutoLock scoped_lock(lock_);
request_dict_.Clear();
if (widevine_classic) {
// Javascript/JSON does not support int64_t or unsigned numbers. Use double
// instead as 32-bit integer can be lossless represented using double.
request_dict_.SetDouble("asset_id", asset_id);
return FetchKeysInternal(!kEnableKeyRotation, 0, true);
} else {
std::string pssh_data_base64_string;
BytesToBase64String(pssh_data, &pssh_data_base64_string);
request_dict_.SetString("pssh_data", pssh_data_base64_string);
}
return FetchKeysInternal(!kEnableKeyRotation, 0, widevine_classic);
}
Status WidevineKeySource::GetKey(TrackType track_type, EncryptionKey* key) {
@ -566,7 +581,9 @@ bool WidevineKeySource::ExtractEncryptionKey(
DCHECK(!encryption_key_map.empty());
if (!enable_key_rotation) {
encryption_key_map_.swap(encryption_key_map);
// Merge with previously requested keys.
for (auto& pair : encryption_key_map)
encryption_key_map_[pair.first] = std::move(pair.second);
return true;
}
return PushToKeyPool(&encryption_key_map);

View File

@ -36,10 +36,8 @@ class WidevineKeySource : public KeySource {
/// @name KeySource implementation overrides.
/// @{
Status FetchKeys(const std::vector<uint8_t>& pssh_box) override;
Status FetchKeys(const std::vector<std::vector<uint8_t>>& key_ids) override;
Status FetchKeys(uint32_t asset_id) override;
Status FetchKeys(EmeInitDataType init_data_type,
const std::vector<uint8_t>& init_data) override;
Status GetKey(TrackType track_type, EncryptionKey* key) override;
Status GetKey(const std::vector<uint8_t>& key_id,
EncryptionKey* key) override;

View File

@ -75,6 +75,7 @@ const uint8_t kRequestKeyId[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05};
// 32-bit with leading bit set, to verify that big uint32_t can be handled
// correctly.
const uint32_t kClassicAssetId = 0x80038cd9;
const uint8_t kClassicAssetIdBytes[] = {0x80, 0x03, 0x8c, 0xd9};
std::string Base64Encode(const std::string& input) {
std::string output;
@ -319,11 +320,11 @@ TEST_P(WidevineKeySourceTest, LicenseStatusCencWithPsshBoxOK) {
widevine_key_source_->set_signer(std::move(mock_request_signer_));
std::vector<uint8_t> pssh_box(kRequestPsshBox,
kRequestPsshBox + arraysize(kRequestPsshBox));
ASSERT_OK(widevine_key_source_->FetchKeys(pssh_box));
ASSERT_OK(widevine_key_source_->FetchKeys(EmeInitDataType::CENC, pssh_box));
VerifyKeys(false);
}
TEST_P(WidevineKeySourceTest, LicenseStatusCencWithKeyIdsOK) {
TEST_P(WidevineKeySourceTest, LicenseStatusCencWithKeyIdOK) {
std::string expected_pssh_data(
kRequestPsshDataFromKeyIds,
kRequestPsshDataFromKeyIds + arraysize(kRequestPsshDataFromKeyIds));
@ -341,10 +342,9 @@ TEST_P(WidevineKeySourceTest, LicenseStatusCencWithKeyIdsOK) {
CreateWidevineKeySource();
widevine_key_source_->set_signer(std::move(mock_request_signer_));
std::vector<std::vector<uint8_t>> key_ids;
key_ids.push_back(std::vector<uint8_t>(
kRequestKeyId, kRequestKeyId + arraysize(kRequestKeyId)));
ASSERT_OK(widevine_key_source_->FetchKeys(key_ids));
std::vector<uint8_t> key_id(kRequestKeyId,
kRequestKeyId + arraysize(kRequestKeyId));
ASSERT_OK(widevine_key_source_->FetchKeys(EmeInitDataType::WEBM, key_id));
VerifyKeys(false);
}
@ -363,7 +363,10 @@ TEST_P(WidevineKeySourceTest, LicenseStatusClassicOK) {
CreateWidevineKeySource();
widevine_key_source_->set_signer(std::move(mock_request_signer_));
ASSERT_OK(widevine_key_source_->FetchKeys(kClassicAssetId));
ASSERT_OK(widevine_key_source_->FetchKeys(
EmeInitDataType::WIDEVINE_CLASSIC,
std::vector<uint8_t>(std::begin(kClassicAssetIdBytes),
std::end(kClassicAssetIdBytes))));
VerifyKeys(true);
}

View File

@ -640,27 +640,18 @@ bool MP4MediaParser::FetchKeysIfNecessary(
if (!decryption_key_source_)
return true;
Status status;
for (std::vector<ProtectionSystemSpecificHeader>::const_iterator iter =
headers.begin(); iter != headers.end(); ++iter) {
status = decryption_key_source_->FetchKeys(iter->raw_box);
if (!status.ok()) {
// If there is an error, try using the next PSSH box and report if none
// work.
VLOG(1) << "Unable to fetch decryption keys: " << status
<< ", trying the next PSSH box";
continue;
std::vector<uint8_t> pssh_raw_data;
for (const auto& header : headers) {
pssh_raw_data.insert(pssh_raw_data.end(), header.raw_box.begin(),
header.raw_box.end());
}
return true;
}
Status status =
decryption_key_source_->FetchKeys(EmeInitDataType::CENC, pssh_raw_data);
if (!status.ok()) {
LOG(ERROR) << "Error fetching decryption keys: " << status;
return false;
}
LOG(ERROR) << "No viable 'pssh' box found for content decryption.";
return false;
return true;
}
bool MP4MediaParser::EnqueueSample(bool* err) {

View File

@ -31,7 +31,9 @@ const char kKeyId[] = "0123456789012345";
class MockKeySource : public FixedKeySource {
public:
MOCK_METHOD1(FetchKeys, Status(const std::vector<uint8_t>& pssh_data));
MOCK_METHOD2(FetchKeys,
Status(EmeInitDataType init_data_type,
const std::vector<uint8_t>& init_data));
MOCK_METHOD2(GetKey,
Status(const std::vector<uint8_t>& key_id, EncryptionKey* key));
};
@ -247,7 +249,7 @@ TEST_F(MP4MediaParserTest, CencInitWithoutDecryptionSource) {
TEST_F(MP4MediaParserTest, CencWithDecryptionSourceAndAuxInMdat) {
MockKeySource mock_key_source;
EXPECT_CALL(mock_key_source, FetchKeys(_)).WillOnce(Return(Status::OK));
EXPECT_CALL(mock_key_source, FetchKeys(_, _)).WillOnce(Return(Status::OK));
EncryptionKey encryption_key;
encryption_key.key.assign(kKey, kKey + strlen(kKey));
@ -266,7 +268,7 @@ TEST_F(MP4MediaParserTest, CencWithDecryptionSourceAndAuxInMdat) {
TEST_F(MP4MediaParserTest, CencWithDecryptionSourceAndSenc) {
MockKeySource mock_key_source;
EXPECT_CALL(mock_key_source, FetchKeys(_)).WillOnce(Return(Status::OK));
EXPECT_CALL(mock_key_source, FetchKeys(_, _)).WillOnce(Return(Status::OK));
EncryptionKey encryption_key;
encryption_key.key.assign(kKey, kKey + strlen(kKey));

View File

@ -242,17 +242,19 @@ bool WebMMediaParser::FetchKeysIfNecessary(
if (!decryption_key_source_)
return true;
std::vector<std::vector<uint8_t>> key_ids;
Status status;
if (!audio_encryption_key_id.empty()) {
key_ids.push_back(std::vector<uint8_t>(audio_encryption_key_id.begin(),
audio_encryption_key_id.end()));
status.Update(decryption_key_source_->FetchKeys(
EmeInitDataType::WEBM,
std::vector<uint8_t>(audio_encryption_key_id.begin(),
audio_encryption_key_id.end())));
}
if (!video_encryption_key_id.empty()) {
key_ids.push_back(std::vector<uint8_t>(video_encryption_key_id.begin(),
video_encryption_key_id.end()));
status.Update(decryption_key_source_->FetchKeys(
EmeInitDataType::WEBM,
std::vector<uint8_t>(video_encryption_key_id.begin(),
video_encryption_key_id.end())));
}
Status status = decryption_key_source_->FetchKeys(key_ids);
if (!status.ok()) {
LOG(ERROR) << "Error fetching decryption keys: " << status;
return false;

View File

@ -1058,21 +1058,23 @@ bool WvmMediaParser::EmitSample(uint32_t parsed_audio_or_video_stream_id,
return true;
}
bool WvmMediaParser::GetAssetKey(const uint32_t asset_id,
bool WvmMediaParser::GetAssetKey(const uint8_t* asset_id,
EncryptionKey* encryption_key) {
DCHECK(decryption_key_source_);
Status status = decryption_key_source_->FetchKeys(asset_id);
Status status = decryption_key_source_->FetchKeys(
EmeInitDataType::WIDEVINE_CLASSIC,
std::vector<uint8_t>(asset_id, asset_id + sizeof(uint32_t)));
if (!status.ok()) {
LOG(ERROR) << "Fetch Key(s) failed for AssetID = " << asset_id
<< ", error = " << status;
LOG(ERROR) << "Fetch Key(s) failed for AssetID = "
<< ntohlFromBuffer(asset_id) << ", error = " << status;
return false;
}
status = decryption_key_source_->GetKey(KeySource::TRACK_TYPE_HD,
encryption_key);
if (!status.ok()) {
LOG(ERROR) << "Fetch Key(s) failed for AssetID = " << asset_id
<< ", error = " << status;
LOG(ERROR) << "Fetch Key(s) failed for AssetID = "
<< ntohlFromBuffer(asset_id) << ", error = " << status;
return false;
}
@ -1097,22 +1099,17 @@ bool WvmMediaParser::ProcessEcm() {
ecm_data += sizeof(uint32_t); // old version field - skip.
ecm_data += sizeof(uint32_t); // clear lead - skip.
ecm_data += sizeof(uint32_t); // system id(includes ECM version) - skip.
uint32_t asset_id = ntohlFromBuffer(ecm_data);
if (asset_id == 0) {
LOG(ERROR) << "AssetID in ECM is not valid.";
return false;
}
ecm_data += sizeof(uint32_t); // asset_id.
EncryptionKey encryption_key;
if (!GetAssetKey(asset_id, &encryption_key)) {
if (!GetAssetKey(ecm_data, &encryption_key)) {
return false;
}
if (encryption_key.key.size() < kAssetKeySizeBytes) {
LOG(ERROR) << "Asset Key size of " << encryption_key.key.size()
<< " for AssetID = " << asset_id
<< " for AssetID = " << ntohlFromBuffer(ecm_data)
<< " is less than minimum asset key size.";
return false;
}
ecm_data += sizeof(uint32_t); // asset_id.
// Legacy WVM content may have asset keys > 16 bytes.
// Use only the first 16 bytes of the asset key to get
// the content key.

View File

@ -200,7 +200,7 @@ class WvmMediaParser : public MediaParser {
// to ouput media sample as encrypted.
bool Output(bool must_process_encrypted);
bool GetAssetKey(const uint32_t asset_id, EncryptionKey* encryption_key);
bool GetAssetKey(const uint8_t* asset_id, EncryptionKey* encryption_key);
// Callback invoked by the ES media parser
// to emit a new audio/video access unit.

View File

@ -52,7 +52,9 @@ class MockKeySource : public FixedKeySource {
MockKeySource() {}
~MockKeySource() override {}
MOCK_METHOD1(FetchKeys, Status(uint32_t asset_id));
MOCK_METHOD2(FetchKeys,
Status(EmeInitDataType init_data_type,
const std::vector<uint8_t>& init_data));
MOCK_METHOD2(GetKey, Status(TrackType track_type,
EncryptionKey* key));
@ -185,7 +187,7 @@ TEST_F(WvmMediaParserTest, ParseWvmInitWithoutKeySource) {
}
TEST_F(WvmMediaParserTest, ParseWvm) {
EXPECT_CALL(*key_source_, FetchKeys(_)).WillOnce(Return(Status::OK));
EXPECT_CALL(*key_source_, FetchKeys(_, _)).WillOnce(Return(Status::OK));
EXPECT_CALL(*key_source_, GetKey(_, _))
.WillOnce(DoAll(SetArgPointee<1>(encryption_key_), Return(Status::OK)));
Parse(kWvmFile);
@ -196,7 +198,7 @@ TEST_F(WvmMediaParserTest, ParseWvm) {
}
TEST_F(WvmMediaParserTest, ParseWvmWith64ByteAssetKey) {
EXPECT_CALL(*key_source_, FetchKeys(_)).WillOnce(Return(Status::OK));
EXPECT_CALL(*key_source_, FetchKeys(_, _)).WillOnce(Return(Status::OK));
// WVM uses only the first 16 bytes of the asset key.
encryption_key_.key.resize(64);
encryption_key_.key.assign(k64ByteAssetKey, k64ByteAssetKey + 64);
@ -209,7 +211,7 @@ TEST_F(WvmMediaParserTest, ParseWvmWith64ByteAssetKey) {
}
TEST_F(WvmMediaParserTest, ParseMultiConfigWvm) {
EXPECT_CALL(*key_source_, FetchKeys(_)).WillOnce(Return(Status::OK));
EXPECT_CALL(*key_source_, FetchKeys(_, _)).WillOnce(Return(Status::OK));
EXPECT_CALL(*key_source_, GetKey(_, _))
.WillOnce(DoAll(SetArgPointee<1>(encryption_key_), Return(Status::OK)));
Parse(kMultiConfigWvmFile);