From 596e59c8becb9ecdc3a50517735a2ffb0b8e60d9 Mon Sep 17 00:00:00 2001 From: Kongqun Yang Date: Mon, 13 Jan 2014 20:52:05 -0800 Subject: [PATCH] Add RSA signing support in WidevineEncryptorSource Defines an abstract request signer class and two implementations of the signer class, AesRequestSigner for AES-CBC signing and RsaRequestSigner for RSA-PSS signing. WidevineEncryptorSource now uses Signer for signature generation. Change-Id: I4528409a9be998535bccde40fdadb412e4bbdaf3 --- media/base/request_signer.cc | 73 +++++++++++++++++++++ media/base/request_signer.h | 86 +++++++++++++++++++++++++ media/base/status.h | 6 +- media/base/widevine_encryptor_source.cc | 84 ++++++------------------ media/base/widevine_encryptor_source.h | 30 ++++----- packager.gyp | 42 +++++++++++- 6 files changed, 237 insertions(+), 84 deletions(-) create mode 100644 media/base/request_signer.cc create mode 100644 media/base/request_signer.h diff --git a/media/base/request_signer.cc b/media/base/request_signer.cc new file mode 100644 index 0000000000..9c39fc21ea --- /dev/null +++ b/media/base/request_signer.cc @@ -0,0 +1,73 @@ +// Copyright (c) 2013 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. + +#include "media/base/request_signer.h" + +#include "base/sha1.h" +#include "base/strings/string_number_conversions.h" +#include "media/base/aes_encryptor.h" +#include "media/base/rsa_key.h" + +namespace media { + +RequestSigner::RequestSigner(const std::string& signer_name) + : signer_name_(signer_name) {} +RequestSigner::~RequestSigner() {} + +AesRequestSigner::AesRequestSigner(const std::string& signer_name, + scoped_ptr encryptor) + : RequestSigner(signer_name), aes_cbc_encryptor_(encryptor.Pass()) { + DCHECK(aes_cbc_encryptor_); +} +AesRequestSigner::~AesRequestSigner() {} + +AesRequestSigner* AesRequestSigner::CreateSigner(const std::string& signer_name, + const std::string& aes_key_hex, + const std::string& iv_hex) { + std::vector aes_key; + if (!base::HexStringToBytes(aes_key_hex, &aes_key)) { + LOG(ERROR) << "Failed to convert hex string to bytes: " << aes_key_hex; + return NULL; + } + std::vector iv; + if (!base::HexStringToBytes(iv_hex, &iv)) { + LOG(ERROR) << "Failed to convert hex string to bytes: " << iv_hex; + return NULL; + } + + scoped_ptr encryptor(new AesCbcEncryptor()); + if (!encryptor->InitializeWithIv(aes_key, iv)) + return NULL; + return new AesRequestSigner(signer_name, encryptor.Pass()); +} + +bool AesRequestSigner::GenerateSignature(const std::string& message, + std::string* signature) { + aes_cbc_encryptor_->Encrypt(base::SHA1HashString(message), signature); + return true; +} + +RsaRequestSigner::RsaRequestSigner(const std::string& signer_name, + scoped_ptr rsa_private_key) + : RequestSigner(signer_name), rsa_private_key_(rsa_private_key.Pass()) { + DCHECK(rsa_private_key_); +} +RsaRequestSigner::~RsaRequestSigner() {} + +RsaRequestSigner* RsaRequestSigner::CreateSigner( + const std::string& signer_name, + const std::string& pkcs1_rsa_key) { + scoped_ptr rsa_private_key( + RsaPrivateKey::Create(pkcs1_rsa_key)); + if (!rsa_private_key) + return NULL; + return new RsaRequestSigner(signer_name, rsa_private_key.Pass()); +} + +bool RsaRequestSigner::GenerateSignature(const std::string& message, + std::string* signature) { + return rsa_private_key_->GenerateSignature(message, signature); +} + +} // namespace media diff --git a/media/base/request_signer.h b/media/base/request_signer.h new file mode 100644 index 0000000000..38257fa48a --- /dev/null +++ b/media/base/request_signer.h @@ -0,0 +1,86 @@ +// Copyright (c) 2013 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. + +#ifndef MEDIA_BASE_REQUEST_SIGNER_H_ +#define MEDIA_BASE_REQUEST_SIGNER_H_ + +#include + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" + +namespace media { + +class AesCbcEncryptor; +class RsaPrivateKey; + +// Define an abstract signer class for signature generation. +class RequestSigner { + public: + virtual ~RequestSigner(); + + // Generate signature for |message|. |signature| should not be NULL. + // Return true on success. + virtual bool GenerateSignature(const std::string& message, + std::string* signature) = 0; + + const std::string& signer_name() const { return signer_name_; } + + protected: + explicit RequestSigner(const std::string& signer_name); + + private: + std::string signer_name_; + + DISALLOW_COPY_AND_ASSIGN(RequestSigner); +}; + +// AesRequestSigner uses AES-CBC signing. +class AesRequestSigner : public RequestSigner { + public: + virtual ~AesRequestSigner(); + + // Create an AesSigner object from key and iv in hex. + // Return NULL on failure. + static AesRequestSigner* CreateSigner(const std::string& signer_name, + const std::string& aes_key_hex, + const std::string& iv_hex); + + virtual bool GenerateSignature(const std::string& message, + std::string* signature) OVERRIDE; + + private: + AesRequestSigner(const std::string& signer_name, + scoped_ptr encryptor); + + scoped_ptr aes_cbc_encryptor_; + + DISALLOW_COPY_AND_ASSIGN(AesRequestSigner); +}; + +// RsaRequestSigner uses RSA-PSS signing. +class RsaRequestSigner : public RequestSigner { + public: + virtual ~RsaRequestSigner(); + + // Create an RsaSigner object using a DER encoded PKCS#1 RSAPrivateKey. + // Return NULL on failure. + static RsaRequestSigner* CreateSigner(const std::string& signer_name, + const std::string& pkcs1_rsa_key); + + virtual bool GenerateSignature(const std::string& message, + std::string* signature) OVERRIDE; + + private: + RsaRequestSigner(const std::string& signer_name, + scoped_ptr rsa_private_key); + + scoped_ptr rsa_private_key_; + + DISALLOW_COPY_AND_ASSIGN(RsaRequestSigner); +}; + +} // namespace media + +#endif // MEDIA_BASE_REQUEST_SIGNER_H_ diff --git a/media/base/status.h b/media/base/status.h index ae9fe12f4d..aa1d2113a6 100644 --- a/media/base/status.h +++ b/media/base/status.h @@ -52,7 +52,11 @@ enum Code { // This track fragment is finalized. FRAGMENT_FINALIZED, - // TODO(kqyang): define packager specific error codes. + // Server errors. Receives malformed response from server. + SERVER_ERROR, + + // Internal errors. Some invariants have been broken. + INTERNAL_ERROR, }; } // namespace error diff --git a/media/base/widevine_encryptor_source.cc b/media/base/widevine_encryptor_source.cc index 697dae2bf8..17a83f3924 100644 --- a/media/base/widevine_encryptor_source.cc +++ b/media/base/widevine_encryptor_source.cc @@ -7,11 +7,9 @@ #include "base/base64.h" #include "base/json/json_reader.h" #include "base/json/json_writer.h" -#include "base/sha1.h" -#include "base/strings/string_number_conversions.h" #include "base/values.h" -#include "media/base/aes_encryptor.h" #include "media/base/httpfetcher.h" +#include "media/base/request_signer.h" // TODO(kqyang): Move media/mp4/rcheck.h to media/base/. // Remove this definition and use RCHECK in rcheck.h instead. @@ -87,51 +85,16 @@ namespace media { WidevineEncryptorSource::WidevineEncryptorSource(const std::string& server_url, const std::string& content_id, - TrackType track_type) + TrackType track_type, + scoped_ptr signer) : server_url_(server_url), content_id_(content_id), - track_type_(track_type) { - DCHECK(!server_url.empty()); - DCHECK(!content_id.empty()); + track_type_(track_type), + signer_(signer.Pass()) { + DCHECK(signer_); } WidevineEncryptorSource::~WidevineEncryptorSource() {} -bool WidevineEncryptorSource::SetAesSigningKey(const std::string& signer, - const std::string& aes_key_hex, - const std::string& iv_hex) { - DCHECK(!aes_cbc_encryptor_); - signer_ = signer; - - std::vector aes_key; - if (!base::HexStringToBytes(aes_key_hex, &aes_key)) { - LOG(ERROR) << "Failed to convert hex string to bytes: " << aes_key_hex; - return false; - } - std::vector iv; - if (!base::HexStringToBytes(iv_hex, &iv)) { - LOG(ERROR) << "Failed to convert hex string to bytes: " << iv_hex; - return false; - } - - scoped_ptr encryptor(new AesCbcEncryptor()); - if (!encryptor->InitializeWithIv(aes_key, iv)) { - LOG(ERROR) << "Failed to initialize encryptor with key: " << aes_key_hex - << " iv:" << iv_hex; - return false; - } - aes_cbc_encryptor_ = encryptor.Pass(); - return true; -} - -bool WidevineEncryptorSource::SetRsaSigningKey( - const std::string& signer, - const std::string& pkcs8_rsa_key) { - DCHECK(!aes_cbc_encryptor_); - // TODO(kqyang): Implement it. - NOTIMPLEMENTED(); - return false; -} - Status WidevineEncryptorSource::Initialize() { std::string request; FillRequest(content_id_, &request); @@ -169,13 +132,17 @@ Status WidevineEncryptorSource::Initialize() { return Status::OK; } -void WidevineEncryptorSource::GenerateSignature(const std::string& message, - std::string* signature) { - DCHECK(signature); - if (aes_cbc_encryptor_) - aes_cbc_encryptor_->Encrypt(base::SHA1HashString(message), signature); - else - NOTIMPLEMENTED() << "Rsa signing is not implemented yet."; +WidevineEncryptorSource::TrackType +WidevineEncryptorSource::GetTrackTypeFromString( + const std::string& track_type_string) { + if (track_type_string == "SD") + return TRACK_TYPE_SD; + if (track_type_string == "HD") + return TRACK_TYPE_HD; + if (track_type_string == "AUDIO") + return TRACK_TYPE_AUDIO; + LOG(WARNING) << "Unexpected track type: " << track_type_string; + return TRACK_TYPE_UNKNOWN; } void WidevineEncryptorSource::FillRequest(const std::string& content_id, @@ -219,7 +186,8 @@ Status WidevineEncryptorSource::SignRequest(const std::string& request, // Sign the request. std::string signature; - GenerateSignature(request, &signature); + if (!signer_->GenerateSignature(request, &signature)) + return Status(error::INTERNAL_ERROR, "Signature generation failed."); // Encode request and signature using Base64 encoding. std::string request_base64_string; @@ -231,7 +199,7 @@ Status WidevineEncryptorSource::SignRequest(const std::string& request, base::DictionaryValue signed_request_dict; signed_request_dict.SetString("request", request_base64_string); signed_request_dict.SetString("signature", signature_base64_string); - signed_request_dict.SetString("signer", signer_); + signed_request_dict.SetString("signer", signer_->signer_name()); base::JSONWriter::Write(&signed_request_dict, signed_request); return Status::OK; @@ -258,17 +226,7 @@ bool WidevineEncryptorSource::DecodeResponse(const std::string& raw_response, bool WidevineEncryptorSource::IsExpectedTrackType( const std::string& track_type_string) { - switch (track_type_) { - case TRACK_TYPE_SD: - return track_type_string == "SD"; - case TRACK_TYPE_HD: - return track_type_string == "HD"; - case TRACK_TYPE_AUDIO: - return track_type_string == "AUDIO"; - default: - NOTREACHED() << "Unexpected track type " << track_type_; - return false; - } + return track_type_ == GetTrackTypeFromString(track_type_string); } bool WidevineEncryptorSource::ExtractEncryptionKey(const std::string& response, diff --git a/media/base/widevine_encryptor_source.h b/media/base/widevine_encryptor_source.h index 1c7769a6e2..a4a2989be3 100644 --- a/media/base/widevine_encryptor_source.h +++ b/media/base/widevine_encryptor_source.h @@ -5,10 +5,13 @@ #ifndef MEDIA_BASE_WIDEVINE_ENCRYPTOR_SOURCE_H_ #define MEDIA_BASE_WIDEVINE_ENCRYPTOR_SOURCE_H_ +#include "base/basictypes.h" #include "media/base/encryptor_source.h" namespace media { +class RequestSigner; + // Defines an encryptor source which talks to Widevine encryption server. class WidevineEncryptorSource : public EncryptorSource { public: @@ -19,29 +22,20 @@ class WidevineEncryptorSource : public EncryptorSource { TRACK_TYPE_AUDIO }; + // Caller transfers the ownership of |signer|, which should not be NULL. WidevineEncryptorSource(const std::string& server_url, const std::string& content_id, - TrackType track_type); - ~WidevineEncryptorSource(); - - // Set AES Signing Key. Use AES-CBC signing for the encryption request. - bool SetAesSigningKey(const std::string& signer, - const std::string& aes_key_hex, - const std::string& iv_hex); - - // Set RSA Signing Key. Use RSA-PSS signing for the encryption request. - bool SetRsaSigningKey(const std::string& signer, - const std::string& pkcs8_rsa_key); + TrackType track_type, + scoped_ptr signer); + virtual ~WidevineEncryptorSource(); // EncryptorSource implementation. - // Note: SetAesSigningKey or SetRsaSigningKey (exclusive) must be called - // before calling Initialize. virtual Status Initialize() OVERRIDE; + static WidevineEncryptorSource::TrackType GetTrackTypeFromString( + const std::string& track_type_string); + private: - // Generate signature using AES-CBC or RSA-PSS. - // |signature| should not be NULL. - void GenerateSignature(const std::string& message, std::string* signature); // Fill |request| with necessary fields for Widevine encryption request. // |request| should not be NULL. void FillRequest(const std::string& content_id, std::string* request); @@ -63,9 +57,7 @@ class WidevineEncryptorSource : public EncryptorSource { std::string server_url_; std::string content_id_; TrackType track_type_; - - std::string signer_; - scoped_ptr aes_cbc_encryptor_; + scoped_ptr signer_; DISALLOW_COPY_AND_ASSIGN(WidevineEncryptorSource); }; diff --git a/packager.gyp b/packager.gyp index dd3a5759a3..4731106bc4 100644 --- a/packager.gyp +++ b/packager.gyp @@ -9,6 +9,41 @@ ], }, 'targets': [ + { + 'target_name': 'httpfetcher', + 'type': 'static_library', + 'sources': [ + 'media/base/httpfetcher.cc', + 'media/base/httpfetcher.h', + 'media/base/status.cc', + ], + 'cflags!': [ '-fno-exceptions' ], + 'cflags_cc!': [ '-fno-exceptions' ], + 'conditions': [ + ['OS=="mac"', { + 'xcode_settings': { + 'GCC_ENABLE_CPP_EXCEPTIONS': 'YES' + } + }] + ], + 'dependencies': [ + 'third_party/happyhttp/happyhttp.gyp:happyhttp_lib', + ], + }, + { + # Note that this test performs real http requests to a http server. + 'target_name': 'httpfetcher_unittest', + 'type': 'executable', + 'sources': [ + 'media/base/httpfetcher_unittest.cc', + ], + 'dependencies': [ + 'base/base.gyp:base', + 'httpfetcher', + 'testing/gtest.gyp:gtest', + 'testing/gtest.gyp:gtest_main', + ], + }, { 'target_name': 'media_base', 'type': 'static_library', @@ -49,6 +84,8 @@ 'media/base/muxer.h', 'media/base/muxer_options.cc', 'media/base/muxer_options.h', + 'media/base/request_signer.cc', + 'media/base/request_signer.h', 'media/base/rsa_key.cc', 'media/base/rsa_key.h', 'media/base/status.cc', @@ -58,9 +95,12 @@ 'media/base/text_track.h', 'media/base/video_stream_info.cc', 'media/base/video_stream_info.h', + 'media/base/widevine_encryptor_source.cc', + 'media/base/widevine_encryptor_source.h', ], 'dependencies': [ 'base/base.gyp:base', + 'httpfetcher', 'third_party/openssl/openssl.gyp:openssl', ], }, @@ -110,7 +150,7 @@ 'media/mp4/aac_audio_specific_config.h', 'media/mp4/box.cc', 'media/mp4/box.h', - 'media/mp4/box_buffer_interface.h', + 'media/mp4/box_buffer.h', 'media/mp4/box_definitions.cc', 'media/mp4/box_definitions.h', 'media/mp4/box_reader.cc',