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:
parent
dadace2c7a
commit
24b0e3031a
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
@ -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_
|
|
@ -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(
|
||||||
|
|
|
@ -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);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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
|
|
@ -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_
|
|
@ -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',
|
||||||
|
|
Loading…
Reference in New Issue