Adding PlayReady support to Shaka packager

PlayReady Header Object Spec (PSSH Data VOD)
http://download.microsoft.com/download/2/3/8/238F67D9-1B8B-48D3-AB83-9C00112268B2/PlayReady%20Header%20Object%202015-08-13-FINAL-CL.PDF

PlayReady Live Streaming Spec (Consult Appendix for package acquisition information)
http://download.microsoft.com/download/2/D/D/2DD6B4E8-CABF-4DE9-8F61-895BE8F1ED33/ProtectingLiveTVServicesWithPlayReady_March2015.pdf

Issue #128
Change-Id: Ic5c0d6c642ceba3bdf9e8e413934f0fbcba242e6
This commit is contained in:
Yohann Connell 2017-01-05 09:32:17 -08:00
parent dadace2c7a
commit 24b0e3031a
10 changed files with 642 additions and 4 deletions

View File

@ -13,6 +13,7 @@
#include "packager/app/mpd_flags.h" #include "packager/app/mpd_flags.h"
#include "packager/app/muxer_flags.h" #include "packager/app/muxer_flags.h"
#include "packager/app/packager_util.h" #include "packager/app/packager_util.h"
#include "packager/app/playready_key_encryption_flags.h"
#include "packager/app/stream_descriptor.h" #include "packager/app/stream_descriptor.h"
#include "packager/app/vlog_flags.h" #include "packager/app/vlog_flags.h"
#include "packager/app/widevine_encryption_flags.h" #include "packager/app/widevine_encryption_flags.h"
@ -458,7 +459,8 @@ bool RunPackager(const StreamDescriptorList& stream_descriptors) {
// Create encryption key source if needed. // Create encryption key source if needed.
std::unique_ptr<KeySource> encryption_key_source; std::unique_ptr<KeySource> encryption_key_source;
if (FLAGS_enable_widevine_encryption || FLAGS_enable_fixed_key_encryption) { if (FLAGS_enable_widevine_encryption || FLAGS_enable_fixed_key_encryption ||
FLAGS_enable_playready_encryption) {
encryption_key_source = CreateEncryptionKeySource(); encryption_key_source = CreateEncryptionKeySource();
if (!encryption_key_source) if (!encryption_key_source)
return false; return false;
@ -538,8 +540,10 @@ int PackagerMain(int argc, char** argv) {
return kSuccess; return kSuccess;
} }
if (!ValidateWidevineCryptoFlags() || !ValidateFixedCryptoFlags()) if (!ValidateWidevineCryptoFlags() || !ValidateFixedCryptoFlags() ||
!ValidatePRCryptoFlags()) {
return kArgumentValidationFailed; return kArgumentValidationFailed;
}
if (FLAGS_override_version) if (FLAGS_override_version)
SetPackagerVersionForTesting(FLAGS_test_version); SetPackagerVersionForTesting(FLAGS_test_version);

View File

@ -10,6 +10,7 @@
#include <iostream> #include <iostream>
#include "packager/app/fixed_key_encryption_flags.h" #include "packager/app/fixed_key_encryption_flags.h"
#include "packager/app/playready_key_encryption_flags.h"
#include "packager/app/mpd_flags.h" #include "packager/app/mpd_flags.h"
#include "packager/app/muxer_flags.h" #include "packager/app/muxer_flags.h"
#include "packager/app/widevine_encryption_flags.h" #include "packager/app/widevine_encryption_flags.h"
@ -19,6 +20,7 @@
#include "packager/media/base/media_stream.h" #include "packager/media/base/media_stream.h"
#include "packager/media/base/muxer.h" #include "packager/media/base/muxer.h"
#include "packager/media/base/muxer_options.h" #include "packager/media/base/muxer_options.h"
#include "packager/media/base/playready_key_source.h"
#include "packager/media/base/request_signer.h" #include "packager/media/base/request_signer.h"
#include "packager/media/base/stream_info.h" #include "packager/media/base/stream_info.h"
#include "packager/media/base/widevine_key_source.h" #include "packager/media/base/widevine_key_source.h"
@ -99,6 +101,34 @@ std::unique_ptr<KeySource> CreateEncryptionKeySource() {
} else if (FLAGS_enable_fixed_key_encryption) { } else if (FLAGS_enable_fixed_key_encryption) {
encryption_key_source = FixedKeySource::CreateFromHexStrings( encryption_key_source = FixedKeySource::CreateFromHexStrings(
FLAGS_key_id, FLAGS_key, FLAGS_pssh, FLAGS_iv); FLAGS_key_id, FLAGS_key, FLAGS_pssh, FLAGS_iv);
} else if (FLAGS_enable_playready_encryption) {
if (!FLAGS_playready_key_id.empty() && !FLAGS_playready_key.empty()) {
encryption_key_source = PlayReadyKeySource::CreateFromKeyAndKeyId(
FLAGS_playready_key_id, FLAGS_playready_key);
} else if (!FLAGS_playready_server_url.empty() &&
!FLAGS_program_identifier.empty()) {
std::unique_ptr<PlayReadyKeySource> playready_key_source;
if (!FLAGS_client_cert_file.empty() &&
!FLAGS_client_cert_private_key_file.empty() &&
!FLAGS_client_cert_private_key_password.empty()) {
playready_key_source.reset(new PlayReadyKeySource(
FLAGS_playready_server_url,
FLAGS_client_cert_file,
FLAGS_client_cert_private_key_file,
FLAGS_client_cert_private_key_password));
} else {
playready_key_source.reset(new PlayReadyKeySource(
FLAGS_playready_server_url));
}
if (!FLAGS_ca_file.empty()) {
playready_key_source->SetCaFile(FLAGS_ca_file);
}
playready_key_source->FetchKeysWithProgramIdentifier(FLAGS_program_identifier);
encryption_key_source = std::move(playready_key_source);
} else {
LOG(ERROR) << "Error creating PlayReady key source.";
return std::unique_ptr<KeySource>();
}
} }
return encryption_key_source; return encryption_key_source;
} }

View File

@ -0,0 +1,73 @@
// Copyright 2017 Google Inc. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
//
// Defines command line flags for playready encryption.
#include "packager/app/playready_key_encryption_flags.h"
#include "packager/app/validate_flag.h"
DEFINE_bool(enable_playready_encryption,
false,
"Enable encryption with playready key.");
DEFINE_string(playready_server_url, "", "PlayReady packaging server url.");
DEFINE_string(program_identifier, "",
"Program identifier for packaging request.");
DEFINE_string(playready_key_id, "", "PlayReady key id in hex.");
DEFINE_string(playready_key, "", "PlayReady key in hex.");
DEFINE_string(ca_file, "",
"Absolute path to the Certificate Authority file for the "
"server cert. PEM format");
DEFINE_string(client_cert_file, "",
"Absolute path to client certificate file.");
DEFINE_string(client_cert_private_key_file, "",
"Absolute path to the Private Key file.");
DEFINE_string(client_cert_private_key_password, "",
"Password to the private key file.");
namespace shaka {
bool ValidatePRCryptoFlags() {
bool success = true;
const char playready_label[] = "--enable_playready_encryption";
bool playready_enabled = FLAGS_enable_playready_encryption;
if (!ValidateFlag("playready_server_url", FLAGS_playready_server_url,
playready_enabled, true, playready_label)) {
success = false;
}
if (!ValidateFlag("program_identifier", FLAGS_program_identifier,
playready_enabled, true, playready_label)) {
success = false;
}
bool use_packaging = !FLAGS_playready_server_url.empty() &&
!FLAGS_program_identifier.empty();
if (!ValidateFlag("playready_key_id", FLAGS_playready_key_id,
playready_enabled, true, playready_label)) {
success = false;
}
if (!ValidateFlag("playready_key", FLAGS_playready_key,
playready_enabled, true, playready_label)) {
success = false;
}
bool use_fixed_key = !FLAGS_playready_key_id.empty() &&
!FLAGS_playready_key.empty();
if (playready_enabled && !use_packaging && !use_fixed_key) {
PrintError("combination of --playready_server_url and "
"--program_identifier or --playready_key_id and playready_key are "
"required");
success = false;
}
if (use_packaging && use_fixed_key) {
PrintError("combination of --playready_server_url, --program_identifier, "
"--playready_key_id, and playready_key is not valid");
success = false;
}
return success;
}
} // namespace shaka

View File

@ -0,0 +1,32 @@
// Copyright 2017 Google Inc. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
//
// Defines command line flags for PlayReady encryption.
#ifndef APP_PLAYREADY_KEY_ENCRYPTION_FLAGS_H_
#define APP_PLAYREADY_KEY_ENCRYPTION_FLAGS_H_
#include <gflags/gflags.h>
DECLARE_bool(enable_playready_encryption);
DECLARE_string(playready_server_url);
DECLARE_string(program_identifier);
DECLARE_string(playready_key_id);
DECLARE_string(playready_key);
DECLARE_string(ca_file);
DECLARE_string(client_cert_file);
DECLARE_string(client_cert_private_key_file);
DECLARE_string(client_cert_private_key_password);
namespace shaka {
/// Validate PlayReady encryption flags.
/// @return true on success, false otherwise.
bool ValidatePRCryptoFlags();
} // namespace shaka
#endif // APP_PLAYREADY_KEY_ENCRYPTION_FLAGS_H_

View File

@ -16,6 +16,10 @@ namespace shaka {
namespace { namespace {
const char kUserAgentString[] = "shaka-packager-http_fetcher/1.0"; const char kUserAgentString[] = "shaka-packager-http_fetcher/1.0";
const char kSoapActionHeader[] =
"SOAPAction: \"http://schemas.microsoft.com/DRM/2007/03/protocols/"
"AcquirePackagingData\"";
const char kXmlContentTypeHeader[] = "Content-Type: text/xml; charset=UTF-8";
// Scoped CURL implementation which cleans up itself when goes out of scope. // Scoped CURL implementation which cleans up itself when goes out of scope.
class ScopedCurl { class ScopedCurl {
@ -98,7 +102,6 @@ Status HttpKeyFetcher::FetchInternal(HttpMethod method,
const std::string& data, const std::string& data,
std::string* response) { std::string* response) {
DCHECK(method == GET || method == POST); DCHECK(method == GET || method == POST);
static LibCurlInitializer lib_curl_initializer; static LibCurlInitializer lib_curl_initializer;
ScopedCurl scoped_curl; ScopedCurl scoped_curl;
@ -116,11 +119,36 @@ Status HttpKeyFetcher::FetchInternal(HttpMethod method,
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, AppendToString); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, AppendToString);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, response); curl_easy_setopt(curl, CURLOPT_WRITEDATA, response);
if (!client_cert_private_key_file_.empty() &&
!client_cert_private_key_password_.empty() &&
!client_cert_file_.empty()) {
// Some PlayReady packaging servers only allow connects via HTTPS with
// client certificates.
curl_easy_setopt(curl, CURLOPT_SSLKEY,
client_cert_private_key_file_.data());
curl_easy_setopt(curl, CURLOPT_KEYPASSWD,
client_cert_private_key_password_.data());
curl_easy_setopt(curl, CURLOPT_SSLKEYTYPE, "PEM");
curl_easy_setopt(curl, CURLOPT_SSLCERTTYPE, "PEM");
curl_easy_setopt(curl, CURLOPT_SSLCERT, client_cert_file_.data());
}
if (!ca_file_.empty()) {
// Host validation needs to be off when using self-signed certificates.
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
curl_easy_setopt(curl, CURLOPT_CAINFO, ca_file_.data());
}
if (method == POST) { if (method == POST) {
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str()); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str());
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, data.size()); curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, data.size());
if (data.find("soap:Envelope") > 0) {
// Adds Http headers for SOAP requests.
struct curl_slist *chunk = NULL;
chunk = curl_slist_append(chunk, kXmlContentTypeHeader);
chunk = curl_slist_append(chunk, kSoapActionHeader);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk);
}
} }
CURLcode res = curl_easy_perform(curl); CURLcode res = curl_easy_perform(curl);
if (res != CURLE_OK) { if (res != CURLE_OK) {
std::string error_message = base::StringPrintf( std::string error_message = base::StringPrintf(

View File

@ -53,6 +53,24 @@ class HttpKeyFetcher : public KeyFetcher {
const std::string& data, const std::string& data,
std::string* response); std::string* response);
/// Sets client certificate information for http requests.
/// @param cert_file absolute path to the client certificate.
/// @param private_key_file absolute path to the client certificate
/// private key file.
/// @param private_key_password private key password.
void SetClientCertInfo(const std::string& cert_file,
const std::string& private_key_file,
const std::string& private_key_password) {
client_cert_file_ = cert_file;
client_cert_private_key_file_ = private_key_file;
client_cert_private_key_password_ = private_key_password;
}
/// Sets the Certifiate Authority file information for http requests.
/// @param ca_file absolute path to the client certificate
void SetCaFile(const std::string& ca_file) {
ca_file_ = ca_file;
}
private: private:
enum HttpMethod { enum HttpMethod {
GET, GET,
@ -65,6 +83,10 @@ class HttpKeyFetcher : public KeyFetcher {
const std::string& data, std::string* response); const std::string& data, std::string* response);
const uint32_t timeout_in_seconds_; const uint32_t timeout_in_seconds_;
std::string ca_file_;
std::string client_cert_file_;
std::string client_cert_private_key_file_;
std::string client_cert_private_key_password_;
DISALLOW_COPY_AND_ASSIGN(HttpKeyFetcher); DISALLOW_COPY_AND_ASSIGN(HttpKeyFetcher);
}; };

View File

@ -69,6 +69,8 @@
'network_util.h', 'network_util.h',
'offset_byte_queue.cc', 'offset_byte_queue.cc',
'offset_byte_queue.h', 'offset_byte_queue.h',
'playready_key_source.cc',
'playready_key_source.h',
'producer_consumer_queue.h', 'producer_consumer_queue.h',
'protection_system_specific_info.cc', 'protection_system_specific_info.cc',
'protection_system_specific_info.h', 'protection_system_specific_info.h',

View File

@ -0,0 +1,358 @@
// Copyright 2017 Google Inc. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
#include "packager/media/base/playready_key_source.h"
#include <openssl/aes.h>
#include <algorithm>
#include "packager/base/base64.h"
#include "packager/base/logging.h"
#include "packager/base/strings/string_number_conversions.h"
#include "packager/base/strings/string_util.h"
#include "packager/media/base/buffer_writer.h"
#include "packager/media/base/http_key_fetcher.h"
namespace shaka {
namespace media {
namespace {
const uint32_t kHttpFetchTimeout = 60; // In seconds
const std::string kPlayHeaderObject_4_1 = "<WRMHEADER "
"xmlns=\"http://schemas.microsoft.com/DRM/2007/03/PlayReadyHeader\" "
"version=\"4.1.0.0\"><DATA><PROTECTINFO>"
"<KID VALUE=\"$0\" ALGID=\"AESCTR\" CHECKSUM=\"$1\"></KID></PROTECTINFO>"
"</DATA></WRMHEADER>";
const std::string kPlayHeaderObject_4_0 = "<WRMHEADER "
"xmlns=\"http://schemas.microsoft.com/DRM/2007/03/PlayReadyHeader\" "
"version=\"4.0.0.0\"><DATA><PROTECTINFO><KEYLEN>16</KEYLEN>"
"<ALGID>AESCTR</ALGID></PROTECTINFO><KID>$0</KID><CHECKSUM>$1</CHECKSUM>"
"</DATA></WRMHEADER>";
const std::string kAcquireLicenseRequest =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
"<soap:Envelope xmlns=\"http://schemas.xmlsoap.org/soap/envelope/\" "
"xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
"xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" "
"xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">"
"<soap:Body>"
"<AcquirePackagingData "
"xmlns=\"http://schemas.microsoft.com/DRM/2007/03/protocols\">"
"<challenge "
"xmlns=\"http://schemas.microsoft.com/DRM"
"/2007/03/protocols/AcquirePackagingData/v1.0\">"
"<ProtectionSystems>"
"<ProtectionSystemId>9A04F079-9840-4286-AB92-E65BE0885F95"
"</ProtectionSystemId>"
"</ProtectionSystems>"
"<StreamProtectionRequests>"
"<StreamInformation>"
"<ProgramIdentifier>$0</ProgramIdentifier>"
"<OffsetFromProgramStart>P0S</OffsetFromProgramStart>"
"</StreamInformation>"
"</StreamProtectionRequests>"
"</challenge>"
"</AcquirePackagingData>"
"</soap:Body>"
"</soap:Envelope>";
bool Base64StringToBytes(const std::string& base64_string,
std::vector<uint8_t>* bytes) {
DCHECK(bytes);
std::string str;
if (!base::Base64Decode(base64_string, &str))
return false;
bytes->assign(str.begin(), str.end());
return true;
}
// Converts the key_id's endianness.
std::vector<uint8_t> ConvertGuidEndianness(const std::vector<uint8_t>& input) {
std::vector<uint8_t> output = input;
if (output.size() > 7) { // Defensive check.
output[0] = input[3];
output[1] = input[2];
output[2] = input[1];
output[3] = input[0];
output[4] = input[5];
output[5] = input[4];
output[6] = input[7];
output[7] = input[6];
// 8-15 are an array of bytes with no endianness.
}
return output;
}
// Generates the data section of a PlayReady PSSH.
// PlayReady PSSH Data is a PlayReady Header Object.
// Format is outlined in the following document.
// http://download.microsoft.com/download/2/3/8/238F67D9-1B8B-48D3-AB83-9C00112268B2/PlayReady%20Header%20Object%202015-08-13-FINAL-CL.PDF
shaka::media::Status GeneratePlayReadyPsshData(const std::vector<uint8_t>& key_id,
const std::vector<uint8_t>& key,
std::vector<uint8_t>* output) {
CHECK(output);
std::vector<uint8_t> key_id_converted = ConvertGuidEndianness(key_id);
std::vector<uint8_t> encrypted_key_id(key_id_converted.size());
std::unique_ptr<AES_KEY> aes_key (new AES_KEY);
CHECK_EQ(AES_set_encrypt_key(key.data(), key.size() * 8, aes_key.get()), 0);
AES_ecb_encrypt(key_id_converted.data(), encrypted_key_id.data(),
aes_key.get(), AES_ENCRYPT);
std::string checksum = std::string(encrypted_key_id.begin(),
encrypted_key_id.end()).substr(0, 8);
std::string base64_checksum;
base::Base64Encode(checksum, &base64_checksum);
std::string base64_key_id;
base::Base64Encode(std::string(key_id_converted.begin(),
key_id_converted.end()),
&base64_key_id);
std::string playready_header = kPlayHeaderObject_4_0;
base::ReplaceFirstSubstringAfterOffset(
&playready_header, 0, "$0", base64_key_id);
base::ReplaceFirstSubstringAfterOffset(
&playready_header, 0, "$1", base64_checksum);
// Create a PlayReady Record.
// Outline in section '2.PlayReady Records' of
// 'PlayReady Header Object' document. Note data is in little endian format.
std::vector<uint16_t> record_value =
std::vector<uint16_t>(playready_header.begin(), playready_header.end());
shaka::media::BufferWriter writer_pr_record;
uint16_t record_type = 1; // Indicates that the record contains a rights management header.
uint16_t record_length = record_value.size() * 2;
writer_pr_record.AppendInt(static_cast<uint8_t>(record_type & 0xff));
writer_pr_record.AppendInt(static_cast<uint8_t>((record_type >> 8) & 0xff));
writer_pr_record.AppendInt(static_cast<uint8_t>(record_length & 0xff));
writer_pr_record.AppendInt(static_cast<uint8_t>((record_length >> 8) & 0xff));
for (auto record_item: record_value) {
writer_pr_record.AppendInt(static_cast<uint8_t>(record_item & 0xff));
writer_pr_record.AppendInt(static_cast<uint8_t>((record_item >> 8) & 0xff));
}
// Create the PlayReady Header object.
// Outline in section '1.PlayReady Header Objects' of
// 'PlayReady Header Object' document. Note data is in little endian format.
shaka::media::BufferWriter writer_pr_header_object;
uint32_t playready_header_length = writer_pr_record.Size() + 4 + 2;
uint16_t record_count = 1;
writer_pr_header_object.AppendInt(
static_cast<uint8_t>(playready_header_length & 0xff));
writer_pr_header_object.AppendInt(
static_cast<uint8_t>((playready_header_length >> 8) & 0xff));
writer_pr_header_object.AppendInt(
static_cast<uint8_t>((playready_header_length >> 16) & 0xff));
writer_pr_header_object.AppendInt(
static_cast<uint8_t>((playready_header_length >> 24) & 0xff));
writer_pr_header_object.AppendInt(
static_cast<uint8_t>(record_count & 0xff));
writer_pr_header_object.AppendInt(
static_cast<uint8_t>((record_count >> 8) & 0xff));
writer_pr_header_object.AppendBuffer(writer_pr_record);
*output = std::vector<uint8_t>(writer_pr_header_object.Buffer(),
writer_pr_header_object.Buffer() +
writer_pr_header_object.Size());
return shaka::media::Status::OK;
}
} // namespace
PlayReadyKeySource::PlayReadyKeySource(
const std::string& server_url)
: encryption_key_(new EncryptionKey),
server_url_(server_url) {
}
PlayReadyKeySource::PlayReadyKeySource(const std::string& server_url,
const std::string& client_cert_file,
const std::string& client_cert_private_key_file,
const std::string& client_cert_private_key_password)
: encryption_key_(new EncryptionKey),
server_url_(server_url),
client_cert_file_(client_cert_file),
client_cert_private_key_file_(client_cert_private_key_file),
client_cert_private_key_password_(client_cert_private_key_password) {
}
PlayReadyKeySource::PlayReadyKeySource(
std::unique_ptr<EncryptionKey> encryption_key)
: encryption_key_(std::move(encryption_key)) {
}
PlayReadyKeySource::~PlayReadyKeySource() {}
std::unique_ptr<PlayReadyKeySource> PlayReadyKeySource::CreateFromKeyAndKeyId(
const std::string& key_id_hex, const std::string& key_hex) {
std::unique_ptr<EncryptionKey> encryption_key(new EncryptionKey);
if (!base::HexStringToBytes(key_id_hex, &encryption_key->key_id)) {
LOG(ERROR) << "Cannot parse key_id_hex " << key_id_hex;
return std::unique_ptr<PlayReadyKeySource>();
}
std::vector<uint8_t> key;
if (!base::HexStringToBytes(key_hex, &encryption_key->key)) {
LOG(ERROR) << "Cannot parse key_hex " << key_hex;
return std::unique_ptr<PlayReadyKeySource>();
}
std::vector<uint8_t> pssh_data;
Status status = GeneratePlayReadyPsshData(
encryption_key->key_id, encryption_key->key, &pssh_data);
if (!status.ok()) {
LOG(ERROR) << status.ToString();
return std::unique_ptr<PlayReadyKeySource>();
}
ProtectionSystemSpecificInfo info;
info.add_key_id(encryption_key->key_id);
info.set_system_id(kPlayReadySystemId, arraysize(kPlayReadySystemId));
info.set_pssh_data(pssh_data);
encryption_key->key_system_info.push_back(info);
return std::unique_ptr<PlayReadyKeySource>(
new PlayReadyKeySource(std::move(encryption_key)));
}
Status RetrieveTextInXMLElement(const std::string& element,
const std::string& xml,
std::string* value) {
std::string start_tag = "<" + element + ">";
std::string end_tag = "</" + element + ">";
std::size_t start_pos = xml.find(start_tag);
if (start_pos == std::string::npos) {
return Status(error::SERVER_ERROR,
"Unable to find tag: " + start_tag);
}
start_pos += start_tag.size();
std::size_t end_pos = xml.find(end_tag);
if (end_pos == std::string::npos) {
return Status(error::SERVER_ERROR,
"Unable to find tag: " + end_tag);
}
if (start_pos > end_pos) {
return Status(error::SERVER_ERROR, "Invalid positions");
}
std::size_t segment_len = end_pos - start_pos;
*value = xml.substr(start_pos, segment_len);
return Status::OK;
}
Status SetKeyInformationFromServerResponse(const std::string& response,
EncryptionKey* encryption_key) {
// TODO(robinconnell): Currently all tracks are encrypted using the same
// key_id and key. Add the ability to retrieve multiple key_id/keys from
// the packager response and encrypt multiple tracks using differnt
// key_id/keys.
std::string key_id_hex;
Status status = RetrieveTextInXMLElement("KeyId", response, &key_id_hex);
if (!status.ok()) {
return status;
}
key_id_hex.erase(
std::remove(key_id_hex.begin(), key_id_hex.end(), '-'), key_id_hex.end());
std::string key_data_b64;
status = RetrieveTextInXMLElement("KeyData", response, &key_data_b64);
if (!status.ok()) {
LOG(ERROR) << "Key retreiving KeyData";
return status;
}
std::string pssh_data_b64;
status = RetrieveTextInXMLElement("Data", response, &pssh_data_b64);
if (!status.ok()) {
LOG(ERROR) << "Key retreiving Data";
return status;
}
if (!base::HexStringToBytes(key_id_hex, &encryption_key->key_id)) {
LOG(ERROR) << "Cannot parse key_id_hex, " << key_id_hex;
return Status(error::SERVER_ERROR, "Cannot parse key_id_hex.");
}
if (!Base64StringToBytes(key_data_b64, &encryption_key->key)) {
LOG(ERROR) << "Cannot parse key, " << key_data_b64;
return Status(error::SERVER_ERROR, "Cannot parse key.");
}
std::vector<uint8_t> pssh_data;
if (!Base64StringToBytes(pssh_data_b64, &pssh_data)) {
LOG(ERROR) << "Cannot parse pssh data, " << pssh_data_b64;
return Status(error::SERVER_ERROR, "Cannot parse pssh.");
}
ProtectionSystemSpecificInfo info;
info.add_key_id(encryption_key->key_id);
info.set_system_id(kPlayReadySystemId, arraysize(kPlayReadySystemId));
info.set_pssh_data(pssh_data);
encryption_key->key_system_info.push_back(info);
return Status::OK;
}
Status PlayReadyKeySource::FetchKeysWithProgramIdentifier(
const std::string& program_identifier) {
std::unique_ptr<EncryptionKey> encryption_key(new EncryptionKey);
HttpKeyFetcher key_fetcher(kHttpFetchTimeout);
if (!client_cert_file_.empty() && !client_cert_private_key_file_.empty() &&
!client_cert_private_key_password_.empty()) {
key_fetcher.SetClientCertInfo(client_cert_file_,
client_cert_private_key_file_,
client_cert_private_key_password_);
}
if (!ca_file_.empty()) {
key_fetcher.SetCaFile(ca_file_);
}
std::string acquire_license_request = kAcquireLicenseRequest;
base::ReplaceFirstSubstringAfterOffset(
&acquire_license_request, 0, "$0", program_identifier);
std::string acquire_license_response;
Status status = key_fetcher.FetchKeys(server_url_, acquire_license_request,
&acquire_license_response);
if (!status.ok()) {
LOG(ERROR) << "Server response: " << acquire_license_response;
return status;
}
status = SetKeyInformationFromServerResponse(acquire_license_response,
encryption_key.get());
encryption_key_ = std::move(encryption_key);
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.
return Status::OK;
}
Status PlayReadyKeySource::GetKey(TrackType track_type, EncryptionKey* key) {
// TODO(robinconnell): Currently all tracks are encrypted using the same
// key_id and key. Add the ability to encrypt each track_type using a
// different key_id and key.
DCHECK(key);
DCHECK(encryption_key_);
*key = *encryption_key_;
return Status::OK;
}
Status PlayReadyKeySource::GetKey(const std::vector<uint8_t>& key_id,
EncryptionKey* key) {
// TODO(robinconnell): Currently all tracks are encrypted using the same
// key_id and key. Add the ability to encrypt using multiple key_id/keys.
DCHECK(key);
DCHECK(encryption_key_);
*key = *encryption_key_;
return Status::OK;
}
Status PlayReadyKeySource::GetCryptoPeriodKey(uint32_t crypto_period_index,
TrackType track_type,
EncryptionKey* key) {
// TODO(robinconnell): Implement key rotation.
*key = *encryption_key_;
return Status::OK;
}
} // namespace media
} // namespace shaka

View File

@ -0,0 +1,87 @@
// Copyright 2017 Google Inc. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
#ifndef MEDIA_BASE_PLAYREADY_SOURCE_H_
#define MEDIA_BASE_PLAYREADY_SOURCE_H_
#include <memory>
#include <string>
#include <vector>
#include "packager/media/base/key_source.h"
namespace shaka {
namespace media {
// SystemID defined for PlayReady Drm.
const uint8_t kPlayReadySystemId[] = {0x9a, 0x04, 0xf0, 0x79, 0x98, 0x40, 0x42,
0x86, 0xab, 0x92, 0xe6, 0x5b, 0xe0, 0x88, 0x5f, 0x95};
/// A key source that uses playready for encryption.
class PlayReadyKeySource : public KeySource {
public:
/// Creates a new PlayReadyKeySource from the given packaging information.
/// @param server_url PlayReady packaging server url.
PlayReadyKeySource(const std::string& server_url);
/// Creates a new PlayReadyKeySource from the given packaging information.
/// @param server_url PlayReady packaging server url.
/// @param client_cert_file absolute path to a client certificate.
/// @param client_cert_private_key_file absolute path to the private file
/// for the client certificate.
/// @param client_cert_private_key_password password for the private key.
PlayReadyKeySource(const std::string& server_url,
const std::string& client_cert_file,
const std::string& client_cert_private_key_file,
const std::string& client_cert_private_key_password);
~PlayReadyKeySource() override;
/// @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 GetKey(TrackType track_type, EncryptionKey* key) override;
Status GetKey(const std::vector<uint8_t>& key_id,
EncryptionKey* key) override;
Status GetCryptoPeriodKey(uint32_t crypto_period_index,
TrackType track_type,
EncryptionKey* key) override;
/// @}
virtual Status FetchKeysWithProgramIdentifier(const std::string& program_identifier);
/// Creates a new PlayReadyKeySource from the hex string information.
/// Returns null if the strings are invalid.
/// @param key_id_hex is the key id in hex string.
/// @param key_hex is the key in hex string.
/// Note: GetKey on the created key source will always return the same key
/// for all track types.
static std::unique_ptr<PlayReadyKeySource> CreateFromKeyAndKeyId(
const std::string& key_id_hex, const std::string& key_hex);
/// Sets the Certificate Authority file for validating self-signed certificates.
void SetCaFile(const std::string& ca_file) {
ca_file_ = ca_file;
}
private:
Status GetKeyInternal();
Status GetCryptoPeriodKeyInternal();
explicit PlayReadyKeySource(std::unique_ptr<EncryptionKey> key);
std::unique_ptr<EncryptionKey> encryption_key_;
std::string server_url_;
std::string ca_file_;
std::string client_cert_file_;
std::string client_cert_private_key_file_;
std::string client_cert_private_key_password_;
DISALLOW_COPY_AND_ASSIGN(PlayReadyKeySource);
};
} // namespace media
} // namespace shaka
#endif // MEDIA_BASE_PLAYREADY_SOURCE_H_

View File

@ -26,6 +26,8 @@
'app/packager_main.cc', 'app/packager_main.cc',
'app/packager_util.cc', 'app/packager_util.cc',
'app/packager_util.h', 'app/packager_util.h',
'app/playready_key_encryption_flags.cc',
'app/playready_key_encryption_flags.h',
'app/retired_flags.cc', 'app/retired_flags.cc',
'app/retired_flags.h', 'app/retired_flags.h',
'app/stream_descriptor.cc', 'app/stream_descriptor.cc',