diff --git a/packager/app/packager_main.cc b/packager/app/packager_main.cc index 0e57dc9b68..060b5d895b 100644 --- a/packager/app/packager_main.cc +++ b/packager/app/packager_main.cc @@ -13,6 +13,7 @@ #include "packager/app/mpd_flags.h" #include "packager/app/muxer_flags.h" #include "packager/app/packager_util.h" +#include "packager/app/playready_key_encryption_flags.h" #include "packager/app/stream_descriptor.h" #include "packager/app/vlog_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. std::unique_ptr 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(); if (!encryption_key_source) return false; @@ -538,8 +540,10 @@ int PackagerMain(int argc, char** argv) { return kSuccess; } - if (!ValidateWidevineCryptoFlags() || !ValidateFixedCryptoFlags()) + if (!ValidateWidevineCryptoFlags() || !ValidateFixedCryptoFlags() || + !ValidatePRCryptoFlags()) { return kArgumentValidationFailed; + } if (FLAGS_override_version) SetPackagerVersionForTesting(FLAGS_test_version); diff --git a/packager/app/packager_util.cc b/packager/app/packager_util.cc index fba302fd3e..bf46af06a4 100644 --- a/packager/app/packager_util.cc +++ b/packager/app/packager_util.cc @@ -10,6 +10,7 @@ #include #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/muxer_flags.h" #include "packager/app/widevine_encryption_flags.h" @@ -19,6 +20,7 @@ #include "packager/media/base/media_stream.h" #include "packager/media/base/muxer.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/stream_info.h" #include "packager/media/base/widevine_key_source.h" @@ -99,6 +101,34 @@ std::unique_ptr CreateEncryptionKeySource() { } else if (FLAGS_enable_fixed_key_encryption) { encryption_key_source = FixedKeySource::CreateFromHexStrings( 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 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(); + } } return encryption_key_source; } diff --git a/packager/app/playready_key_encryption_flags.cc b/packager/app/playready_key_encryption_flags.cc new file mode 100644 index 0000000000..d911e9b9e9 --- /dev/null +++ b/packager/app/playready_key_encryption_flags.cc @@ -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 diff --git a/packager/app/playready_key_encryption_flags.h b/packager/app/playready_key_encryption_flags.h new file mode 100644 index 0000000000..664d00240d --- /dev/null +++ b/packager/app/playready_key_encryption_flags.h @@ -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 + +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_ diff --git a/packager/media/base/http_key_fetcher.cc b/packager/media/base/http_key_fetcher.cc index 49f55b587d..33bfe2dc31 100644 --- a/packager/media/base/http_key_fetcher.cc +++ b/packager/media/base/http_key_fetcher.cc @@ -16,6 +16,10 @@ namespace shaka { namespace { 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. class ScopedCurl { @@ -98,7 +102,6 @@ Status HttpKeyFetcher::FetchInternal(HttpMethod method, const std::string& data, std::string* response) { DCHECK(method == GET || method == POST); - static LibCurlInitializer lib_curl_initializer; ScopedCurl scoped_curl; @@ -116,11 +119,36 @@ Status HttpKeyFetcher::FetchInternal(HttpMethod method, curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, AppendToString); 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) { curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str()); 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); if (res != CURLE_OK) { std::string error_message = base::StringPrintf( diff --git a/packager/media/base/http_key_fetcher.h b/packager/media/base/http_key_fetcher.h index d312ad3bd5..a668f58e3f 100644 --- a/packager/media/base/http_key_fetcher.h +++ b/packager/media/base/http_key_fetcher.h @@ -53,6 +53,24 @@ class HttpKeyFetcher : public KeyFetcher { const std::string& data, 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: enum HttpMethod { GET, @@ -65,6 +83,10 @@ class HttpKeyFetcher : public KeyFetcher { const std::string& data, std::string* response); 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); }; diff --git a/packager/media/base/media_base.gyp b/packager/media/base/media_base.gyp index bdfa614b27..7853b741c9 100644 --- a/packager/media/base/media_base.gyp +++ b/packager/media/base/media_base.gyp @@ -69,6 +69,8 @@ 'network_util.h', 'offset_byte_queue.cc', 'offset_byte_queue.h', + 'playready_key_source.cc', + 'playready_key_source.h', 'producer_consumer_queue.h', 'protection_system_specific_info.cc', 'protection_system_specific_info.h', diff --git a/packager/media/base/playready_key_source.cc b/packager/media/base/playready_key_source.cc new file mode 100644 index 0000000000..8ef375d19a --- /dev/null +++ b/packager/media/base/playready_key_source.cc @@ -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 +#include +#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 = "" + "" + ""; +const std::string kPlayHeaderObject_4_0 = "16" + "AESCTR$0$1" + ""; +const std::string kAcquireLicenseRequest = + "" + "" + "" + "" + "" + "" + "9A04F079-9840-4286-AB92-E65BE0885F95" + "" + "" + "" + "" + "$0" + "P0S" + "" + "" + "" + "" + "" + ""; + +bool Base64StringToBytes(const std::string& base64_string, + std::vector* 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 ConvertGuidEndianness(const std::vector& input) { + std::vector 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& key_id, + const std::vector& key, + std::vector* output) { + CHECK(output); + std::vector key_id_converted = ConvertGuidEndianness(key_id); + std::vector encrypted_key_id(key_id_converted.size()); + std::unique_ptr 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 record_value = + std::vector(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(record_type & 0xff)); + writer_pr_record.AppendInt(static_cast((record_type >> 8) & 0xff)); + writer_pr_record.AppendInt(static_cast(record_length & 0xff)); + writer_pr_record.AppendInt(static_cast((record_length >> 8) & 0xff)); + for (auto record_item: record_value) { + writer_pr_record.AppendInt(static_cast(record_item & 0xff)); + writer_pr_record.AppendInt(static_cast((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(playready_header_length & 0xff)); + writer_pr_header_object.AppendInt( + static_cast((playready_header_length >> 8) & 0xff)); + writer_pr_header_object.AppendInt( + static_cast((playready_header_length >> 16) & 0xff)); + writer_pr_header_object.AppendInt( + static_cast((playready_header_length >> 24) & 0xff)); + writer_pr_header_object.AppendInt( + static_cast(record_count & 0xff)); + writer_pr_header_object.AppendInt( + static_cast((record_count >> 8) & 0xff)); + writer_pr_header_object.AppendBuffer(writer_pr_record); + *output = std::vector(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 encryption_key) + : encryption_key_(std::move(encryption_key)) { +} + +PlayReadyKeySource::~PlayReadyKeySource() {} + +std::unique_ptr PlayReadyKeySource::CreateFromKeyAndKeyId( + const std::string& key_id_hex, const std::string& key_hex) { + std::unique_ptr 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(); + } + std::vector key; + if (!base::HexStringToBytes(key_hex, &encryption_key->key)) { + LOG(ERROR) << "Cannot parse key_hex " << key_hex; + return std::unique_ptr(); + } + std::vector 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(); + } + 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( + 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 = ""; + 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 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 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& pssh_box) { + // Does nothing for playready encryption/decryption. + return Status::OK; +} + +Status PlayReadyKeySource::FetchKeys( + const std::vector>& 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& 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 diff --git a/packager/media/base/playready_key_source.h b/packager/media/base/playready_key_source.h new file mode 100644 index 0000000000..00ebf4ba00 --- /dev/null +++ b/packager/media/base/playready_key_source.h @@ -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 +#include +#include + +#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& pssh_box) override; + Status FetchKeys(const std::vector>& key_ids) override; + Status FetchKeys(uint32_t asset_id) override; + + Status GetKey(TrackType track_type, EncryptionKey* key) override; + Status GetKey(const std::vector& 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 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 key); + + std::unique_ptr 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_ diff --git a/packager/packager.gyp b/packager/packager.gyp index 6347705a0d..6d8db451e0 100644 --- a/packager/packager.gyp +++ b/packager/packager.gyp @@ -26,6 +26,8 @@ 'app/packager_main.cc', 'app/packager_util.cc', 'app/packager_util.h', + 'app/playready_key_encryption_flags.cc', + 'app/playready_key_encryption_flags.h', 'app/retired_flags.cc', 'app/retired_flags.h', 'app/stream_descriptor.cc',