From 958bbe6d051fb787e620b456a3d190cf68285ac6 Mon Sep 17 00:00:00 2001 From: Jeff Tinker Date: Mon, 22 Apr 2013 20:05:55 -0700 Subject: [PATCH] Certificate provisioning verification bug: 8620943 This is a merge of changes made to the Widevine CDM repository during certificate provisioning verification. The following changes are included: Fixes for certificate based licensing https://widevine-internal-review.googlesource.com/#/c/5162/ Base64 encode and decode now handles non-multiple of 24-bits input https://widevine-internal-review.googlesource.com/#/c/4981/ Fixed issues with device provisioning response handling https://widevine-internal-review.googlesource.com/#/c/5153/ Persistent storage to support device certificates https://widevine-internal-review.googlesource.com/#/c/5161/ Enable loading of certificates https://widevine-internal-review.googlesource.com/#/c/5172/ Provide license server url https://widevine-internal-review.googlesource.com/#/c/5173/ Change-Id: I0c032c1ae0055dcc1a7a77ad4b0ea0898030dc7d --- .../build_and_run_all_unit_tests.sh | 21 +- libwvdrmengine/cdm/Android.mk | 3 + libwvdrmengine/cdm/core/include/cdm_engine.h | 22 +- libwvdrmengine/cdm/core/include/cdm_session.h | 6 +- .../cdm/core/include/crypto_session.h | 7 +- .../cdm/core/include/device_files.h | 33 +++ libwvdrmengine/cdm/core/include/file_store.h | 52 +++++ libwvdrmengine/cdm/core/include/license.h | 7 +- libwvdrmengine/cdm/core/src/cdm_engine.cpp | 193 +++++++--------- libwvdrmengine/cdm/core/src/cdm_session.cpp | 20 +- .../cdm/core/src/crypto_session.cpp | 45 ++-- libwvdrmengine/cdm/core/src/device_files.cpp | 196 +++++++++++++++++ .../cdm/core/src/device_files.proto | 47 ++++ libwvdrmengine/cdm/core/src/license.cpp | 32 ++- .../cdm/core/src/string_conversions.cpp | 146 +++++++++++-- libwvdrmengine/cdm/core/test/base64_test.cpp | 193 ++++++++++++++++ .../cdm/core/test/cdm_engine_test.cpp | 119 +++++----- .../cdm/core/test/device_files_unittest.cpp | 92 ++++++++ .../cdm/core/test/file_store_unittest.cpp | 174 +++++++++++++++ .../cdm/core/test/license_unittest.cpp | 12 +- libwvdrmengine/cdm/core/test/url_request.cpp | 27 ++- libwvdrmengine/cdm/core/test/url_request.h | 2 + .../include/wv_content_decryption_module.h | 3 +- libwvdrmengine/cdm/src/file_store.cpp | 206 ++++++++++++++++++ .../cdm/src/wv_content_decryption_module.cpp | 11 +- libwvdrmengine/cdm/test/Android.mk | 12 + .../cdm/test/request_license_test.cpp | 54 ++++- libwvdrmengine/mediadrm/src/WVDrmPlugin.cpp | 5 +- .../mediadrm/test/WVDrmPlugin_test.cpp | 23 +- libwvdrmengine/run_all_unit_tests.sh | 24 ++ 30 files changed, 1497 insertions(+), 290 deletions(-) create mode 100644 libwvdrmengine/cdm/core/include/device_files.h create mode 100644 libwvdrmengine/cdm/core/include/file_store.h create mode 100644 libwvdrmengine/cdm/core/src/device_files.cpp create mode 100644 libwvdrmengine/cdm/core/src/device_files.proto create mode 100644 libwvdrmengine/cdm/core/test/base64_test.cpp create mode 100644 libwvdrmengine/cdm/core/test/device_files_unittest.cpp create mode 100644 libwvdrmengine/cdm/core/test/file_store_unittest.cpp create mode 100644 libwvdrmengine/cdm/src/file_store.cpp create mode 100755 libwvdrmengine/run_all_unit_tests.sh diff --git a/libwvdrmengine/build_and_run_all_unit_tests.sh b/libwvdrmengine/build_and_run_all_unit_tests.sh index d2ae3a6b..c343cb17 100755 --- a/libwvdrmengine/build_and_run_all_unit_tests.sh +++ b/libwvdrmengine/build_and_run_all_unit_tests.sh @@ -11,11 +11,11 @@ cd $ANDROID_BUILD_TOP/external/gtest/ pwd mm -cd $ANDROID_BUILD_TOP/vendor/widevine/libwvdrmengine/test/gmock +cd $ANDROID_BUILD_TOP/vendor/widevine/libwvdrmengine pwd mm -cd $ANDROID_BUILD_TOP/vendor/widevine/libwvdrmengine/cdm/core/test +cd $ANDROID_BUILD_TOP/vendor/widevine/libwvdrmengine/test/gmock pwd mm @@ -43,18 +43,5 @@ cd $ANDROID_BUILD_TOP/vendor/widevine/libwvdrmengine/test/java/src/com/widevine/ pwd mm -echo "waiting for device" -adb root && adb wait-for-device remount && adb sync - -adb shell /system/bin/request_license_test -adb shell /system/bin/policy_engine_unittest -adb shell /system/bin/libwvdrmmediacrypto_test -adb shell /system/bin/libwvdrmdrmplugin_test -adb shell /system/bin/cdm_engine_test -adb shell /system/bin/oemcrypto_test -adb shell LD_LIBRARY_PATH=/system/vendor/lib/mediadrm/ /system/bin/libwvdrmengine_test - -adb shell am start com.widevine.test/com.widevine.test.MediaDrmAPITest - -# TODO: make this test more command line friendly -echo "check logcat output for MediaDrmAPITest" +cd $ANDROID_BUILD_TOP/vendor/widevine/libwvdrmengine +./run_all_unit_tests.sh diff --git a/libwvdrmengine/cdm/Android.mk b/libwvdrmengine/cdm/Android.mk index 62a79123..1344f62b 100644 --- a/libwvdrmengine/cdm/Android.mk +++ b/libwvdrmengine/cdm/Android.mk @@ -12,6 +12,7 @@ LOCAL_C_INCLUDES := \ vendor/widevine/libwvdrmengine/oemcrypto/include LOCAL_C_INCLUDES += \ + external/openssl/include \ external/protobuf/src \ ../oemcrypto/include @@ -26,11 +27,13 @@ LOCAL_SRC_FILES := \ $(CORE_SRC_DIR)/cdm_session.cpp \ $(CORE_SRC_DIR)/crypto_engine.cpp \ $(CORE_SRC_DIR)/crypto_session.cpp \ + $(CORE_SRC_DIR)/device_files.cpp \ $(CORE_SRC_DIR)/license.cpp \ $(CORE_SRC_DIR)/policy_engine.cpp \ $(CORE_SRC_DIR)/properties.cpp \ $(CORE_SRC_DIR)/string_conversions.cpp \ $(SRC_DIR)/clock.cpp \ + $(SRC_DIR)/file_store.cpp \ $(SRC_DIR)/lock.cpp \ $(SRC_DIR)/log.cpp \ $(SRC_DIR)/properties.cpp \ diff --git a/libwvdrmengine/cdm/core/include/cdm_engine.h b/libwvdrmengine/cdm/core/include/cdm_engine.h index adbc2114..e7e50db2 100644 --- a/libwvdrmengine/cdm/core/include/cdm_engine.h +++ b/libwvdrmengine/cdm/core/include/cdm_engine.h @@ -3,13 +3,13 @@ #ifndef CDM_BASE_CDM_ENGINE_H_ #define CDM_BASE_CDM_ENGINE_H_ -#include "crypto_engine.h" #include "timer.h" #include "wv_cdm_types.h" namespace wvcdm { class CdmSession; +class CryptoEngine; class WvCdmEventListener; typedef std::map CdmSessionMap; @@ -32,13 +32,11 @@ class CdmEngine : public TimerHandler { const CdmInitData& init_data, const CdmLicenseType license_type, CdmAppParameterMap& app_parameters, - CdmKeyMessage* key_request); + CdmKeyMessage* key_request, + std::string* server_url); // Accept license response and extract key info. CdmResponseType AddKey(const CdmSessionId& session_id, - bool is_key_system_init_data_present, - const CdmKeySystem& key_system, - const CdmInitData& init_data, const CdmKeyResponse& key_data); // Cancel session and unload keys. @@ -48,16 +46,11 @@ class CdmEngine : public TimerHandler { // Construct valid renewal request for the current session keys. CdmResponseType GenerateRenewalRequest(const CdmSessionId& session_id, - bool is_key_system_init_data_present, - const CdmKeySystem& key_system, - const CdmInitData& init_data, - CdmKeyMessage* key_request); + CdmKeyMessage* key_request, + std::string* server_url); // Accept renewal response and update key info. CdmResponseType RenewKey(const CdmSessionId& session_id, - bool is_key_system_init_data_present, - const CdmKeySystem& key_system, - const CdmInitData& init_data, const CdmKeyResponse& key_data); // Query system information @@ -107,9 +100,7 @@ class CdmEngine : public TimerHandler { // private methods // Cancel all sessions bool CancelSessions(); - void CleanupProvisioingSessions(CdmSession* cdm_session, - CryptoEngine* crypto_engine, - const CdmSessionId& cdm_session_id); + void CleanupProvisioningSession(const CdmSessionId& cdm_session_id); void ComposeJsonRequest(const std::string& message, const std::string& signature, CdmProvisioningRequest* request); @@ -131,6 +122,7 @@ class CdmEngine : public TimerHandler { virtual void OnTimerEvent(); // instance variables + CdmSession* provisioning_session_; CdmSessionMap sessions_; // policy timer diff --git a/libwvdrmengine/cdm/core/include/cdm_session.h b/libwvdrmengine/cdm/core/include/cdm_session.h index 92d3ed11..1c0fce3b 100644 --- a/libwvdrmengine/cdm/core/include/cdm_session.h +++ b/libwvdrmengine/cdm/core/include/cdm_session.h @@ -36,7 +36,8 @@ class CdmSession { CdmResponseType GenerateKeyRequest(const CdmInitData& pssh_data, const CdmLicenseType license_type, CdmAppParameterMap& app_parameters, - CdmKeyMessage* key_request); + CdmKeyMessage* key_request, + std::string* server_url); // AddKey() - Accept license response and extract key info. CdmResponseType AddKey(const CdmKeyResponse& key_response); @@ -65,7 +66,8 @@ class CdmSession { // License renewal // GenerateRenewalRequest() - Construct valid renewal request for the current // session keys. - CdmResponseType GenerateRenewalRequest(CdmKeyMessage* key_request); + CdmResponseType GenerateRenewalRequest(CdmKeyMessage* key_request, + std::string* server_url); // RenewKey() - Accept renewal response and update key info. CdmResponseType RenewKey(const CdmKeyResponse& key_response); diff --git a/libwvdrmengine/cdm/core/include/crypto_session.h b/libwvdrmengine/cdm/core/include/crypto_session.h index 0e031f42..abaf0821 100644 --- a/libwvdrmengine/cdm/core/include/crypto_session.h +++ b/libwvdrmengine/cdm/core/include/crypto_session.h @@ -56,10 +56,11 @@ class CryptoSession { bool GenerateSignature(const std::string& message, std::string* signature); bool RewrapDeviceRSAKey(const std::string& message, - const uint32_t* nonce, - const uint8_t* enc_rsa_key, + const std::string& signature, + const std::string& nonce, + const std::string& enc_rsa_key, size_t enc_rsa_key_length, - const uint8_t* enc_rsa_key_iv, + const std::string& rsa_key_iv, uint8_t* wrapped_rsa_key, size_t* wrapped_rsa_key_length); diff --git a/libwvdrmengine/cdm/core/include/device_files.h b/libwvdrmengine/cdm/core/include/device_files.h new file mode 100644 index 00000000..5bf3e684 --- /dev/null +++ b/libwvdrmengine/cdm/core/include/device_files.h @@ -0,0 +1,33 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// +#ifndef CDM_BASE_DEVICE_FILES_H_ +#define CDM_BASE_DEVICE_FILES_H_ + +#include "wv_cdm_types.h" + +namespace wvcdm { + +class DeviceFiles { + public: + static bool StoreCertificate(const std::string& certificate, + const std::string& wrapped_private_key); + static bool RetrieveCertificate(std::string* certificate, + std::string* wrapped_private_key); + + static std::string GetPath(const char* dir, const char * filename); + static const char* kBasePath; + static const char* kIdmPath; + static const char* kCencPath; + static const char* kDeviceCertificateFileName; + + private: + static bool Hash(const std::string& data, std::string* hash); + static bool StoreFile(const char* name, const std::string& data); + static bool RetrieveFile(const char* name, std::string* data); + + CORE_DISALLOW_COPY_AND_ASSIGN(DeviceFiles); +}; // namespace wvcdm + +} + +#endif // CDM_BASE_DEVICE_FILES_H_ diff --git a/libwvdrmengine/cdm/core/include/file_store.h b/libwvdrmengine/cdm/core/include/file_store.h new file mode 100644 index 00000000..0b10c2e1 --- /dev/null +++ b/libwvdrmengine/cdm/core/include/file_store.h @@ -0,0 +1,52 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// +// File - Platform independent interface for a File class +// +#ifndef CDM_BASE_FILE_STORE_H_ +#define CDM_BASE_FILE_STORE_H_ + +#include "wv_cdm_types.h" + +namespace wvcdm { + +// File class. The implementation is platform dependent. +class File { + public: + // defines as bit flag + enum OpenFlags { + kNoFlags = 0, + kBinary = 1, + kCreate = 2, + kReadOnly = 4, // defauts to read and write access + kTruncate = 8 + }; + + File(); + File(const std::string& file_path, int flags); + virtual ~File(); + + bool Open(const std::string& file_path, int flags); + void Close(); + bool IsOpen(); + bool IsBad(); + + ssize_t Read(void *buf, size_t bytes); + ssize_t Write(const void* buf, size_t bytes); + + static bool Exists(const std::string& file_path); + static bool Remove(const std::string& file_path); + static bool CreateDirectory(const std::string dir_path); + static bool IsDirectory(const std::string& dir_path); + static bool IsRegularFile(const std::string& file_path); + static ssize_t FileSize(const std::string& file_path); + + private: + class Impl; + Impl *impl_; + + CORE_DISALLOW_COPY_AND_ASSIGN(File); +}; + +} // namespace wvcdm + +#endif // CDM_BASE_FILE_STORE_H_ diff --git a/libwvdrmengine/cdm/core/include/license.h b/libwvdrmengine/cdm/core/include/license.h index 30f15e31..4899c6b3 100644 --- a/libwvdrmengine/cdm/core/include/license.h +++ b/libwvdrmengine/cdm/core/include/license.h @@ -27,8 +27,10 @@ class CdmLicense { bool PrepareKeyRequest(const CdmInitData& pssh_data, const CdmLicenseType license_type, CdmAppParameterMap& app_parameters, - CdmKeyMessage* signed_request); - bool PrepareKeyRenewalRequest(CdmKeyMessage* signed_request); + CdmKeyMessage* signed_request, + std::string* server_url); + bool PrepareKeyRenewalRequest(CdmKeyMessage* signed_request, + std::string* server_url); CdmResponseType HandleKeyResponse(const CdmKeyResponse& license_response); CdmResponseType HandleKeyRenewalResponse( const CdmKeyResponse& license_response); @@ -39,6 +41,7 @@ private: LicenseIdentification license_id_; CryptoSession* session_; PolicyEngine* policy_engine_; + std::string server_url_; std::string token_; // Used for certificate based licensing diff --git a/libwvdrmengine/cdm/core/src/cdm_engine.cpp b/libwvdrmengine/cdm/core/src/cdm_engine.cpp index 3a1fda6e..e3e45637 100644 --- a/libwvdrmengine/cdm/core/src/cdm_engine.cpp +++ b/libwvdrmengine/cdm/core/src/cdm_engine.cpp @@ -8,6 +8,7 @@ #include "buffer_reader.h" #include "cdm_session.h" #include "crypto_engine.h" +#include "device_files.h" #include "license_protocol.pb.h" #include "log.h" #include "properties.h" @@ -29,7 +30,7 @@ using video_widevine_server::sdk::SignedProvisioningMessage; typedef std::map::const_iterator CdmSessionIter; -CdmEngine::CdmEngine() { +CdmEngine::CdmEngine() : provisioning_session_(NULL) { Properties::Init(); } @@ -105,7 +106,8 @@ CdmResponseType CdmEngine::GenerateKeyRequest( const CdmInitData& init_data, const CdmLicenseType license_type, CdmAppParameterMap& app_parameters, - CdmKeyMessage* key_request) { + CdmKeyMessage* key_request, + std::string* server_url) { LOGI("CdmEngine::GenerateKeyRequest"); CdmSessionIter iter = sessions_.find(session_id); @@ -140,7 +142,8 @@ CdmResponseType CdmEngine::GenerateKeyRequest( CdmResponseType sts = iter->second->GenerateKeyRequest(extracted_pssh, license_type, app_parameters, - key_request); + key_request, + server_url); if (KEY_MESSAGE != sts) { LOGE("CdmEngine::GenerateKeyRequest: key request generation failed, sts=%d", @@ -156,9 +159,6 @@ CdmResponseType CdmEngine::GenerateKeyRequest( CdmResponseType CdmEngine::AddKey( const CdmSessionId& session_id, - bool is_key_system_init_data_present, - const CdmKeySystem& key_system, - const CdmInitData& init_data, const CdmKeyResponse& key_data) { LOGI("CdmEngine::AddKey"); @@ -168,14 +168,6 @@ CdmResponseType CdmEngine::AddKey( return KEY_ERROR; } - if (is_key_system_init_data_present) { - // TODO(edwinwong, rfrias): validate key_system has not changed - } - - if (is_key_system_init_data_present) { - // TODO(edwinwong, rfrias): validate init_data has not changed - } - if (key_data.empty()) { LOGE("CdmEngine::AddKey: no key_data"); return KEY_ERROR; @@ -218,10 +210,8 @@ CdmResponseType CdmEngine::CancelKeyRequest( CdmResponseType CdmEngine::GenerateRenewalRequest( const CdmSessionId& session_id, - bool is_key_system_init_data_present, - const CdmKeySystem& key_system, - const CdmInitData& init_data, - CdmKeyMessage* key_request) { + CdmKeyMessage* key_request, + std::string* server_url) { LOGI("CdmEngine::GenerateRenewalRequest"); CdmSessionIter iter = sessions_.find(session_id); @@ -230,14 +220,6 @@ CdmResponseType CdmEngine::GenerateRenewalRequest( return KEY_ERROR; } - if (is_key_system_init_data_present) { - // TODO(edwinwong, rfrias): validate key_system has not changed - } - - if (is_key_system_init_data_present) { - // TODO(edwinwong, rfrias): validate init_data has not changed - } - if (!key_request) { LOGE("CdmEngine::GenerateRenewalRequest: no key request destination provided"); return KEY_ERROR; @@ -245,7 +227,8 @@ CdmResponseType CdmEngine::GenerateRenewalRequest( key_request->clear(); - CdmResponseType sts = iter->second->GenerateRenewalRequest(key_request); + CdmResponseType sts = iter->second->GenerateRenewalRequest(key_request, + server_url); if (KEY_MESSAGE != sts) { LOGE("CdmEngine::GenerateRenewalRequest: key request generation failed, sts=%d", @@ -258,9 +241,6 @@ CdmResponseType CdmEngine::GenerateRenewalRequest( CdmResponseType CdmEngine::RenewKey( const CdmSessionId& session_id, - bool is_key_system_init_data_present, - const CdmKeySystem& key_system, - const CdmInitData& init_data, const CdmKeyResponse& key_data) { LOGI("CdmEngine::RenewKey"); @@ -270,14 +250,6 @@ CdmResponseType CdmEngine::RenewKey( return KEY_ERROR; } - if (is_key_system_init_data_present) { - // TODO(edwinwong, rfrias): validate key_system has not changed - } - - if (is_key_system_init_data_present) { - // TODO(edwinwong, rfrias): validate init_data has not changed - } - if (key_data.empty()) { LOGE("CdmEngine::RenewKey: no key_data"); return KEY_ERROR; @@ -357,15 +329,9 @@ CdmResponseType CdmEngine::QueryKeyControlInfo( return iter->second->QueryKeyControlInfo(key_info); } -void CdmEngine::CleanupProvisioingSessions( - CdmSession* cdm_session, - CryptoEngine* crypto_engine, - const CdmSessionId& cdm_session_id) { - if (NULL == cdm_session) return; - - cdm_session->DestroySession(); - if (crypto_engine) crypto_engine->DestroySession(cdm_session_id); - delete cdm_session; +void CdmEngine::CleanupProvisioningSession(const CdmSessionId& cdm_session_id) { + CloseSession(cdm_session_id); + provisioning_session_ = NULL; } /* @@ -391,21 +357,16 @@ void CdmEngine::ComposeJsonRequest( // performs base64 encoding for message std::vector message_vector(message.begin(), message.end()); std::string message_b64 = Base64SafeEncode(message_vector); - LOGD("b64 serialized req:\r\n%s", message_b64.data()); // performs base64 encoding for signature std::vector signature_vector(signature.begin(), signature.end()); std::string signature_b64 = Base64SafeEncode(signature_vector); - LOGD("b64 signature:\r\n%s", signature_b64.data()); - // TODO(edwinwong): write a function to escape JSON output request->assign("{'signedRequest':{'message':'"); request->append(message_b64); request->append("','signature':'"); request->append(signature_b64); request->append("'}}"); - LOGD("json str:\r\n%s", request->c_str()); - } /* @@ -419,7 +380,12 @@ CdmResponseType CdmEngine::GetProvisioningRequest( CdmProvisioningRequest* request, std::string* default_url) { if (!request || !default_url) { - LOGE("CdmEngine::GetProvisioningRequest: invalid input parameters"); + LOGE("GetProvisioningRequest: invalid input parameters"); + return UNKNOWN_ERROR; + } + + if (provisioning_session_) { + LOGE("GetProvisioningRequest: duplicate provisioning request?"); return UNKNOWN_ERROR; } @@ -430,19 +396,19 @@ CdmResponseType CdmEngine::GetProvisioningRequest( // CdmSession* cdm_session = new CdmSession(); if (!cdm_session) { - LOGE("CdmEngine::GetProvisioningRequest: fails to create a cdm session"); + LOGE("GetProvisioningRequest: fails to create a cdm session"); return UNKNOWN_ERROR; } if (cdm_session->session_id().empty()) { - LOGE("CdmEngine::GetProvisioningRequest: fails to generate session ID"); + LOGE("GetProvisioningRequest: fails to generate session ID"); delete cdm_session; return UNKNOWN_ERROR; } CryptoEngine* crypto_engine = CryptoEngine::GetInstance(); if (!crypto_engine) { - LOGE("CdmEngine::GetProvisioningRequest: fails to create a crypto engine"); + LOGE("GetProvisioningRequest: fails to create a crypto engine"); delete cdm_session; return UNKNOWN_ERROR; } @@ -450,11 +416,13 @@ CdmResponseType CdmEngine::GetProvisioningRequest( CdmSessionId cdm_session_id = cdm_session->session_id(); CryptoSession* crypto_session = crypto_engine->CreateSession(cdm_session_id); if (!crypto_session) { - LOGE("CdmEngine::GetProvisioningRequest: fails to create a crypto session"); + LOGE("GetProvisioningRequest: fails to create a crypto session"); delete cdm_session; return UNKNOWN_ERROR; } + provisioning_session_ = cdm_session; + // //--------------------------------------------------------------------------- // Prepares device provisioning request. @@ -464,20 +432,24 @@ CdmResponseType CdmEngine::GetProvisioningRequest( client_id->set_type(ClientIdentification::KEYBOX); std::string token; if (!crypto_engine->GetToken(&token)) { - LOGE("CdmEngine::GetProvisioningRequest: fails to get token"); - CleanupProvisioingSessions(cdm_session, crypto_engine, cdm_session_id); + LOGE("GetProvisioningRequest: fails to get token"); + CleanupProvisioningSession(cdm_session_id); return UNKNOWN_ERROR; } client_id->set_token(token); uint32_t nonce; if (!crypto_session->GenerateNonce(&nonce)) { - LOGE("CdmEngine::GetProvisioningRequest: fails to generate a nonce"); + LOGE("GetProvisioningRequest: fails to generate a nonce"); crypto_engine->DestroySession(cdm_session_id); - CleanupProvisioingSessions(cdm_session, crypto_engine, cdm_session_id); + CleanupProvisioningSession(cdm_session_id); return UNKNOWN_ERROR; } - provisioning_request.set_nonce(UintToString(nonce)); + + // The provisioning server does not convert the nonce to uint32_t, it just + // passes the binary data to the response message. + std::string the_nonce(reinterpret_cast(&nonce), sizeof(nonce)); + provisioning_request.set_nonce(the_nonce); // Serializes the provisioning request. std::string serialized_request; @@ -487,27 +459,23 @@ CdmResponseType CdmEngine::GetProvisioningRequest( std::string request_signature; if (!crypto_session->PrepareRequest(serialized_request, &request_signature)) { request->clear(); - CleanupProvisioingSessions(cdm_session, crypto_engine, cdm_session_id); + CleanupProvisioningSession(cdm_session_id); return UNKNOWN_ERROR; } if (request_signature.empty()) { request->clear(); - CleanupProvisioingSessions(cdm_session, crypto_engine, cdm_session_id); + CleanupProvisioningSession(cdm_session_id); return UNKNOWN_ERROR; } // converts request into JSON string ComposeJsonRequest(serialized_request, request_signature, request); - // TODO(edwinwong): returns default provisioning server url - default_url->clear(); + static const std::string kDefaultProvisioningServerUrl = + "http://www-googleapis-test.sandbox.google.com/certificateprovisioning/v1/devicecertificates/create"; + default_url->assign(kDefaultProvisioningServerUrl); - // - //--------------------------------------------------------------------------- - // Closes the cdm session. - // - CleanupProvisioingSessions(cdm_session, crypto_engine, cdm_session_id); return NO_ERROR; } @@ -537,12 +505,6 @@ bool CdmEngine::ParseJsonResponse( size_t b64_string_size = end - start - start_substr.length(); b64_string.assign(json_str, start + start_substr.length(), b64_string_size); - - // Due to the size of the message, debug string cannot dump out the - // entire string. Dump the beginning and end to verify instead. - LOGD("size=%u, b64_string start=%s, end=%s", b64_string.length(), - b64_string.substr(0, 16).c_str(), - b64_string.substr(b64_string_size - 16).c_str()); } // Decodes base64 substring and returns it in *result @@ -587,32 +549,23 @@ CdmResponseType CdmEngine::HandleProvisioningResponse( // //--------------------------------------------------------------------------- - // First creates a cdm session, then creates a crypto session. + // Creates a crypto session using provisioning_session_. // - CdmSession* cdm_session = new CdmSession(); - if (!cdm_session) { - LOGE("CdmEngine::HandleProvisioningResponse: fails to create a cdm session"); - return UNKNOWN_ERROR; - } - - if (cdm_session->session_id().empty()) { - LOGE("CdmEngine::HandleProvisioningResponse: fails to generate session ID"); - delete cdm_session; + if (!provisioning_session_) { + LOGE("HandleProvisioningResponse: invalid provisioning session"); return UNKNOWN_ERROR; } CryptoEngine* crypto_engine = CryptoEngine::GetInstance(); if (!crypto_engine) { - LOGE("CdmEngine::HandleProvisioningResponse: fails to create a crypto engine"); - delete cdm_session; + LOGE("HandleProvisioningResponse: fails to create a crypto engine"); return UNKNOWN_ERROR; } - CdmSessionId cdm_session_id = cdm_session->session_id(); - CryptoSession* crypto_session = crypto_engine->CreateSession(cdm_session_id); + CdmSessionId cdm_session_id = provisioning_session_->session_id(); + CryptoSession* crypto_session = crypto_engine->FindSession(cdm_session_id); if (!crypto_session) { - LOGE("CdmEngine::HandleProvisioningResponse: fails to create a crypto session"); - delete cdm_session; + LOGE("HandleProvisioningResponse: fails to find %s", cdm_session_id.c_str()); return UNKNOWN_ERROR; } @@ -621,36 +574,50 @@ CdmResponseType CdmEngine::HandleProvisioningResponse( // the provisioing request's input). Validate provisioning response and // stores private device RSA key and certificate. ProvisioningResponse provisioning_response; + if (!provisioning_response.ParseFromString(signed_message)) { - LOGE("CdmEngine::HandleProvisioningResponse: fails to parse signed message"); - CleanupProvisioingSessions(cdm_session, crypto_engine, cdm_session_id); - return UNKNOWN_ERROR; - } - const std::string& enc_rsa_key = provisioning_response.device_rsa_key(); - const std::string& enc_rsa_key_iv = provisioning_response.device_rsa_key_iv(); - uint32_t nonce = strtoul(provisioning_response.nonce().data(), NULL, 10); - std::vector wrapped_rsa_key; - size_t wrapped_rsa_key_length = 0; - if (!crypto_session->RewrapDeviceRSAKey(response, - &nonce, - reinterpret_cast(enc_rsa_key.data()), - enc_rsa_key.length(), - reinterpret_cast(enc_rsa_key_iv.data()), - &wrapped_rsa_key[0], - &wrapped_rsa_key_length)) { - LOGE("CdmEngine::HandleProvisioningResponse: RewrapDeviceRSAKey fails"); - CleanupProvisioingSessions(cdm_session, crypto_engine, cdm_session_id); + LOGE("HandleProvisioningResponse: fails to parse signed message"); + CleanupProvisioningSession(cdm_session_id); + return UNKNOWN_ERROR; + } + + if (!provisioning_response.has_device_rsa_key()) { + LOGE("HandleProvisioningResponse: invalid response - key not found"); + CleanupProvisioningSession(cdm_session_id); + return UNKNOWN_ERROR; + } + + const std::string& enc_rsa_key = provisioning_response.device_rsa_key(); + const std::string& rsa_key_iv = provisioning_response.device_rsa_key_iv(); + const std::string& nonce = provisioning_response.nonce(); + + const int kRsaKeySize = 256; + size_t wrapped_rsa_key_length = kRsaKeySize + enc_rsa_key.length(); + std::vector wrapped_rsa_key; + wrapped_rsa_key.resize(wrapped_rsa_key_length); + + if (!crypto_session->RewrapDeviceRSAKey(signed_message, + signature, + nonce.data(), + enc_rsa_key, + enc_rsa_key.size(), + rsa_key_iv, + &wrapped_rsa_key[0], + &wrapped_rsa_key_length)) { + LOGE("HandleProvisioningResponse: RewrapDeviceRSAKey fails"); + CleanupProvisioningSession(cdm_session_id); return UNKNOWN_ERROR; } - // TODO(edwinwong): stores private device RSA key and cert const std::string& device_certificate = provisioning_response.device_certificate(); + std::string the_wrapped_rsa_key(wrapped_rsa_key.begin(), wrapped_rsa_key.end()); + DeviceFiles::StoreCertificate(device_certificate, the_wrapped_rsa_key); // //--------------------------------------------------------------------------- // Closes the cdm session. // - CleanupProvisioingSessions(cdm_session, crypto_engine, cdm_session_id); + CleanupProvisioningSession(cdm_session_id); return NO_ERROR; } diff --git a/libwvdrmengine/cdm/core/src/cdm_session.cpp b/libwvdrmengine/cdm/core/src/cdm_session.cpp index 75dfcdc9..75ec40c8 100644 --- a/libwvdrmengine/cdm/core/src/cdm_session.cpp +++ b/libwvdrmengine/cdm/core/src/cdm_session.cpp @@ -8,6 +8,7 @@ #include "clock.h" #include "crypto_engine.h" +#include "device_files.h" #include "log.h" #include "properties.h" #include "string_conversions.h" @@ -64,7 +65,8 @@ CdmResponseType CdmSession::GenerateKeyRequest( const CdmInitData& pssh_data, const CdmLicenseType license_type, CdmAppParameterMap& app_parameters, - CdmKeyMessage* key_request) { + CdmKeyMessage* key_request, + std::string* server_url) { if (!crypto_session_) { LOGW("CdmSession::GenerateKeyRequest: Invalid crypto session"); return UNKNOWN_ERROR; @@ -77,7 +79,8 @@ CdmResponseType CdmSession::GenerateKeyRequest( if (license_received_) { return Properties::require_explicit_renew_request() ? - UNKNOWN_ERROR : GenerateRenewalRequest(key_request); + UNKNOWN_ERROR : GenerateRenewalRequest(key_request, + server_url); } else { if (Properties::use_certificates_as_identification()) { @@ -88,7 +91,8 @@ CdmResponseType CdmSession::GenerateKeyRequest( if (!license_parser_.PrepareKeyRequest(pssh_data, license_type, app_parameters, - key_request)) { + key_request, + server_url)) { return KEY_ERROR; } else { return KEY_MESSAGE; @@ -178,8 +182,10 @@ CdmResponseType CdmSession::Decrypt(bool is_encrypted, // License renewal // GenerateRenewalRequest() - Construct valid renewal request for the current // session keys. -CdmResponseType CdmSession::GenerateRenewalRequest(CdmKeyMessage* key_request) { - if (!license_parser_.PrepareKeyRenewalRequest(key_request)) { +CdmResponseType CdmSession::GenerateRenewalRequest(CdmKeyMessage* key_request, + std::string* server_url) { + if (!license_parser_.PrepareKeyRenewalRequest(key_request, + server_url)) { return KEY_ERROR; } else { return KEY_MESSAGE; @@ -207,8 +213,8 @@ CdmSessionId CdmSession::GenerateSessionId() { bool CdmSession::LoadDeviceCertificate(std::string* certificate, std::string* wrapped_key) { - // TODO(edwingwong,rfrias): Need to read in the private key - return false; + return DeviceFiles::RetrieveCertificate(certificate, + wrapped_key); } bool CdmSession::AttachEventListener(WvCdmEventListener* listener) { diff --git a/libwvdrmengine/cdm/core/src/crypto_session.cpp b/libwvdrmengine/cdm/core/src/crypto_session.cpp index 18c4e2b9..7cd0db64 100755 --- a/libwvdrmengine/cdm/core/src/crypto_session.cpp +++ b/libwvdrmengine/cdm/core/src/crypto_session.cpp @@ -16,6 +16,7 @@ #include "wv_cdm_constants.h" namespace { + const uint32_t kMaxSignatureBufLength = 256; // Encode unsigned integer into a big endian formatted string std::string EncodeUint32(unsigned int u) { std::string s; @@ -357,8 +358,8 @@ bool CryptoSession::GenerateDerivedKeys(const std::string& message, bool CryptoSession::GenerateSignature(const std::string& message, std::string* signature) { LOGV("GenerateSignature: id=%ld", (uint32_t) oec_session_id_); - uint8_t signature_buf[32]; - uint32_t length = 32; + uint8_t signature_buf[kMaxSignatureBufLength]; + size_t length = kMaxSignatureBufLength; OEMCryptoResult sts; if (Properties::use_certificates_as_identification()) { sts = OEMCrypto_GenerateRSASignature( @@ -475,10 +476,11 @@ bool CryptoSession::SetDestinationBufferType() { } bool CryptoSession::RewrapDeviceRSAKey(const std::string& message, - const uint32_t* nonce, - const uint8_t* enc_rsa_key, + const std::string& signature, + const std::string& nonce, + const std::string& enc_rsa_key, size_t enc_rsa_key_length, - const uint8_t* enc_rsa_key_iv, + const std::string& rsa_key_iv, uint8_t* wrapped_rsa_key, size_t* wrapped_rsa_key_length) { LOGV("CryptoSession::RewrapDeviceRSAKey: Lock+++"); @@ -487,31 +489,28 @@ bool CryptoSession::RewrapDeviceRSAKey(const std::string& message, LOGV("crypto session id=%ld", static_cast(oec_session_id_)); - // HMAC-SHA256 signature - uint8_t signature[kSignatureSize]; - size_t signature_length = kSignatureSize; - OEMCryptoResult status = OEMCrypto_GenerateSignature( - oec_session_id_, - reinterpret_cast(message.data()), - message.size(), - signature, - &signature_length); - if (OEMCrypto_SUCCESS != status) { - LOGE("CryptoSession::RewrapDeviceRSAKey: GenerateSiganture failed"); - return false; + const uint8_t* signed_msg = reinterpret_cast(message.data()); + const uint8_t* msg_rsa_key = NULL; + const uint8_t* msg_rsa_key_iv = NULL; + const uint32_t* msg_nonce = NULL; + if (enc_rsa_key.size() >= MAC_KEY_SIZE && rsa_key_iv.size() >= KEY_IV_SIZE) { + msg_rsa_key = signed_msg + GetOffset(message, enc_rsa_key); + msg_rsa_key_iv = signed_msg + GetOffset(message, rsa_key_iv); + msg_nonce = reinterpret_cast(signed_msg + GetOffset(message, nonce)); } - status = OEMCrypto_RewrapDeviceRSAKey( + OEMCryptoResult status = OEMCrypto_RewrapDeviceRSAKey( oec_session_id_, - reinterpret_cast(message.data()), message.length(), - signature, signature_length, - nonce, - enc_rsa_key, enc_rsa_key_length, - enc_rsa_key_iv, + signed_msg, message.size(), + reinterpret_cast(signature.data()), signature.size(), + msg_nonce, + msg_rsa_key, enc_rsa_key_length, + msg_rsa_key_iv, wrapped_rsa_key, wrapped_rsa_key_length); if (OEMCrypto_SUCCESS != status) { + LOGE("OEMCrypto_RewrapDeviceRSAKey fails with %d", status); return false; } return true; diff --git a/libwvdrmengine/cdm/core/src/device_files.cpp b/libwvdrmengine/cdm/core/src/device_files.cpp new file mode 100644 index 00000000..9791e604 --- /dev/null +++ b/libwvdrmengine/cdm/core/src/device_files.cpp @@ -0,0 +1,196 @@ +// Copyright 2013 Google Inc. All Rights Reserved. + +#include "device_files.h" + +#include +#include +#include + +#include "device_files.pb.h" +#include "file_store.h" +#include "log.h" +#include "openssl/sha.h" + +namespace wvcdm { + +// TODO(rfrias): Make this work for non-unix paths +const char* DeviceFiles::kBasePath = "/data/drm"; +const char* DeviceFiles::kIdmPath = "/data/drm/IDM"; +const char* DeviceFiles::kCencPath = "/data/drm/IDM/CENC"; +const char* DeviceFiles::kDeviceCertificateFileName = "cert.bin"; + +// Protobuf generated classes. +using video_widevine_client::sdk::DeviceCertificate; +using video_widevine_client::sdk::HashedFile; + +bool DeviceFiles::StoreCertificate(const std::string& certificate, + const std::string& wrapped_private_key) { + // Fill in file information + video_widevine_client::sdk::File file; + + file.set_type(video_widevine_client::sdk::File::DEVICE_CERTIFICATE); + file.set_version(video_widevine_client::sdk::File::VERSION_1); + + DeviceCertificate *device_certificate = file.mutable_device_certificate(); + device_certificate->set_certificate(certificate); + device_certificate->set_wrapped_private_key(wrapped_private_key); + + std::string serialized_string; + file.SerializeToString(&serialized_string); + + // calculate SHA hash + std::string hash; + if (!Hash(serialized_string, &hash)) { + LOGW("DeviceFiles::StoreCertificate: Hash computation failed"); + return false; + } + + // File in hashed file data + HashedFile hashed_file; + hashed_file.set_file(serialized_string); + hashed_file.set_hash(hash); + + hashed_file.SerializeToString(&serialized_string); + + return StoreFile(kDeviceCertificateFileName, serialized_string); +} + +bool DeviceFiles::RetrieveCertificate(std::string* certificate, + std::string* wrapped_private_key) { + + std::string serialized_hashed_file; + if (!RetrieveFile(kDeviceCertificateFileName, &serialized_hashed_file)) + return false; + + HashedFile hashed_file; + if (!hashed_file.ParseFromString(serialized_hashed_file)) { + LOGW("DeviceFiles::RetrieveCertificate: Unable to parse hash file"); + return false; + } + + std::string hash; + if (!Hash(hashed_file.file(), &hash)) { + LOGW("DeviceFiles::RetrieveCertificate: Hash computation failed"); + return false; + } + + if (hash.compare(hashed_file.hash())) { + LOGW("DeviceFiles::RetrieveCertificate: Hash mismatch"); + return false; + } + + video_widevine_client::sdk::File file; + if (!file.ParseFromString(hashed_file.file())) { + LOGW("DeviceFiles::RetrieveCertificate: Unable to parse file"); + return false; + } + + if (file.type() != video_widevine_client::sdk::File::DEVICE_CERTIFICATE) { + LOGW("DeviceFiles::RetrieveCertificate: Incorrect file type"); + return false; + } + + if (file.version() != video_widevine_client::sdk::File::VERSION_1) { + LOGW("DeviceFiles::RetrieveCertificate: Incorrect file version"); + return false; + } + + if (!file.has_device_certificate()) { + LOGW("DeviceFiles::RetrieveCertificate: Certificate not present"); + return false; + } + + DeviceCertificate device_certificate = file.device_certificate(); + + *certificate = device_certificate.certificate(); + *wrapped_private_key = device_certificate.wrapped_private_key(); + return true; +} + +bool DeviceFiles::Hash(const std::string& data, std::string* hash) { + if (!hash) + return false; + + hash->resize(SHA256_DIGEST_LENGTH); + SHA256_CTX sha256; + SHA256_Init(&sha256); + SHA256_Update(&sha256, data.data(), data.size()); + SHA256_Final(reinterpret_cast(const_cast(hash->data())), &sha256); + return true; +} + +bool DeviceFiles::StoreFile(const char* name, const std::string& data) { + if (!name) + return false; + + if (!File::IsDirectory(kCencPath)) { + if (!File::CreateDirectory(kCencPath)) + return false; + } + + std::string path = GetPath(kCencPath, name); + + File file(path, File::kCreate | File::kTruncate | File::kBinary); + if (file.IsBad()) { + LOGW("DeviceFiles::StoreFile: File open failed: %s", path.c_str()); + return false; + } + + ssize_t bytes = file.Write(data.data(), data.size()); + + file.Close(); + + if (bytes != static_cast(data.size())) { + LOGW("DeviceFiles::StoreFile: write failed: %d %d", data.size(), bytes); + return false; + } + + return true; +} + +bool DeviceFiles::RetrieveFile(const char* name, std::string* data) { + if (!data) + return false; + + std::string path = GetPath(kCencPath, name); + + if (!File::Exists(path)) { + LOGW("DeviceFiles::RetrieveFile: %s does not exist", path.c_str()); + return false; + } + + ssize_t bytes = File::FileSize(path); + + if (bytes <= 0) { + LOGW("DeviceFiles::RetrieveFile: File size invalid: %d", path.c_str()); + return false; + } + + File file(path, File::kReadOnly | File::kBinary); + if (file.IsBad()) { + LOGW("DeviceFiles::RetrieveFile: File open failed: %s", path.c_str()); + return false; + } + + data->resize(bytes); + + bytes = file.Read(reinterpret_cast(const_cast(data->data())), + data->size()); + + if (bytes != static_cast(data->size())) { + LOGW("DeviceFiles::StoreFile: write failed: %d %d", data->size(), bytes); + return false; + } + + return true; +} + +std::string DeviceFiles::GetPath(const char* dir, const char * filename) { + // TODO(rfrias): Make this work for non-unix paths + std::string path = dir; + path += "/"; + path += filename; + return path; +} + +} diff --git a/libwvdrmengine/cdm/core/src/device_files.proto b/libwvdrmengine/cdm/core/src/device_files.proto new file mode 100644 index 00000000..339ed9c1 --- /dev/null +++ b/libwvdrmengine/cdm/core/src/device_files.proto @@ -0,0 +1,47 @@ +// ---------------------------------------------------------------------------- +// device_files.proto +// ---------------------------------------------------------------------------- +// Copyright 2013 Google Inc. All Rights Reserved. +// +// Description: +// Format of various files stored at the device. +// +syntax = "proto2"; + +package video_widevine_client.sdk; + +// need this if we are using libprotobuf-cpp-2.3.0-lite +option optimize_for = LITE_RUNTIME; + +message DeviceCertificate { + optional bytes certificate = 1; + optional bytes wrapped_private_key = 2; +} + +message License { + optional bytes key_set_id = 1; + optional bytes pssh_data = 2; + optional bytes license_request = 3; + optional bytes license = 4; +} + +message File { + enum FileType { + DEVICE_CERTIFICATE = 1; + LICENSE = 2; + } + + enum FileVersion { + VERSION_1 = 1; + } + + optional FileType type = 1; + optional FileVersion version = 2 [default = VERSION_1]; + optional DeviceCertificate device_certificate = 3; + repeated License licenses = 4; +} + +message HashedFile { + optional bytes file = 1; + optional bytes hash = 2; +} diff --git a/libwvdrmengine/cdm/core/src/license.cpp b/libwvdrmengine/cdm/core/src/license.cpp index 80cd892a..0ed97025 100644 --- a/libwvdrmengine/cdm/core/src/license.cpp +++ b/libwvdrmengine/cdm/core/src/license.cpp @@ -91,7 +91,8 @@ bool CdmLicense::Init(const std::string& token, bool CdmLicense::PrepareKeyRequest(const CdmInitData& init_data, const CdmLicenseType license_type, CdmAppParameterMap& app_parameters, - CdmKeyMessage* signed_request) { + CdmKeyMessage* signed_request, + std::string* server_url) { if (!session_ || token_.empty()) { return false; @@ -104,6 +105,10 @@ bool CdmLicense::PrepareKeyRequest(const CdmInitData& init_data, LOGE("CdmLicense::PrepareKeyRequest : No signed request provided."); return false; } + if (!server_url) { + LOGE("CdmLicense::PrepareKeyRequest : No server url provided."); + return false; + } // TODO(gmorgan): Request ID owned by session? std::string request_id; @@ -191,6 +196,9 @@ bool CdmLicense::PrepareKeyRequest(const CdmInitData& init_data, std::string serialized_license_req; license_request.SerializeToString(&serialized_license_req); + if (Properties::use_certificates_as_identification()) + key_request_ = serialized_license_req; + // Derive signing and encryption keys and construct signature. std::string license_request_signature; if (!session_->PrepareRequest(serialized_license_req, @@ -212,13 +220,12 @@ bool CdmLicense::PrepareKeyRequest(const CdmInitData& init_data, signed_message.SerializeToString(signed_request); - if (Properties::use_certificates_as_identification()) - key_request_ = *signed_request; - + *server_url = server_url_; return true; } -bool CdmLicense::PrepareKeyRenewalRequest(CdmKeyMessage* signed_request) { +bool CdmLicense::PrepareKeyRenewalRequest(CdmKeyMessage* signed_request, + std::string* server_url) { if (!session_) { return false; } @@ -226,6 +233,10 @@ bool CdmLicense::PrepareKeyRenewalRequest(CdmKeyMessage* signed_request) { LOGE("CdmLicense::PrepareKeyRenewalRequest : No signed request provided."); return false; } + if (!server_url) { + LOGE("CdmLicense::PrepareKeyRenewalRequest : No server url provided."); + return false; + } LicenseRequest license_request; license_request.set_type(LicenseRequest::RENEWAL); @@ -263,7 +274,7 @@ bool CdmLicense::PrepareKeyRenewalRequest(CdmKeyMessage* signed_request) { signed_message.set_msg(serialized_license_req); signed_message.SerializeToString(signed_request); - + *server_url = server_url_; return true; } @@ -332,6 +343,10 @@ CdmResponseType CdmLicense::HandleKeyResponse( return KEY_ERROR; } + if (license.policy().has_renewal_server_url()) { + server_url_ = license.policy().renewal_server_url(); + } + policy_engine_->SetLicense(license); if (session_->LoadKeys(signed_response.msg(), @@ -378,6 +393,11 @@ CdmResponseType CdmLicense::HandleKeyRenewalResponse( // This is the normal case. license_id_.CopyFrom(license.id()); + if (license.policy().has_renewal_server_url() && + license.policy().renewal_server_url().size() > 0) { + server_url_ = license.policy().renewal_server_url(); + } + policy_engine_->UpdateLicense(license); std::vector key_array = ExtractContentKeys(license); diff --git a/libwvdrmengine/cdm/core/src/string_conversions.cpp b/libwvdrmengine/cdm/core/src/string_conversions.cpp index 638b4e6f..8afeda3f 100644 --- a/libwvdrmengine/cdm/core/src/string_conversions.cpp +++ b/libwvdrmengine/cdm/core/src/string_conversions.cpp @@ -1,4 +1,4 @@ -// Copyright 2012 Google Inc. All Rights Reserved. +// Copyright 2013 Google Inc. All Rights Reserved. #include "string_conversions.h" @@ -12,14 +12,41 @@ #include "log.h" namespace { -// Helper for Base64SafeDecode() -char B64ToBin(char inch) { - if (inch >= 'A' && inch <= 'Z') return inch - 'A'; - if (inch >= 'a' && inch <= 'z') return inch - 'a' + 26; - if (inch >= '0' && inch <= '9') return inch - '0' + 52; - if (inch == '-') return 62; - // if (inch == '_') - return 63; +/* + * Returns a 8-bit char that is mapped to the 6-bit base64 in_ch. + * + * Extracted from http://www.ietf.org/rfc/rfc3548.txt. + * + The "URL and Filename safe" Base 64 Alphabet + + Value Encoding Value Encoding Value Encoding Value Encoding + 0 A 17 R 34 i 51 z + 1 B 18 S 35 j 52 0 + 2 C 19 T 36 k 53 1 + 3 D 20 U 37 l 54 2 + 4 E 21 V 38 m 55 3 + 5 F 22 W 39 n 56 4 + 6 G 23 X 40 o 57 5 + 7 H 24 Y 41 p 58 6 + 8 I 25 Z 42 q 59 7 + 9 J 26 a 43 r 60 8 + 10 K 27 b 44 s 61 9 + 11 L 28 c 45 t 62 - (minus) + 12 M 29 d 46 u 63 _ + 13 N 30 e 47 v (underline) + 14 O 31 f 48 w + 15 P 32 g 49 x + 16 Q 33 h 50 y (pad) = + */ +char B64ToBin(char in_ch) { + if (in_ch >= 'A' && in_ch <= 'Z') return in_ch - 'A'; + if (in_ch >= 'a' && in_ch <= 'z') return in_ch - 'a' + 26; + if (in_ch >= '0' && in_ch <= '9') return in_ch - '0' + 52; + if (in_ch == '-') return 62; + if (in_ch == '_') return 63; + + // arbitrary delimiter not in Base64 encoded alphabet, do not pick 0 + return '?'; } } @@ -75,10 +102,13 @@ std::string b2a_hex(const std::string& byte) { byte.length()); } -// Filename-friendly base64 encoding (RFC4648). +// Filename-friendly base64 encoding (RFC4648), commonly referred as +// Base64WebSafeEncode. // This is the encoding required by GooglePlay for certain // license server transactions. It is also used for logging // certain strings. +// The difference between web safe encoding vs regular encoding is that +// the web safe version replaces '+' with '-' and '/' with '_'. std::string Base64SafeEncode(const std::vector& bin_input) { static const char kBase64Chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" @@ -87,9 +117,13 @@ std::string Base64SafeEncode(const std::vector& bin_input) { if (bin_input.empty()) { return std::string(); } + int in_size = bin_input.size(); - int rup = ((in_size % 3) != 0) ? 1 : 0; - int out_size = ((in_size * 4) / 3) + rup; + int final_quantum_in_bytes = in_size % 3; + int full_in_chunks = in_size / 3; + int out_size = full_in_chunks * 4; + if (final_quantum_in_bytes) out_size += 4; + std::string b64_output(out_size, '\0'); int in_index = 0; int out_index = 0; @@ -98,7 +132,7 @@ std::string Base64SafeEncode(const std::vector& bin_input) { static const unsigned long kInMask = 0xff; static const unsigned long kOutMask = 0x3f; - while (in_index < in_size) { + for (int i = 0; i < full_in_chunks; ++i) { // up to 3 bytes (0..255) in buffer = (bin_input.at(in_index) & kInMask); buffer <<= 8; @@ -106,6 +140,7 @@ std::string Base64SafeEncode(const std::vector& bin_input) { buffer <<= 8; buffer |= (++in_index >= in_size) ? 0 : (bin_input.at(in_index) & kInMask); ++in_index; + // up to 4 bytes (0..63) out out_cc = (buffer >> 18) & kOutMask; b64_output.at(out_index) = kBase64Chars[out_cc]; @@ -123,10 +158,49 @@ std::string Base64SafeEncode(const std::vector& bin_input) { b64_output.at(out_index) = kBase64Chars[out_cc]; ++out_index; } + + if (final_quantum_in_bytes) { + switch(final_quantum_in_bytes) { + case 1: { + // reads 24-bits data, which is made up of one 8-bits char + buffer = (bin_input.at(in_index++) & kInMask); + buffer <<= 16; + + // writes two 6-bits chars followed by two '=' padding char + out_cc = (buffer >> 18) & kOutMask; + b64_output.at(out_index++) = kBase64Chars[out_cc]; + out_cc = (buffer >> 12) & kOutMask; + b64_output.at(out_index++) = kBase64Chars[out_cc]; + b64_output.at(out_index++) = '='; + b64_output.at(out_index) = '='; + break; + } + case 2: { + // reads 24-bits data, which is made up of two 8-bits chars + buffer = (bin_input.at(in_index++) & kInMask); + buffer <<= 8; + buffer |= (bin_input.at(in_index++) & kInMask); + buffer <<= 8; + + // writes three 6-bits chars followed by one '=' padding char + out_cc = (buffer >> 18) & kOutMask; + b64_output.at(out_index++) = kBase64Chars[out_cc]; + out_cc = (buffer >> 12) & kOutMask; + b64_output.at(out_index++) = kBase64Chars[out_cc]; + out_cc = (buffer >> 6) & kOutMask; + b64_output.at(out_index++) = kBase64Chars[out_cc]; + b64_output.at(out_index) = '='; + break; + } + default: + break; + } + } return b64_output; } -// Decode for Filename-friendly base64 encoding (RFC4648). +// Decode for Filename-friendly base64 encoding (RFC4648), commonly referred +// as Base64WebSafeDecode. // This is the encoding required by GooglePlay for certain // license server transactions. It is also used for logging // certain strings. @@ -134,9 +208,9 @@ std::vector Base64SafeDecode(const std::string& b64_input) { if (b64_input.empty()) { return std::vector(); } + int in_size = b64_input.size(); - // out_size should be an integral number of bytes, assuming correct encode - int out_size = ((in_size * 3) / 4); + int out_size = in_size; std::vector bin_output(out_size, '\0'); int in_index = 0; int out_index = 0; @@ -144,7 +218,14 @@ std::vector Base64SafeDecode(const std::string& b64_input) { unsigned char out_cc; static const unsigned long kOutMask = 0xff; - while (in_index < in_size) { + int counter = 0; + size_t delimiter_pos = b64_input.rfind('='); + if (delimiter_pos != std::string::npos) { + // Special case for partial last quantum indicated by '=' + // at the end of encoded input. + counter = 1; + } + for (; counter < (in_size / 4); ++counter) { // up to 4 bytes (0..63) in buffer = B64ToBin(b64_input.at(in_index)); buffer <<= 6; @@ -167,6 +248,37 @@ std::vector Base64SafeDecode(const std::string& b64_input) { bin_output.at(out_index) = out_cc; ++out_index; } + + if (delimiter_pos != std::string::npos) { + // it is either 2 chars plus 2 '=' or 3 chars plus one '=' + buffer = B64ToBin(b64_input.at(in_index++)); + buffer <<= 6; + buffer |= B64ToBin(b64_input.at(in_index++)); + buffer <<= 6; + char special_char = b64_input.at(in_index++); + if ('=' == special_char) { + // we have 2 chars and 2 '=' + buffer <<= 6; + out_cc = (buffer >> 16) & kOutMask; + bin_output.at(out_index++) = out_cc; + out_cc = (buffer >> 8) & kOutMask; + bin_output.at(out_index) = out_cc; + } else { + // we have 3 chars and 1 '=' + buffer |= B64ToBin(special_char); + buffer <<= 6; + buffer |= B64ToBin(b64_input.at(in_index)); + out_cc = (buffer >> 16) & kOutMask; + bin_output.at(out_index++) = out_cc; + out_cc = (buffer >> 8) & kOutMask; + bin_output.at(out_index++) = out_cc; + out_cc = buffer & kOutMask; + bin_output.at(out_index) = out_cc; + } + } + + // adjust vector to reflect true size + bin_output.resize(out_index); return bin_output; } diff --git a/libwvdrmengine/cdm/core/test/base64_test.cpp b/libwvdrmengine/cdm/core/test/base64_test.cpp new file mode 100644 index 00000000..37e5dd02 --- /dev/null +++ b/libwvdrmengine/cdm/core/test/base64_test.cpp @@ -0,0 +1,193 @@ +// Copyright 2013 Google Inc. All Rights Reserved. + +#include "gtest/gtest.h" +#include "log.h" +#include "string_conversions.h" + +namespace { + std::string kMultipleOf24BitsData("Good day!"); + std::string kOneByteOverData("Hello Googler"); + std::string kTwoBytesOverData("Hello Googlers"); + std::string kMultipleOf24BitsB64Data("R29vZCBkYXkh"); + std::string kOneByteOverB64Data("SGVsbG8gR29vZ2xlcg=="); + std::string kTwoBytesOverB64Data("SGVsbG8gR29vZ2xlcnM="); + std::string kTestData = + "\030\361\\\366\267> \331\210\360\\-\311:\324\256\376" + "\261\234\241\326d\326\177\346\346\223\333Y\305\214\330"; + std::string kB64TestData = "GPFc9rc-INmI8FwtyTrUrv6xnKHWZNZ_5uaT21nFjNg="; + std::string kB64ShortString("r-LpoZcbbr2KtoPaFnuWTVBh4Gup1k8vn0ClW2qm32A="); + std::string kB64LongString = + "CrAJYTyIdLPiA2jBzMskbE_gFQj69wv23VlJ2e3MBKtK4nJwKyNYGyyluqKo" + "TP751tvoADf86iLrf73mEzF58eSlaOjCpJRf2R3dojbNeSTy3JICmCc8vKtMjZRX9QWTvJbq_cg" + "yMB8FQC8enuYhOaw1yJDYyCFHgik34NrUVUfmvaKKdSKQimqAZmjXi6P0znAn-XdPtz2xJVRxZp" + "NH3QCD1bGcH_O1ercBW2JwF9KNalKFsxQrBhIwvyx-q-Ah4vf4r3M2HzY6JTHvcYGGc7dJNA3Xe" + "WfCrYIvg0SGCP_z7Y2wICIA36VMwR3gnwNZlKkx6WGCCgsaU6IbLm4HpRBZfajuiOlasoYN4z1R" + "lQ14Z32fdaFy8xOqLl-ZukxjWa7wv9zOSveH6JcHap1FS3R-RZ7E5WhfjxSTS0nWWZgmAjS2PkP" + "9g4GPNsnpsrVymI39j6R6jPoc3__2EGN6qAvmp4pFKR7lQyslgNn2vYLuE0Ps5mIXVkxNiZOO3T" + "jxgZyHaHOm1KmAZKI0EfddMATJCTt-UeLG3haqS_pYaBWcQ_xzWhoEHWU7_6ZaWrWemV8CVCg6s" + "OB1SRI5MrkRBBSV0r8UKddLJGthZVjuTG75KK72KE9yhe86mCadvfVYe5keJ5GOC-t1EiFzBo4c" + "4oqwkOCkkmYX_BEuZ3pOWztFp1_Br2Tl_fziw4O2vNIPCXB9yEewV6PkYPziTue3x4vRqD_mYjm" + "1ia8fxISQnEC0vrqvrFFs9fLAHPlsvaRFnhv_XKpRwFoBdfqWTakb3k6uRz0Oh2SJ8euzFIyQNB" + "efesMWk45DSrQjnlwlKXwZSiDKjAss0W2WwIb9F_x5LdB1Aa-CBudLVdxf62ggYaNZ57qx3YeHA" + "jkqMGIF7Fq09D4OxM0jRsnrmXbJWKleUpJi7nHJgQGZk2ifN95gjuTNcRaGfYXMOsDoWdkrNAq0" + "LScsPB06xEUR0DcO9vWx0zAEK7gsxxHziR7ZaYiIIkPysRR92r2NoLFPOUXf8j8ait-51jZmPKn" + "bD6adieLy6ujSl907QsUgyGvokLs1OCsYHZr-X6vnyMjdk4G3QfmWwRepD_CMyXGvtLbTNCto7E" + "L_M2yPZveAwYWwNlBtWK21gwIU2dgY298z7_S6jaQBc29f25sREjvN793ttYsPaeyom08qHYDnb" + "jae3XX-2qqde6AGXlv__jO8WDZ5od6DWu2ThqV10ijVGFfGniRsSruzq0iq8zuAqTOGhmA9Dw7b" + "rNlI95P4LpJA5pbjmNdnX7CQa2oHUuojmwlXRYuOA28PNEf-sc7ZPmMyFzedJi4EpkqzeQspEdH" + "yNMf23iEjK6GOff7dgAaxg9vYHyprhkEml4BdmFVYwCYQy8o6KRcA0NgJb8c3tg4d3aRXWp6L-F" + "sVhwqvq6FLOunSTNRIqhr2mOjRpU5w4mx-9GJRtk4XEcKT9YgUHGOUjGwfhQ5gBQDyZZVTddIUb" + "MOThsSg7zr38oUCfgXeZaai3X2foKo1Bt94Q_q18dw5xNAN5e7rSwfilltHL23zbZduuhWkvp8S" + "dag_NbO2C4IRMkzbjQBmiO9ixjXRhdqHlRRWcfR0wbQvEhD47egRVfnhKZ0W9G2-FGhyGuwJCq4" + "CCAISEAfZ_94TqpXBImeAUzYhNr0Y48SbiwUijgIwggEKAoIBAQDRigR9nFm4mfBUh1Y3SGyOcF" + "E-yK2NtfDiQe9l70KtkOeH4sB6MMB8g1QKPbUE8SBjPvXVJC_2DAWKjALzk4Aw-K-VmYe_Ag9CH" + "JiS-XcfUYEGgK4jVMxadEq3LufEEREKUZnzjgQlR39dzgjFqIrC1bwfy3_99RsjPt6QpWPg36PI" + "O4UKlmwBDTFzSOJB-4IV8Opy5Zv84BqPuyO9P5e3bXj_shRfy_XAGG2HGP_PpOCZWEfxuce0Iyu" + "vpTPLQpTOgNw-VvUBGCWMZFoERopmqp_pQwWZ2a-EwlT_vvYY4SkuNjflBskR70xz4QzEo9665g" + "k6I-HbHrTv29KEiAllAgMBAAEomSASgAIkKz1CSdFJVKcpO56jW0vsjKp92_cdqXBSEY3nuhzug" + "_LFluMJx_IqATUcCOY-w6w0yKn2ezfZGE0MDIaCngEgQFI_DRoaSOBNNeirF59uYM0sK3P2eGS9" + "G6F0l-OUXJdSO0b_LO8AbAK9LA3j7UHaajupJI1mdc4VtJfPRTsml2vIeKhDWXWaSvmeHgfF_tp" + "-OV7oPuk6Ub26xpCp2He2rEAblCYEl25Zlz97K4DhyTOV5_xuSdSt-KbTLY9cWM5i9ncND1RzCc" + "4qOixKarnMM5DdpZhs3B5xVj3yBAM1mVxPD2sZnqHSEN2EK7BMlHEnnyxhX0MGE36TQZR7P-I-G" + "rUFCq8CCAESEDAxMjM0NTY3ODlBQkNERUYYspIEIo4CMIIBCgKCAQEApwA2YGXcvVRaKkC04RWU" + "WBFPlFjd3qcfPCzgiAkpYVdnXlZ-7iePWTSaKqqdtE76p2rUyXpTwU6f4zT3PbfJEEdPKNo_zjF" + "7_QYQ6_e-kvmv-z5o2u4aZEzzKfJznjnY9m_YsoCCcY61pPLCPs0KyrYEzZoTi1RzVCVUjL6Yem" + "et2rNOs_qCqEpnmFZXVHHNEn_towHAaoskA5aIvpdmKrxTyYMGUVqIZRMY5Drta_FhW0zIHvTCr" + "gheLV_4En-i_LshGDDa_kD7AcouNw7O3XaHgkYLOnePwHIHLH-dHoZb7Scp3wOXYu9E01s925xe" + "G3s5tAttBGu7uyxfz7N6BQIDAQABKNKF2MwEEoADe9NAqNAxHpU13bMgz8LPySZJU8hY1RLwcfT" + "UM47Xb3m-F-s2cfI7w08668f79kD45uRRzkVc8GbRIlVyzVC0WgIvtxEkYRKfgF_J7snUe2J2NN" + "1FrkK7H3oYhcfPyYZH_SPZJr5HPoBFQTmS5A4l24U1dzQ6Z7_q-oS6uT0DiagTnzWhEg6AEnIkT" + "sJtK3cZuKGYq3NDefZ7nslPuLXxdXl6SAEOtrk-RvCY6EBqYOuPUXgxXOEPbyM289R6aHQyPPYw" + "qs9Pt9_E4BuMqCsbf5H5mLms9FA-wRx6mK2IaOboT4tf9_YObp3hVeL3WyxzXncETzJdE1GPGlO" + "t_x5S_MylgJKbiWQYSdmqs3fzYExunw3wvI4tPHT_O8A_xKjyTEAvE5cBuCkfjwT716qUOzFUzF" + "gZYLHnFiQLZekZUbUUlWY_CwU9Cv0UtxqQ6Oa835_Ug8_n1BwX6BPbmbcWe2Y19laSnDWg4JBNl" + "F2CyP9N75jPtW9rVfjUSqKEPOwaIgwzNDkyMjM3NDcAAAA="; +} + +namespace wvcdm { + +class Base64Test : public testing::Test { + public: + Base64Test() {} + ~Base64Test() {} + +}; + +TEST_F(Base64Test, Base64MultipleOf24BitsTest) +{ + // encodes string + std::vector message_vector(kMultipleOf24BitsData.begin(), + kMultipleOf24BitsData.end()); + std::string message_b64 = Base64SafeEncode(message_vector); + + // decodes string + std::vector result_vector = Base64SafeDecode(message_b64); + std::string result; + result.assign(result_vector.begin(), result_vector.end()); + EXPECT_STREQ(kMultipleOf24BitsData.data(), result.data()); +} + +TEST_F(Base64Test, Base64OneByteOverTest) +{ + // encodes string + std::vector message_vector(kOneByteOverData.begin(), + kOneByteOverData.end()); + std::string message_b64 = Base64SafeEncode(message_vector); + + // decodes string + std::vector result_vector = Base64SafeDecode(message_b64); + std::string result; + result.assign(result_vector.begin(), result_vector.end()); + EXPECT_STREQ(kOneByteOverData.data(), result.data()); +} + +TEST_F(Base64Test, Base64TwoBytesOverTest) +{ + // encodes string + std::vector message_vector(kTwoBytesOverData.begin(), + kTwoBytesOverData.end()); + std::string message_b64 = Base64SafeEncode(message_vector); + + // decodes string + std::vector result_vector = Base64SafeDecode(message_b64); + std::string result; + result.assign(result_vector.begin(), result_vector.end()); + EXPECT_STREQ(kTwoBytesOverData.data(), result.data()); +} + +TEST_F(Base64Test, Base64EncodeTest) +{ + // encodes string + std::vector message_vector(kTestData.begin(), kTestData.end()); + std::string message_b64 = Base64SafeEncode(message_vector); + std::string result; + result.assign(message_b64.begin(), message_b64.end()); + EXPECT_STREQ(kB64TestData.data(), result.data()); + + // decodes string + std::vector result_vector = Base64SafeDecode(message_b64); + result.clear(); + result.assign(result_vector.begin(), result_vector.end()); + EXPECT_STREQ(kTestData.data(), result.data()); +} + +TEST_F(Base64Test, Base64MultipleOf24BitsDecodeTest) +{ + // decodes string + std::vector decoded_vector = Base64SafeDecode(kMultipleOf24BitsB64Data); + std::string result; + result.assign(decoded_vector.begin(), decoded_vector.end()); + EXPECT_STREQ(kMultipleOf24BitsData.data(), result.data()); + + // encodes string + std::string b64_string = Base64SafeEncode(decoded_vector); + EXPECT_STREQ(kMultipleOf24BitsB64Data.data(), b64_string.data()); +} + +TEST_F(Base64Test, Base64OneByteOverDecodeTest) +{ + // decodes string + std::vector decoded_vector = Base64SafeDecode(kOneByteOverB64Data); + std::string result; + result.assign(decoded_vector.begin(), decoded_vector.end()); + EXPECT_STREQ(kOneByteOverData.data(), result.data()); + + // encodes string + std::string b64_string = Base64SafeEncode(decoded_vector); + EXPECT_STREQ(kOneByteOverB64Data.data(), b64_string.data()); +} + +TEST_F(Base64Test, Base64TwoBytesOverDecodeTest) +{ + // decodes string + std::vector decoded_vector = Base64SafeDecode(kTwoBytesOverB64Data); + std::string result; + result.assign(decoded_vector.begin(), decoded_vector.end()); + EXPECT_STREQ(kTwoBytesOverData.data(), result.data()); + + // encodes string + std::string b64_string = Base64SafeEncode(decoded_vector); + EXPECT_STREQ(kTwoBytesOverB64Data.data(), b64_string.data()); +} + +TEST_F(Base64Test, Base64ShortDecodeTest) +{ + // decodes string + std::vector decoded_vector = Base64SafeDecode(kB64ShortString); + + // encodes string + std::string b64_string = Base64SafeEncode(decoded_vector); + EXPECT_STREQ(kB64ShortString.data(), b64_string.data()); +} + +TEST_F(Base64Test, Base64LongDecodeTest) +{ + // decodes string + std::vector decoded_vector = Base64SafeDecode(kB64LongString); + + // encodes string + std::string b64_string = Base64SafeEncode(decoded_vector); + EXPECT_STREQ(kB64LongString.data(), b64_string.data()); +} + +} // namespace wvcdm diff --git a/libwvdrmengine/cdm/core/test/cdm_engine_test.cpp b/libwvdrmengine/cdm/core/test/cdm_engine_test.cpp index 51855b76..f90b48e2 100644 --- a/libwvdrmengine/cdm/core/test/cdm_engine_test.cpp +++ b/libwvdrmengine/cdm/core/test/cdm_engine_test.cpp @@ -24,53 +24,55 @@ wvcdm::KeyId g_wrong_key_id; int g_use_full_path = 0; // cannot use boolean in getopt_long static wvcdm::CdmProvisioningResponse kJsonResponse = - "{\"signedResponse\": {" - "\"message\": \"CrAJYTyIdLPiA2jBzMskbE_gFQj69wv23VlJ2e3MBKtK4nJwKyNYGyyluqKo" - "TP751tvoADf86iLrf73mEzF58eSlaOjCpJRf2R3dojbNeSTy3JICmCc8vKtMjZRX9QWTvJbq_cg" - "yMB8FQC8enuYhOaw1yJDYyCFHgik34NrUVUfmvaKKdSKQimqAZmjXi6P0znAn-XdPtz2xJVRxZp" - "NH3QCD1bGcH_O1ercBW2JwF9KNalKFsxQrBhIwvyx-q-Ah4vf4r3M2HzY6JTHvcYGGc7dJNA3Xe" - "WfCrYIvg0SGCP_z7Y2wICIA36VMwR3gnwNZlKkx6WGCCgsaU6IbLm4HpRBZfajuiOlasoYN4z1R" - "lQ14Z32fdaFy8xOqLl-ZukxjWa7wv9zOSveH6JcHap1FS3R-RZ7E5WhfjxSTS0nWWZgmAjS2PkP" - "9g4GPNsnpsrVymI39j6R6jPoc3__2EGN6qAvmp4pFKR7lQyslgNn2vYLuE0Ps5mIXVkxNiZOO3T" - "jxgZyHaHOm1KmAZKI0EfddMATJCTt-UeLG3haqS_pYaBWcQ_xzWhoEHWU7_6ZaWrWemV8CVCg6s" - "OB1SRI5MrkRBBSV0r8UKddLJGthZVjuTG75KK72KE9yhe86mCadvfVYe5keJ5GOC-t1EiFzBo4c" - "4oqwkOCkkmYX_BEuZ3pOWztFp1_Br2Tl_fziw4O2vNIPCXB9yEewV6PkYPziTue3x4vRqD_mYjm" - "1ia8fxISQnEC0vrqvrFFs9fLAHPlsvaRFnhv_XKpRwFoBdfqWTakb3k6uRz0Oh2SJ8euzFIyQNB" - "efesMWk45DSrQjnlwlKXwZSiDKjAss0W2WwIb9F_x5LdB1Aa-CBudLVdxf62ggYaNZ57qx3YeHA" - "jkqMGIF7Fq09D4OxM0jRsnrmXbJWKleUpJi7nHJgQGZk2ifN95gjuTNcRaGfYXMOsDoWdkrNAq0" - "LScsPB06xEUR0DcO9vWx0zAEK7gsxxHziR7ZaYiIIkPysRR92r2NoLFPOUXf8j8ait-51jZmPKn" - "bD6adieLy6ujSl907QsUgyGvokLs1OCsYHZr-X6vnyMjdk4G3QfmWwRepD_CMyXGvtLbTNCto7E" - "L_M2yPZveAwYWwNlBtWK21gwIU2dgY298z7_S6jaQBc29f25sREjvN793ttYsPaeyom08qHYDnb" - "jae3XX-2qqde6AGXlv__jO8WDZ5od6DWu2ThqV10ijVGFfGniRsSruzq0iq8zuAqTOGhmA9Dw7b" - "rNlI95P4LpJA5pbjmNdnX7CQa2oHUuojmwlXRYuOA28PNEf-sc7ZPmMyFzedJi4EpkqzeQspEdH" - "yNMf23iEjK6GOff7dgAaxg9vYHyprhkEml4BdmFVYwCYQy8o6KRcA0NgJb8c3tg4d3aRXWp6L-F" - "sVhwqvq6FLOunSTNRIqhr2mOjRpU5w4mx-9GJRtk4XEcKT9YgUHGOUjGwfhQ5gBQDyZZVTddIUb" - "MOThsSg7zr38oUCfgXeZaai3X2foKo1Bt94Q_q18dw5xNAN5e7rSwfilltHL23zbZduuhWkvp8S" - "dag_NbO2C4IRMkzbjQBmiO9ixjXRhdqHlRRWcfR0wbQvEhD47egRVfnhKZ0W9G2-FGhyGuwJCq4" - "CCAISEAfZ_94TqpXBImeAUzYhNr0Y48SbiwUijgIwggEKAoIBAQDRigR9nFm4mfBUh1Y3SGyOcF" - "E-yK2NtfDiQe9l70KtkOeH4sB6MMB8g1QKPbUE8SBjPvXVJC_2DAWKjALzk4Aw-K-VmYe_Ag9CH" - "JiS-XcfUYEGgK4jVMxadEq3LufEEREKUZnzjgQlR39dzgjFqIrC1bwfy3_99RsjPt6QpWPg36PI" - "O4UKlmwBDTFzSOJB-4IV8Opy5Zv84BqPuyO9P5e3bXj_shRfy_XAGG2HGP_PpOCZWEfxuce0Iyu" - "vpTPLQpTOgNw-VvUBGCWMZFoERopmqp_pQwWZ2a-EwlT_vvYY4SkuNjflBskR70xz4QzEo9665g" - "k6I-HbHrTv29KEiAllAgMBAAEomSASgAIkKz1CSdFJVKcpO56jW0vsjKp92_cdqXBSEY3nuhzug" - "_LFluMJx_IqATUcCOY-w6w0yKn2ezfZGE0MDIaCngEgQFI_DRoaSOBNNeirF59uYM0sK3P2eGS9" - "G6F0l-OUXJdSO0b_LO8AbAK9LA3j7UHaajupJI1mdc4VtJfPRTsml2vIeKhDWXWaSvmeHgfF_tp" - "-OV7oPuk6Ub26xpCp2He2rEAblCYEl25Zlz97K4DhyTOV5_xuSdSt-KbTLY9cWM5i9ncND1RzCc" - "4qOixKarnMM5DdpZhs3B5xVj3yBAM1mVxPD2sZnqHSEN2EK7BMlHEnnyxhX0MGE36TQZR7P-I-G" - "rUFCq8CCAESEDAxMjM0NTY3ODlBQkNERUYYspIEIo4CMIIBCgKCAQEApwA2YGXcvVRaKkC04RWU" - "WBFPlFjd3qcfPCzgiAkpYVdnXlZ-7iePWTSaKqqdtE76p2rUyXpTwU6f4zT3PbfJEEdPKNo_zjF" - "7_QYQ6_e-kvmv-z5o2u4aZEzzKfJznjnY9m_YsoCCcY61pPLCPs0KyrYEzZoTi1RzVCVUjL6Yem" - "et2rNOs_qCqEpnmFZXVHHNEn_towHAaoskA5aIvpdmKrxTyYMGUVqIZRMY5Drta_FhW0zIHvTCr" - "gheLV_4En-i_LshGDDa_kD7AcouNw7O3XaHgkYLOnePwHIHLH-dHoZb7Scp3wOXYu9E01s925xe" - "G3s5tAttBGu7uyxfz7N6BQIDAQABKNKF2MwEEoADe9NAqNAxHpU13bMgz8LPySZJU8hY1RLwcfT" - "UM47Xb3m-F-s2cfI7w08668f79kD45uRRzkVc8GbRIlVyzVC0WgIvtxEkYRKfgF_J7snUe2J2NN" - "1FrkK7H3oYhcfPyYZH_SPZJr5HPoBFQTmS5A4l24U1dzQ6Z7_q-oS6uT0DiagTnzWhEg6AEnIkT" - "sJtK3cZuKGYq3NDefZ7nslPuLXxdXl6SAEOtrk-RvCY6EBqYOuPUXgxXOEPbyM289R6aHQyPPYw" - "qs9Pt9_E4BuMqCsbf5H5mLms9FA-wRx6mK2IaOboT4tf9_YObp3hVeL3WyxzXncETzJdE1GPGlO" - "t_x5S_MylgJKbiWQYSdmqs3fzYExunw3wvI4tPHT_O8A_xKjyTEAvE5cBuCkfjwT716qUOzFUzF" - "gZYLHnFiQLZekZUbUUlWY_CwU9Cv0UtxqQ6Oa835_Ug8_n1BwX6BPbmbcWe2Y19laSnDWg4JBNl" - "F2CyP9N75jPtW9rVfjUSqKEPOwaIgwzNDkyMjM3NDcAAAA=\"," - "\"signature\": \"r-LpoZcbbr2KtoPaFnuWTVBh4Gup1k8vn0ClW2qm32A=\"}}"; + "{\"kind\": \"certificateprovisioning#certificateProvisioningResponse\"," + "\"signedResponse\": {" + "\"message\": \"CtAJiVocnKtls7HO9SZtMg7-aEZosRT-qAjLnKt4FZ_5jvW-BEVBPNj1yeXh" + "o_wla-VdgYQBRsnXuONH4Rh7Kg0T1mv3ybc2VIU4imZ46nW7FsZYRxz3EwWkFelIav9JDeHBlat" + "qOKJEXtCiaySgdzNyZDDsZHxQu8Yh4ZeiHTVNHotoAiFy9qoUO0oaHpZ8xJxDkuN0u9-yauLdGy" + "F8B3u2CJjO7ztbYC1a5b8NTTDzN3hKdXy9FcR0a5R5YDzvlCWj-TeEIFSop0t4QI7bGLaubXJ_K" + "y7ppcLwM4LAOs12G4TyXa3R3A64rjF7tBdnbsA2S1ELpePWPK2jtvUu1TBWxS2bToU1PQrY8C9E" + "bDaN7TpE92sOyrW1sIGkit03thAaRjHUl_JtLyqKCFUamjsQqCi7pk_4IvoFJiYKAfE5Wf5cbP0" + "IrFW3CSkgYtO_d7jnyTUaweh9eP_8udIoUNkJjHMpglR_VJcWyIqw-hg8PqWm7AcYBUNDF0sYzM" + "uMiw2uq4RSBq8vklZ_nJjVc2GZ1pSqpaLLghDpviGUbEoGGJP4I15pveCKbyOj6TfKbeiN2jgit" + "1CaHMOmirOeSpD0gODGHQaTuVFbWQg-jgsbhht0NDHyqwmPDrCe78h2gXS6k0qDb3HL9M4QT5E4" + "-E2cZLfmvtrDokugAwf0eZM14464Oi_o6NK-Gbic8q1VnI1Y7uDctVHPAlV2NjsCVoaALtpNQZ0" + "HmaOF_jJ2RxCSyQCX1sxkRR_jeKyH10_i-GkXPokB3Z23LvHOheEVNmJJNvM9YBSw318aAiOIAo" + "Wdffrfo9j4xDs34W-ndKn344BNUq8tGmo3EVhSg6pdLPcCWSpjbgQxnBe_cwmKJVKU39GPMFV0P" + "vEeYKWljptsdUc9MH4XLomyalSPGCOkoVIwdPd1qXLhh1mmdNRm_7X-7zBezltjw42XKjtOKwBi" + "_UFmgY6i4Mjbk9ShExjWpvgKNntWepIZGZhYVP1tt4H-wqsXMfupTviHf_FaQX3cadUbO_6imfa" + "91MvnFe5v0iIi1HxsQzl0NmIByHFNUfHpzrZ0RzkmxsRY5lMLpOfdue7NJ5IZN1iVgiSS3llwa3" + "2s2Yq-gCzqv7vwdIwAxZ__gOr42LR__KCB51g9xwXdVLH71Dv_WDgDv5LJas9MVz5vv_TDo9Gqf" + "ZdEI8M2h0LbhdCVGxyYoeWVl6iHrPoeP6mJ5uaDZAAn_IdoVVPuCA8Wqmi_kbRdduwkuyasbIVx" + "dMwR7cRJz5Rl5kw36kjJtOv4wy9NU41PgNNuW1_2dWtIeKOcBJH2-5KbQbnntbKNdcPqZhTaoci" + "_3qDZtdlSqIJbDh7_4ZBkbcOHJ73jPKNOiLeDGD_rXFuPy4mAuxkZ8shXcw57xsIjz_OkB_Vr_2" + "2JhNMicnjITZDTGZzN4xyPldtOINCQGBBpUBLOaiWEFePqG9m5uWv6_NtpPt3I_YQnfUzv2RLFe" + "1pOLktoorTPWi8TH8hXnnmhHLMvd9r1Z1nISnZGmn_EhEFt-N-CCpiWAo5yQWIUA0iMqCdLHTub" + "l-tuhHyLqmiBUU9O7Ynwl12k2nqJTbbeSCt1mueee8j5bITUGZ8KAAtbIe_k0XMl5mRZWVzoBw0" + "RKmvBiWSRnOESEOdEAZw1ALRVjzEhSkDqGoIa7wkKsAIIAhIQB9n_3hOqlcEiZ4BTNiE2vRjgzd" + "aLBSKOAjCCAQoCggEBAKfruetSk8ULthZLZCoqaViWPEdlO50Bi1ChIjXCiuGNp566o74R4r_ou" + "8V7VX-Ze6p2Zpbm3iTPbB-vORaINQECi8TOA1xmxH0u7jrqHwfYX2a1EdCzp3DX5_lF2Rb44FI7" + "gKW1BwIeUDOdaMKApxZOp-jt1MBsMaMZDWgvQ5EAULzESq04zsynylaScm-IaZrY0mC3ynGz2YW" + "6TxbQKBUJsOUnpEEIDK2qlyEEESp0UFcTn2Lakda7MFI9wMAlH_qrDRdlDcLwGeD2qT8Lm4CFhV" + "cNDm3nqpONmBCJlebqSvavf5tALlIdR5CdfFV64beT8zWG1J-Rz6AGmHYgCucCAwEAASiZIDABE" + "oACFJ8UtXGiM__6SQFHXjN69lqzWfJKhplriW7m9wdCO-LEcCNFpUmzWBd1JppY11hL9AYO8OTI" + "Habeb65R51q_SjulaxlJwureJAYe-cO7RTvQvCddK9SJaDxeBe367ot_pnXvh66tksCkfvakY7W" + "igaDB8WVIaml6hmDADZgGAVfrPVWVPXMaY9LYg0r3r5PllfrmG7uXU0JcIa_0N-3MgT4QV4qIG2" + "9K9givfjELYidRpDDXu8KKcw0NYu-bTUNRaNrx6yaIpSbozen5HHBVdb5gJFB-922gr9mqY1PF_" + "-ulwvcXLn-Oa17QJ0YL1LkswDjVSC89MpJWkjW_qVlF8Rq2BQqwAggBEhCr7oe2E5WSaDltfP2r" + "q_jyGK_qu4sFIo4CMIIBCgKCAQEAmtIaWtGjInLYjdQic5DPUGUgnZah-FZVFt0SwcH90u1UhQW" + "eRgBH1psYrYqaaS8zu6vclTCT17E0WsDJlS8P8dBDlSMlyrNBZjCTDyczkI68tM5v0HXnpDCb5s" + "RaCSlP7Dl9BV_67GZPFoVuw53QCaI8cQ4RHcNVupExNRcFiCc-HSb3tZO7yyZyoB1Yx6GJnlts3" + "WA3A0DqVQIY79IGqdtTkKwydiNXVqR5ksMecqALwh9vKx6oSkxNat3I1BKQnzTY0pYChDjprabV" + "O9dAM2fYwGELJYel9qQV6LT6ar0PbKCMGa9qR-YwYiR8-SN-jpvmKL0h98bhTsC65DUQswIDAQA" + "BKJkgMAESgANZMzj1jPLFR8wnWKrTwL7dLF8qNJOPf7KDdJeqioqEkAe7mZiH3ZlQlE5EjPFGn5" + "IytduT2SdPGZoAsMEavJb_nETlELNXW16bQSPKJXr1gYrzoJ86t2ePrbXOTDQUV9pIi9yCLgWYp" + "r9PVx9osIRfd4qHg_MPEITQhlxvMvX51-_NxiQpdDf3HH-x2tMu6pELU7eruIPhBPxhrOJ9eWTK" + "54w6ygZ0-RdEgJKRSCCtTcjjom1TJ_GimvqVJZ449LeVycc23BsJDkBNE9XOARSCHK0i3VDWTg-" + "autjKClFWaVcyoNDqW6cfdODF7YyEYP8sBbYenMoJn5pizXaYvl3qc4LY8a-8HVFU6akgCvcj2w" + "vaAMOdQvh5f5m6X33lLfE0w8R4JFkqxB7UC5Ev_nPDkyX1urg76t31_9qTmQAlxdoIhmBsPziSW" + "jE3gII_qetwT7z1cPX8bGGPELwFiG5sQ6jQbHlGV-gaL2v-Peh6HZbGltsoI0wm7_N1i-BwiyUi" + "BPHgGJ8=\"," + "\"signature\": \"fRiAeyze4AtvYXpxWImduHrJ1lZ4K0bRT1lk2jVIpcs=\"}}"; } // namespace namespace wvcdm { @@ -84,22 +86,23 @@ class WvCdmEngineTest : public testing::Test { void GenerateKeyRequest(const std::string& key_system, const std::string& init_data) { wvcdm::CdmAppParameterMap app_parameters; + std::string server_url; EXPECT_EQ(cdm_engine_.GenerateKeyRequest(session_id_, true, // is_key_system_present key_system, init_data, kLicenseTypeStreaming, app_parameters, - &key_msg_), wvcdm::KEY_MESSAGE); + &key_msg_, + &server_url), wvcdm::KEY_MESSAGE); } void GenerateRenewalRequest(const std::string& key_system, const std::string& init_data) { + std::string server_url; EXPECT_EQ(cdm_engine_.GenerateRenewalRequest(session_id_, - true, // is_key_system_init_data_present, - key_system, - init_data, - &key_msg_), + &key_msg_, + &server_url), wvcdm::KEY_MESSAGE); } @@ -147,18 +150,10 @@ class WvCdmEngineTest : public testing::Test { client_auth, 200); if (is_renewal) { - EXPECT_EQ(cdm_engine_.RenewKey(session_id_, - true, // is_key_system_init_data_present - g_key_system, - init_data, - resp), wvcdm::KEY_ADDED); + EXPECT_EQ(cdm_engine_.RenewKey(session_id_, resp), wvcdm::KEY_ADDED); } else { - EXPECT_EQ(cdm_engine_.AddKey(session_id_, - true, // is_key_system_init_data_present - g_key_system, - init_data, - resp), wvcdm::KEY_ADDED); + EXPECT_EQ(cdm_engine_.AddKey(session_id_, resp), wvcdm::KEY_ADDED); } } diff --git a/libwvdrmengine/cdm/core/test/device_files_unittest.cpp b/libwvdrmengine/cdm/core/test/device_files_unittest.cpp new file mode 100644 index 00000000..d6fdf1dc --- /dev/null +++ b/libwvdrmengine/cdm/core/test/device_files_unittest.cpp @@ -0,0 +1,92 @@ +// Copyright 2013 Google Inc. All Rights Reserved. + +#include "device_files.h" +#include "file_store.h" +#include "gtest/gtest.h" + +namespace wvcdm { + +TEST(DeviceFilesTest, StoreCertificate) { + std::string device_certificate_path = + DeviceFiles::GetPath(DeviceFiles::kCencPath, + DeviceFiles::kDeviceCertificateFileName); + + if (!File::Exists(DeviceFiles::kCencPath)) + EXPECT_TRUE(File::CreateDirectory(DeviceFiles::kCencPath)); + if (File::Exists(device_certificate_path)) + EXPECT_TRUE(File::Remove(device_certificate_path)); + + char test_buf[1200]; + for (size_t i = 0; i < sizeof(test_buf); i++) { + test_buf[i] = i % 128; + } + + size_t cert_len = 500; + std::string certificate(&test_buf[0], cert_len); + std::string wrapped_private_key(&test_buf[cert_len], + sizeof(test_buf) - cert_len - 1); + + EXPECT_TRUE(DeviceFiles::StoreCertificate(certificate, wrapped_private_key)); + EXPECT_TRUE(File::Exists(device_certificate_path)); + EXPECT_GT(File::FileSize(device_certificate_path), + (ssize_t)sizeof(test_buf)); +} + +TEST(DeviceFilesTest, StoreCertificateInitial) { + std::string device_certificate_path = + DeviceFiles::GetPath(DeviceFiles::kCencPath, + DeviceFiles::kDeviceCertificateFileName); + + if (File::Exists(DeviceFiles::kCencPath)) + EXPECT_TRUE(File::Remove(DeviceFiles::kIdmPath)); + + char test_buf[1200]; + for (size_t i = 0; i < sizeof(test_buf); i++) { + test_buf[i] = i % 128; + } + + size_t cert_len = 500; + std::string certificate(&test_buf[0], cert_len); + std::string wrapped_private_key(&test_buf[cert_len], + sizeof(test_buf) - cert_len - 1); + + EXPECT_TRUE(DeviceFiles::StoreCertificate(certificate, wrapped_private_key)); + EXPECT_TRUE(File::Exists(device_certificate_path)); + EXPECT_GT(File::FileSize(device_certificate_path), + (ssize_t)sizeof(test_buf)); +} + +TEST(DeviceFilesTest, RetrieveCertificate) { + std::string device_certificate_path = + DeviceFiles::GetPath(DeviceFiles::kCencPath, + DeviceFiles::kDeviceCertificateFileName); + + if (File::Exists(DeviceFiles::kCencPath)) + EXPECT_TRUE(File::Remove(DeviceFiles::kIdmPath)); + + char test_buf[1200]; + for (size_t i = 0; i < sizeof(test_buf); i++) { + test_buf[i] = i % 128; + } + + size_t cert_len = 500; + std::string certificate(&test_buf[0], cert_len); + std::string wrapped_private_key(&test_buf[cert_len], + sizeof(test_buf) - cert_len - 1); + + EXPECT_TRUE(DeviceFiles::StoreCertificate(certificate, wrapped_private_key)); + EXPECT_TRUE(File::Exists(device_certificate_path)); + EXPECT_GT(File::FileSize(device_certificate_path), + (ssize_t)sizeof(test_buf)); + + std::string in_certificate; + std::string in_wrapped_private_key; + EXPECT_TRUE(DeviceFiles::RetrieveCertificate(&in_certificate, + &in_wrapped_private_key)); + EXPECT_TRUE(memcmp(certificate.data(), in_certificate.data(), + certificate.size()) == 0); + EXPECT_TRUE(memcmp(wrapped_private_key.data(), in_wrapped_private_key.data(), + wrapped_private_key.size()) == 0); +} + +} diff --git a/libwvdrmengine/cdm/core/test/file_store_unittest.cpp b/libwvdrmengine/cdm/core/test/file_store_unittest.cpp new file mode 100644 index 00000000..24649159 --- /dev/null +++ b/libwvdrmengine/cdm/core/test/file_store_unittest.cpp @@ -0,0 +1,174 @@ +// Copyright 2013 Google Inc. All Rights Reserved. + +#include "file_store.h" +#include "gtest/gtest.h" + +namespace { + // TODO(rfrias): Make this work for non-unix paths + const std::string kDataDrmDir = "/data/drm"; + const std::string kIdmTestDir = "/data/drm/IDMtest"; + const std::string kCencTestDir = "/data/drm/IDMtest/CENCtest"; + const std::string kCencTestDirWithSlash = "/data/drm/IDMtest/CENCtest/"; + const std::string kTestFile01 = "/data/drm/IDMtest/CENCtest/file01.txt"; + + const std::string kFileExists = "/system/bin/sh"; + const std::string kDirExists = "/system/bin"; + const std::string kFileDoesNotExist = "/system/bin/shxyxyxy"; + const std::string kDirDoesNotExist = "/system/binxyxyxy"; +} // namespace + +namespace wvcdm { + +TEST(FileTest, FileExists) { + EXPECT_TRUE(File::Exists(kFileExists)); + EXPECT_TRUE(File::Exists(kDirExists)); + EXPECT_FALSE(File::Exists(kFileDoesNotExist)); + EXPECT_FALSE(File::Exists(kDirDoesNotExist)); +} + +TEST(FileTest, CreateDirectory) { + if (File::Exists(kCencTestDir)) + EXPECT_TRUE(File::Remove(kIdmTestDir)); + EXPECT_FALSE(File::Exists(kCencTestDir)); + EXPECT_TRUE(File::CreateDirectory(kCencTestDir)); + EXPECT_TRUE(File::Exists(kCencTestDir)); + EXPECT_TRUE(File::Remove(kIdmTestDir)); + EXPECT_TRUE(File::CreateDirectory(kCencTestDirWithSlash)); + EXPECT_TRUE(File::Exists(kCencTestDir)); + EXPECT_TRUE(File::Remove(kIdmTestDir)); +} + +TEST(FileTest, RemoveDir) { + if (!File::Exists(kCencTestDir)) + EXPECT_TRUE(File::CreateDirectory(kCencTestDir)); + EXPECT_TRUE(File::Exists(kCencTestDir)); + EXPECT_TRUE(File::Remove(kCencTestDir)); + EXPECT_FALSE(File::Exists(kCencTestDir)); +} + +TEST(FileTest, OpenFileUsingConstructor) { + if (!File::Exists(kCencTestDir)) + EXPECT_TRUE(File::CreateDirectory(kCencTestDir)); + EXPECT_TRUE(File::Exists(kCencTestDir)); + File::Remove(kTestFile01); + File file(kTestFile01, File::kCreate); + EXPECT_TRUE(file.IsOpen()); + file.Close(); + EXPECT_TRUE(File::Exists(kTestFile01)); +} + +TEST(FileTest, OpenFile) { + if (!File::Exists(kCencTestDir)) + EXPECT_TRUE(File::CreateDirectory(kCencTestDir)); + EXPECT_TRUE(File::Exists(kCencTestDir)); + File::Remove(kTestFile01); + File file; + file.Open(kTestFile01, File::kCreate); + EXPECT_TRUE(file.IsOpen()); + file.Close(); + EXPECT_TRUE(File::Exists(kTestFile01)); +} + +TEST(FileTest, RemoveDirAndFile) { + if (!File::Exists(kCencTestDir)) + EXPECT_TRUE(File::CreateDirectory(kCencTestDir)); + EXPECT_TRUE(File::Exists(kCencTestDir)); + File file(kTestFile01, File::kCreate); + EXPECT_TRUE(file.IsOpen()); + file.Close(); + EXPECT_TRUE(File::Remove(kTestFile01)); + EXPECT_TRUE(File::Remove(kCencTestDir)); + EXPECT_FALSE(File::Exists(kTestFile01)); + EXPECT_FALSE(File::Exists(kCencTestDir)); +} + +TEST(FileTest, IsDir) { + if (!File::Exists(kCencTestDir)) + EXPECT_TRUE(File::CreateDirectory(kCencTestDir)); + EXPECT_TRUE(File::Exists(kCencTestDir)); + File file(kTestFile01, File::kCreate); + EXPECT_TRUE(file.IsOpen()); + file.Close(); + EXPECT_TRUE(File::Exists(kTestFile01)); + EXPECT_TRUE(File::Exists(kCencTestDir)); + EXPECT_FALSE(File::IsDirectory(kTestFile01)); + EXPECT_TRUE(File::IsDirectory(kCencTestDir)); +} + +TEST(FileTest, IsRegularFile) { + if (!File::Exists(kCencTestDir)) + EXPECT_TRUE(File::CreateDirectory(kCencTestDir)); + EXPECT_TRUE(File::Exists(kCencTestDir)); + File file(kTestFile01, File::kCreate); + EXPECT_TRUE(file.IsOpen()); + file.Close(); + EXPECT_TRUE(File::Exists(kTestFile01)); + EXPECT_TRUE(File::Exists(kCencTestDir)); + EXPECT_TRUE(File::IsRegularFile(kTestFile01)); + EXPECT_FALSE(File::IsRegularFile(kCencTestDir)); +} + +TEST(FileTest, WriteReadTextFile) { + if (!File::Exists(kCencTestDir)) + EXPECT_TRUE(File::CreateDirectory(kCencTestDir)); + EXPECT_TRUE(File::Exists(kCencTestDir)); + File::Remove(kTestFile01); + + const char* test_string = "This is a test"; + File file1(kTestFile01, File::kCreate); + EXPECT_TRUE(file1.IsOpen()); + EXPECT_TRUE(file1.Write(test_string, strlen(test_string)+1)); + file1.Close(); + EXPECT_TRUE(File::Exists(kTestFile01)); + + char buf[100]; + File file2(kTestFile01, File::kReadOnly); + EXPECT_TRUE(file2.IsOpen()); + EXPECT_EQ((ssize_t)strlen(test_string)+1, file2.Read(buf, sizeof(buf))); + file2.Close(); + EXPECT_STREQ(test_string, buf); +} + +TEST(FileTest, WriteReadBinaryFile) { + if (!File::Exists(kCencTestDir)) + EXPECT_TRUE(File::CreateDirectory(kCencTestDir)); + EXPECT_TRUE(File::Exists(kCencTestDir)); + File::Remove(kTestFile01); + + unsigned char test_buf[600]; + for (size_t i = 0; i < sizeof(test_buf); i++) { + test_buf[i] = i % 128; + } + File file1(kTestFile01, File::kCreate | File::kBinary); + EXPECT_TRUE(file1.IsOpen()); + EXPECT_TRUE(file1.Write(test_buf, sizeof(test_buf))); + file1.Close(); + EXPECT_TRUE(File::Exists(kTestFile01)); + + char buf[1000]; + File file2(kTestFile01, File::kReadOnly); + EXPECT_TRUE(file2.IsOpen()); + EXPECT_EQ((ssize_t)sizeof(test_buf), file2.Read(buf, sizeof(buf))); + file2.Close(); + EXPECT_TRUE(memcmp(test_buf, buf, sizeof(test_buf)) == 0); +} + +TEST(FileTest, FileSize) { + if (!File::Exists(kCencTestDir)) + EXPECT_TRUE(File::CreateDirectory(kCencTestDir)); + File::Remove(kTestFile01); + + unsigned char test_buf[600]; + for (size_t i = 0; i < sizeof(test_buf); i++) { + test_buf[i] = i % 128; + } + File file1(kTestFile01, File::kCreate | File::kBinary); + EXPECT_TRUE(file1.IsOpen()); + EXPECT_TRUE(file1.Write(test_buf, sizeof(test_buf))); + file1.Close(); + EXPECT_TRUE(File::Exists(kTestFile01)); + + EXPECT_EQ((ssize_t)sizeof(test_buf), File::FileSize(kTestFile01)); +} + +} diff --git a/libwvdrmengine/cdm/core/test/license_unittest.cpp b/libwvdrmengine/cdm/core/test/license_unittest.cpp index 6a692a05..f26995a4 100644 --- a/libwvdrmengine/cdm/core/test/license_unittest.cpp +++ b/libwvdrmengine/cdm/core/test/license_unittest.cpp @@ -79,20 +79,24 @@ TEST(LicenseTestSession, InitNullSession) { TEST_F(LicenseTest, PrepareKeyRequest) { std::string signed_request; CdmAppParameterMap app_parameters; + std::string server_url; license_.PrepareKeyRequest(a2bs_hex(kInitData), kLicenseTypeStreaming, app_parameters, - &signed_request); + &signed_request, + &server_url); EXPECT_EQ(signed_request, a2bs_hex(kSignedRequest)); } TEST_F(LicenseTest, HandleKeyResponseValid) { std::string signed_request; CdmAppParameterMap app_parameters; + std::string server_url; license_.PrepareKeyRequest(a2bs_hex(kInitData), kLicenseTypeStreaming, app_parameters, - &signed_request); + &signed_request, + &server_url); EXPECT_EQ(signed_request, a2bs_hex(kSignedRequest)); EXPECT_TRUE(license_.HandleKeyResponse(a2bs_hex(kValidResponse))); } @@ -100,10 +104,12 @@ TEST_F(LicenseTest, HandleKeyResponseValid) { TEST_F(LicenseTest, HandleKeyResponseInvalid) { std::string signed_request; CdmAppParameterMap app_parameters; + std::string server_url; license_.PrepareKeyRequest(a2bs_hex(kInitData), kLicenseTypeStreaming, app_parameters, - &signed_request); + &signed_request, + &server_url); EXPECT_EQ(signed_request, a2bs_hex(kSignedRequest)); EXPECT_FALSE(license_.HandleKeyResponse(a2bs_hex(kInvalidResponse))); } diff --git a/libwvdrmengine/cdm/core/test/url_request.cpp b/libwvdrmengine/cdm/core/test/url_request.cpp index 27b8bd0c..6d57f860 100644 --- a/libwvdrmengine/cdm/core/test/url_request.cpp +++ b/libwvdrmengine/cdm/core/test/url_request.cpp @@ -4,6 +4,7 @@ #include "http_socket.h" #include "log.h" +#include "string_conversions.h" namespace wvcdm { @@ -49,7 +50,7 @@ void UrlRequest::AppendChunkToUpload(const std::string& data) { int UrlRequest::GetResponse(std::string& response) { response.clear(); - const int kTimeoutInMs = 1500; + const int kTimeoutInMs = 1500 * 2; int bytes = 0; int total_bytes = 0; do { @@ -102,5 +103,29 @@ bool UrlRequest::PostRequest(const std::string& data) { return true; } +void UrlRequest::AppendData(const std::string& data) { + request_.append(data); + request_.append("\r\n"); // marks end of data +} + +bool UrlRequest::PostCertRequest(const std::string& data) { + request_.assign("POST /"); + request_.append(socket_.resource_path()); + request_.append(" HTTP/1.1\r\n"); + request_.append("User-Agent: Widevine CDM v1.0\r\n"); + request_.append("Host: "); + request_.append(socket_.domain_name()); + request_.append("\r\nAccept: */*"); + request_.append("\r\nContent-Type: application/json"); + request_.append("\r\nContent-Length: "); + request_.append(UintToString(data.size())); + request_.append("\r\n"); // empty line to terminate header + request_.append("\r\n"); // terminates the request + + AppendData(data); + + socket_.Write(request_.c_str(), request_.size()); + return true; +} } // namespace wvcdm diff --git a/libwvdrmengine/cdm/core/test/url_request.h b/libwvdrmengine/cdm/core/test/url_request.h index 22a58880..119264ca 100644 --- a/libwvdrmengine/cdm/core/test/url_request.h +++ b/libwvdrmengine/cdm/core/test/url_request.h @@ -17,10 +17,12 @@ class UrlRequest { ~UrlRequest(); void AppendChunkToUpload(const std::string& data); + void AppendData(const std::string& data); int GetResponse(std::string& response); int GetStatusCode(const std::string& response); bool is_connected() const { return is_connected_; } bool PostRequest(const std::string& data); + bool PostCertRequest(const std::string& data); private: static const unsigned int kHttpBufferSize = 4096; diff --git a/libwvdrmengine/cdm/include/wv_content_decryption_module.h b/libwvdrmengine/cdm/include/wv_content_decryption_module.h index 9da0b01e..9217dd46 100644 --- a/libwvdrmengine/cdm/include/wv_content_decryption_module.h +++ b/libwvdrmengine/cdm/include/wv_content_decryption_module.h @@ -27,7 +27,8 @@ class WvContentDecryptionModule { const CdmInitData& init_data, const CdmLicenseType license_type, CdmAppParameterMap& app_parameters, - CdmKeyMessage* key_request); + CdmKeyMessage* key_request, + std::string* server_url); // Accept license response and extract key info. virtual CdmResponseType AddKey(const CdmSessionId& session_id, diff --git a/libwvdrmengine/cdm/src/file_store.cpp b/libwvdrmengine/cdm/src/file_store.cpp new file mode 100644 index 00000000..3df331ad --- /dev/null +++ b/libwvdrmengine/cdm/src/file_store.cpp @@ -0,0 +1,206 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// +// File class - provides a simple android specific file implementation + +#include "file_store.h" + +#include +#include +#include +#include +#include +#include + +#include "log.h" + +namespace { + const char* kCurrentDirectory = "."; + const char* kParentDirectory = ".."; +} + +namespace wvcdm { + +class File::Impl { + public: + FILE* file_; +}; + +File::File() : impl_(new File::Impl()) {} + +File::File(const std::string& file_path, int flags) : + impl_(new File::Impl()) { + Open(file_path, flags); +} + +File::~File() { + Close(); + delete impl_; +} + +bool File::Open(const std::string& name, int flags) { + std::string openFlags = ""; + + if (((flags & File::kTruncate) && Exists(name)) || + ((flags & File::kCreate) && !Exists(name))) { + FILE* fp = fopen(name.c_str(), "w+"); + if (fp) { + fclose(fp); + } + } + + if (flags & File::kBinary) { + openFlags = (flags & File::kReadOnly)? "rb" : "rb+"; + } else { + openFlags = (flags & File::kReadOnly)? "r" : "r+"; + } + + impl_->file_ = fopen(name.c_str(), openFlags.c_str()); + if (!impl_->file_) { + LOGW("File::Open: fopen failed: %d", errno); + } + return IsOpen(); +} + +void File::Close() { + if (impl_->file_) { + fclose(impl_->file_); + impl_->file_ = NULL; + } +} + +bool File::IsOpen() { + return impl_->file_ != NULL; +} + +bool File::IsBad() { + if (impl_->file_) + return ferror(impl_->file_) != 0; + else + return true; +} + +ssize_t File::Read(void* buffer, size_t bytes) { + if (impl_->file_) { + size_t len = fread(buffer, 1, bytes, impl_->file_); + if (len == 0) { + LOGW("File::Read: fread failed: %d", errno); + } + return len; + } + LOGW("File::Read: file not open"); + return -1; +} + +ssize_t File::Write(const void* buffer, size_t bytes) { + if (impl_->file_) { + size_t len = fwrite(buffer, 1, bytes, impl_->file_); + if (len == 0) { + LOGW("File::Write: fwrite failed: %d", errno); + } + return len; + } + LOGW("File::Write: file not open"); + return -1; +} + +bool File::Exists(const std::string& file) { + struct stat buf; + int res = stat(file.c_str(), &buf) == 0; + if (!res) { + LOGV("File::Exists: stat failed: %d", errno); + } + return res; +} + +bool File::Remove(const std::string& file_path) { + if (IsDirectory(file_path)) { + DIR* dir; + if ((dir = opendir(file_path.c_str())) != NULL) { + // first remove files and dir within it + struct dirent* entry; + while ((entry = readdir(dir)) != NULL) { + if (strcmp(entry->d_name, kCurrentDirectory) && + (strcmp(entry->d_name, kParentDirectory))) { + std::string file_path_to_remove = file_path + '/'; + file_path_to_remove += entry->d_name; + if (!Remove(file_path_to_remove)) { + closedir(dir); + return false; + } + } + } + closedir(dir); + } + if (rmdir(file_path.c_str())) { + LOGW("File::Remove: rmdir failed: %d", errno); + return false; + } + return true; + } else { + if (unlink(file_path.c_str())) { + LOGW("File::Remove: unlink failed: %d", errno); + return false; + } + return true; + } +} + +bool File::CreateDirectory(std::string path) { + size_t size = path.size(); + if ((size == 1) && (path[0] == '/')) + return true; + + if (size <= 1) + return false; + + if (path.at(size-1) == '/') { + --size; + path.resize(size); + } + + size_t pos = path.find('/', 1); + while (pos < size) { + path.at(pos) = '\0'; + if (mkdir(path.c_str(), 0775) != 0) { + if (errno != EEXIST) { + LOGW("File::CreateDirectory: mkdir failed: %d\n", errno); + return false; + } + } + path.at(pos) = '/'; + pos = path.find('/', pos+1); + } + if (mkdir(path.c_str(), 0775) != 0) { + if (errno != EEXIST) { + LOGW("File::CreateDirectory: mkdir failed: %d\n", errno); + return false; + } + } + return true; +} + +bool File::IsDirectory(const std::string& path) { + struct stat buf; + if (stat(path.c_str(), &buf) == 0) + return buf.st_mode & S_IFDIR; + else + return false; +} + +bool File::IsRegularFile(const std::string& path) { + struct stat buf; + if (stat(path.c_str(), &buf) == 0) + return buf.st_mode & S_IFREG; + else + return false; +} + +ssize_t File::FileSize(const std::string& file_path) { + struct stat buf; + if (stat(file_path.c_str(), &buf) == 0) + return buf.st_size; + else + return -1; +} + +} // namespace wvcdm diff --git a/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp b/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp index 8f30578b..5171f3ca 100644 --- a/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp +++ b/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp @@ -34,20 +34,19 @@ CdmResponseType WvContentDecryptionModule::GenerateKeyRequest( const CdmInitData& init_data, const CdmLicenseType license_type, CdmAppParameterMap& app_parameters, - CdmKeyMessage* key_request) { + CdmKeyMessage* key_request, + std::string* server_url) { CdmKeySystem key_system; return cdm_engine_->GenerateKeyRequest(session_id, false, key_system, init_data, license_type, - app_parameters, key_request); + app_parameters, key_request, + server_url); } CdmResponseType WvContentDecryptionModule::AddKey( const CdmSessionId& session_id, const CdmKeyResponse& key_data) { - CdmKeySystem key_system; - CdmInitData init_data; - return cdm_engine_->AddKey(session_id, false, key_system, - init_data, key_data); + return cdm_engine_->AddKey(session_id, key_data); } CdmResponseType WvContentDecryptionModule::CancelKeyRequest( diff --git a/libwvdrmengine/cdm/test/Android.mk b/libwvdrmengine/cdm/test/Android.mk index 4cce1e40..0ef4c20a 100644 --- a/libwvdrmengine/cdm/test/Android.mk +++ b/libwvdrmengine/cdm/test/Android.mk @@ -3,10 +3,22 @@ # LOCAL_PATH := $(call my-dir) +test_name := base64_test +test_src_dir := ../core/test +include $(LOCAL_PATH)/unit-test.mk + test_name := cdm_engine_test test_src_dir := ../core/test include $(LOCAL_PATH)/unit-test.mk +test_name := device_files_unittest +test_src_dir := ../core/test +include $(LOCAL_PATH)/unit-test.mk + +test_name := file_store_unittest +test_src_dir := ../core/test +include $(LOCAL_PATH)/unit-test.mk + test_name := http_socket_test test_src_dir := ../core/test include $(LOCAL_PATH)/unit-test.mk diff --git a/libwvdrmengine/cdm/test/request_license_test.cpp b/libwvdrmengine/cdm/test/request_license_test.cpp index 5b4f5b6b..3a620c69 100644 --- a/libwvdrmengine/cdm/test/request_license_test.cpp +++ b/libwvdrmengine/cdm/test/request_license_test.cpp @@ -23,6 +23,9 @@ std::string g_license_server; std::string g_port; wvcdm::KeyId g_wrong_key_id; int g_use_full_path = 0; // cannot use boolean in getopt_long + +static const std::string kDefaultProvisioningServerUrl = + "http://www-googleapis-test.sandbox.google.com/certificateprovisioning/v1/devicecertificates/create"; } // namespace namespace wvcdm { @@ -46,11 +49,14 @@ class WvCdmRequestLicenseTest : public testing::Test { void GenerateKeyRequest(const std::string& key_system, const std::string& init_data) { wvcdm::CdmAppParameterMap app_parameters; + std::string server_url; EXPECT_EQ(decryptor_.GenerateKeyRequest(session_id_, init_data, kLicenseTypeStreaming, app_parameters, - &key_msg_), wvcdm::KEY_MESSAGE); + &key_msg_, + &server_url), wvcdm::KEY_MESSAGE); + EXPECT_EQ(0, server_url.size()); } void GenerateRenewalRequest(const std::string& key_system, @@ -58,11 +64,14 @@ class WvCdmRequestLicenseTest : public testing::Test { // TODO application makes a license request, CDM will renew the license // when appropriate. wvcdm::CdmAppParameterMap app_parameters; + std::string server_url; EXPECT_EQ(decryptor_.GenerateKeyRequest(session_id_, init_data, kLicenseTypeStreaming, app_parameters, - &key_msg_), wvcdm::KEY_MESSAGE); + &key_msg_, + &server_url), wvcdm::KEY_MESSAGE); + EXPECT_NE(0, server_url.size()); } // posts a request and extracts the drm message from the response @@ -101,6 +110,34 @@ class WvCdmRequestLicenseTest : public testing::Test { return drm_msg; } + // posts a request and extracts the drm message from the response + std::string GetCertRequestResponse(const std::string& server_url, + int expected_response) { + UrlRequest url_request(server_url, g_port); + + if (!url_request.is_connected()) { + return ""; + } + + url_request.PostCertRequest(key_msg_); + std::string response; + int resp_bytes = url_request.GetResponse(response); + LOGD("size=%u, response start: %s", response.size(), + response.substr(0, 1024).c_str()); + LOGD("end: %s", response.substr(response.size() - 256).c_str()); + LOGD("end %d bytes response dump", resp_bytes); + + // Youtube server returns 400 for invalid message while play server returns + // 500, so just test inequity here for invalid message + int status_code = url_request.GetStatusCode(response); + if (expected_response == 200) { + EXPECT_EQ(200, status_code); + } else { + EXPECT_NE(200, status_code); + } + return response; + } + void VerifyKeyRequestResponse(const std::string& server_url, const std::string& client_auth, std::string& init_data, @@ -124,6 +161,19 @@ class WvCdmRequestLicenseTest : public testing::Test { std::string session_id_; }; +TEST_F(WvCdmRequestLicenseTest, ProvisioningTest) { + decryptor_.OpenSession(g_key_system, &session_id_); + std::string provisioning_server_url = ""; + + decryptor_.GetProvisioningRequest(&key_msg_, &provisioning_server_url); + EXPECT_STREQ(provisioning_server_url.data(), kDefaultProvisioningServerUrl.data()); + + std::string response = GetCertRequestResponse(kDefaultProvisioningServerUrl, 200); + if (!response.empty()) + decryptor_.HandleProvisioningResponse(response); + decryptor_.CloseSession(session_id_); +} + TEST_F(WvCdmRequestLicenseTest, BaseMessageTest) { decryptor_.OpenSession(g_key_system, &session_id_); GenerateKeyRequest(g_key_system, g_key_id); diff --git a/libwvdrmengine/mediadrm/src/WVDrmPlugin.cpp b/libwvdrmengine/mediadrm/src/WVDrmPlugin.cpp index 107d76f2..164f78c2 100644 --- a/libwvdrmengine/mediadrm/src/WVDrmPlugin.cpp +++ b/libwvdrmengine/mediadrm/src/WVDrmPlugin.cpp @@ -145,13 +145,16 @@ status_t WVDrmPlugin::getKeyRequest( } CdmKeyMessage keyRequest; + string cdmDefaultUrl; CdmResponseType res = mCDM->GenerateKeyRequest(cdmSessionId, cdmInitData, cdmLicenseType, - cdmParameters, &keyRequest); + cdmParameters, &keyRequest, + &cdmDefaultUrl); if (isCdmResponseTypeSuccess(res)) { defaultUrl.clear(); + defaultUrl.setTo(cdmDefaultUrl.data(), cdmDefaultUrl.size()); request.clear(); request.appendArray(reinterpret_cast(keyRequest.data()), diff --git a/libwvdrmengine/mediadrm/test/WVDrmPlugin_test.cpp b/libwvdrmengine/mediadrm/test/WVDrmPlugin_test.cpp index 859cf001..01eeb58d 100644 --- a/libwvdrmengine/mediadrm/test/WVDrmPlugin_test.cpp +++ b/libwvdrmengine/mediadrm/test/WVDrmPlugin_test.cpp @@ -27,11 +27,11 @@ class MockCDM : public WvContentDecryptionModule { MOCK_METHOD1(CloseSession, CdmResponseType(const CdmSessionId&)); - MOCK_METHOD5(GenerateKeyRequest, CdmResponseType(const CdmSessionId&, + MOCK_METHOD6(GenerateKeyRequest, CdmResponseType(const CdmSessionId&, const CdmInitData&, const CdmLicenseType, CdmAppParameterMap&, - CdmKeyMessage*)); + CdmKeyMessage*, string*)); MOCK_METHOD2(AddKey, CdmResponseType(const CdmSessionId&, const CdmKeyResponse&)); @@ -179,7 +179,6 @@ TEST_F(WVDrmPluginTest, GeneratesKeyRequests) { initData.appendArray(initDataRaw, kInitDataSize); CdmKeyMessage cdmRequest(requestRaw, requestRaw + kRequestSize); - Vector request; KeyedVector parameters; CdmAppParameterMap cdmParameters; @@ -191,7 +190,7 @@ TEST_F(WVDrmPluginTest, GeneratesKeyRequests) { parameters.add(String8("answer"), String8("42")); cdmParameters["answer"] = "42"; - String8 defaultUrl; + static const char* kDefaultUrl = "http://google.com/"; { InSequence calls; @@ -199,19 +198,25 @@ TEST_F(WVDrmPluginTest, GeneratesKeyRequests) { EXPECT_CALL(cdm, GenerateKeyRequest(cdmSessionId, ElementsAreArray(initDataRaw, kInitDataSize), - kLicenseTypeOffline, cdmParameters, _)) + kLicenseTypeOffline, cdmParameters, _, + _)) .WillOnce(DoAll(SetArgPointee<4>(cdmRequest), + SetArgPointee<5>(kDefaultUrl), Return(wvcdm::KEY_MESSAGE))); EXPECT_CALL(cdm, GenerateKeyRequest(cdmSessionId, ElementsAreArray(initDataRaw, kInitDataSize), kLicenseTypeStreaming, cdmParameters, - _)) + _, _)) .WillOnce(DoAll(SetArgPointee<4>(cdmRequest), + SetArgPointee<5>(kDefaultUrl), Return(wvcdm::KEY_MESSAGE))); } + Vector request; + String8 defaultUrl; + status_t res = plugin.getKeyRequest(sessionId, initData, String8("video/h264"), DrmPlugin::kKeyType_Offline, @@ -219,7 +224,7 @@ TEST_F(WVDrmPluginTest, GeneratesKeyRequests) { ASSERT_EQ(OK, res); EXPECT_THAT(request, ElementsAreArray(requestRaw, kRequestSize)); - EXPECT_TRUE(defaultUrl.isEmpty()); + EXPECT_STREQ(kDefaultUrl, defaultUrl.string()); res = plugin.getKeyRequest(sessionId, initData, String8("video/h264"), DrmPlugin::kKeyType_Streaming, parameters, @@ -227,7 +232,7 @@ TEST_F(WVDrmPluginTest, GeneratesKeyRequests) { ASSERT_EQ(OK, res); EXPECT_THAT(request, ElementsAreArray(requestRaw, kRequestSize)); - EXPECT_TRUE(defaultUrl.isEmpty()); + EXPECT_STREQ(kDefaultUrl, defaultUrl.string()); } TEST_F(WVDrmPluginTest, AddsKeys) { @@ -319,7 +324,7 @@ TEST_F(WVDrmPluginTest, GetsProvisioningRequests) { ASSERT_EQ(OK, res); EXPECT_THAT(request, ElementsAreArray(requestRaw, kRequestSize)); - EXPECT_EQ(String8(kDefaultUrl), defaultUrl); + EXPECT_STREQ(kDefaultUrl, defaultUrl.string()); } TEST_F(WVDrmPluginTest, HandlesProvisioningResponses) { diff --git a/libwvdrmengine/run_all_unit_tests.sh b/libwvdrmengine/run_all_unit_tests.sh new file mode 100755 index 00000000..c5f156c1 --- /dev/null +++ b/libwvdrmengine/run_all_unit_tests.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +if [ -z "$ANDROID_BUILD_TOP" ]; then + echo "Android build environment not set" + exit -1 +fi + +echo "waiting for device" +adb root && adb wait-for-device remount && adb sync + +adb shell /system/bin/request_license_test +adb shell /system/bin/policy_engine_unittest +adb shell /system/bin/libwvdrmmediacrypto_test +adb shell /system/bin/libwvdrmdrmplugin_test +adb shell /system/bin/cdm_engine_test +adb shell /system/bin/oemcrypto_test +adb shell /system/bin/file_store_unittest +adb shell /system/bin/device_files_unittest +adb shell LD_LIBRARY_PATH=/system/vendor/lib/mediadrm/ /system/bin/libwvdrmengine_test + +adb shell am start com.widevine.test/com.widevine.test.MediaDrmAPITest +# TODO: make this test more command line friendly +echo "check logcat output for MediaDrmAPITest" +