From aaa3c6192ae7a6dfe8e6af925ecdfd7d307e09a2 Mon Sep 17 00:00:00 2001 From: Joey Parrish Date: Tue, 10 Jun 2014 13:36:59 -0700 Subject: [PATCH] Source release v2.1.2-0-773 + third_party libs Change-Id: Ia07608577b65b301c22a8ff4bf7f743c2d3f9274 --- README | 5 +- build.py | 52 +- cdm/{cdm_api_internal.gyp => cdm.gyp} | 33 +- ...cdm_api_external.gyp => cdm_unittests.gyp} | 3 +- cdm/include/content_decryption_module.h | 3 + cdm/include/wv_cdm_version.h | 2 +- cdm/src/wv_content_decryption_module.cpp | 31 +- cdm/test/cdm_api_test.cpp | 103 +--- core/include/cdm_engine.h | 12 +- core/include/cdm_session.h | 19 +- core/include/crypto_session.h | 12 +- core/include/device_files.h | 53 +- core/include/file_store.h | 3 + core/include/license.h | 11 +- core/include/log.h | 2 + core/include/privacy_crypto.h | 13 +- core/include/wv_cdm_types.h | 10 +- core/src/cdm_engine.cpp | 124 ++++- core/src/cdm_session.cpp | 207 +++++-- core/src/certificate_provisioning.cpp | 13 +- core/src/crypto_session.cpp | 110 +++- core/src/device_files.cpp | 342 ++++++++---- core/src/device_files.proto | 13 + core/src/license.cpp | 122 ++++- core/src/license_protocol.proto | 100 +++- core/src/privacy_crypto.cpp | 128 +++-- core/src/privacy_crypto_dummy.cpp | 43 ++ core/src/string_conversions.cpp | 10 +- core/test/cdm_engine_test.cpp | 23 +- core/test/config_test_env.cpp | 72 ++- core/test/config_test_env.h | 21 +- core/test/device_files_unittest.cpp | 511 +++++++++++++++++- core/test/http_socket.cpp | 379 ++++++++----- core/test/http_socket.h | 47 +- core/test/http_socket_test.cpp | 243 +++++---- core/test/url_request.cpp | 192 +++---- core/test/url_request.h | 21 +- linux/src/log.cpp | 2 +- oemcrypto/include/oemcrypto_logging.h | 65 +++ oemcrypto/test/oemcrypto_test.cpp | 137 ++++- platforms/global_config.gypi | 3 +- third_party/protobuf.gyp | 48 +- 42 files changed, 2425 insertions(+), 918 deletions(-) rename cdm/{cdm_api_internal.gyp => cdm.gyp} (78%) rename cdm/{cdm_api_external.gyp => cdm_unittests.gyp} (92%) create mode 100644 core/src/privacy_crypto_dummy.cpp create mode 100644 oemcrypto/include/oemcrypto_logging.h diff --git a/README b/README index 950107e3..e2f45f22 100644 --- a/README +++ b/README @@ -1,5 +1,5 @@ README for Widevine CDM Partner Kit v2.1 -Date: 5/30/2014 +Date: 6/03/2014 This document provides additional details on installation, system setup, building, and testing components of the Widevine Content @@ -223,11 +223,12 @@ above the test directory. The test-releated GYP targets will have "test" or Here's a current list of GYP files and test-related targets: File - Targets Purpose - cdm/cdm_api_internal.gyp + cdm/cdm.gyp license_protocol Create protobuf sources for license protocol device_files Create protobuf sources for license storage wvcdm_sysdep Build system-dependent layer of CDM wvcdm_shared Build CDM shared library + cdm/cdm_unittests.gyp wvcdm_shared_api_unittest Build CDM unit tests cdm/test/gtest.gyp gtest Provides gtest modules for unit tests diff --git a/build.py b/build.py index 74a7e64a..38e6f53c 100755 --- a/build.py +++ b/build.py @@ -11,19 +11,20 @@ import gyp cdm_top = os.path.abspath(os.path.dirname(__file__)) -parser = argparse.ArgumentParser() -parser.add_argument('platform', - help='The platform configuration to use (x86-64, ...). ' - 'Should be one of the folder names inside platforms/') -parser.add_argument('-r', '--release', - dest='build_config', default='Debug', - action='store_const', const='Release', - help='Builds a release build (equivalent to -c Release)') -parser.add_argument('-c', '--config', - dest='build_config', default='Debug', - help='Select a build config (Debug, Release)') -parser.add_argument('-g', '--generator', default='make', - help='Which build system to use (make, ninja, ...)') + +def IsNinjaInstalled(): + """Determine if ninja is installed.""" + try: + bit_bucket = open(os.devnull, 'w') + subprocess.check_call(['ninja', '--version'], stdout=bit_bucket, + stderr=bit_bucket) + return True + except subprocess.CalledProcessError: + # Error code returned, probably not the ninja we're looking for. + return False + except OSError: + # No such command found. + return False def VerboseSubprocess(args): @@ -93,12 +94,33 @@ def ImportPlatform(name, gyp_args): def main(args): + if IsNinjaInstalled(): + default_generator = 'ninja' + else: + default_generator = 'make' + + parser = argparse.ArgumentParser() + parser.add_argument('platform', + help='The platform configuration to use (x86-64, ...). ' + 'Should be one of the folder names inside platforms/') + parser.add_argument('-r', '--release', + dest='build_config', default='Debug', + action='store_const', const='Release', + help='Builds a release build (equivalent to -c Release)') + parser.add_argument('-c', '--config', + dest='build_config', default='Debug', + help='Select a build config (Debug, Release). ' + 'Defaults to Debug.') + parser.add_argument('-g', '--generator', default=default_generator, + help='Which build system to use (make, ninja, ...). ' + 'Defaults to ninja when available, make otherwise.') + options = parser.parse_args(args) gyp_args = [ '--format=%s' % options.generator, '--depth=%s' % cdm_top, - '%s/cdm/cdm_api_external.gyp' % cdm_top, + '%s/cdm/cdm_unittests.gyp' % cdm_top, ] output_path = ImportPlatform(options.platform, gyp_args) @@ -116,4 +138,4 @@ def main(args): if __name__ == '__main__': - main(sys.argv[1:]) + sys.exit(main(sys.argv[1:])) diff --git a/cdm/cdm_api_internal.gyp b/cdm/cdm.gyp similarity index 78% rename from cdm/cdm_api_internal.gyp rename to cdm/cdm.gyp index 68b9dbda..dc59140d 100644 --- a/cdm/cdm_api_internal.gyp +++ b/cdm/cdm.gyp @@ -48,13 +48,13 @@ ], }, { - 'target_name': '<(cdm_target_name)', - 'type': '<(cdm_target_type)', + 'target_name': 'wvcdm_static', + 'type': 'static_library', 'defines': ['CDM_IMPLEMENTATION'], 'dependencies': [ + 'device_files', 'license_protocol', 'wvcdm_sysdep', - 'device_files', '<(oemcrypto_target)', ], 'include_dirs': [ @@ -82,9 +82,36 @@ '../core/src/privacy_crypto.cpp', '../core/src/properties.cpp', ], + 'conditions': [ + ['disable_privacy_crypto=="true"', { + 'sources!': [ # exclude + '../core/src/privacy_crypto.cpp', + ], + 'sources': [ # include + '../core/src/privacy_crypto_dummy.cpp', + ], + }], + ], 'direct_dependencies': [ + 'device_files', 'license_protocol', ], + 'conditions': [ + ['use_system_protobuf=="true"', { + 'direct_dependent_settings': { + 'libraries': [ + '-lprotobuf', + ], + }, + }], + ], + }, + { + 'target_name': 'wvcdm_shared', + 'type': 'shared_library', + 'dependencies': [ + 'wvcdm_static', + ], }, ], } diff --git a/cdm/cdm_api_external.gyp b/cdm/cdm_unittests.gyp similarity index 92% rename from cdm/cdm_api_external.gyp rename to cdm/cdm_unittests.gyp index 4d308c94..e4759972 100644 --- a/cdm/cdm_api_external.gyp +++ b/cdm/cdm_unittests.gyp @@ -26,10 +26,9 @@ '-lssl', '-lcrypto', '-lpthread', - '-lprotobuf', ], 'dependencies': [ - 'cdm_api_internal.gyp:<(cdm_target_name)', + 'cdm.gyp:wvcdm_shared', 'test/gmock.gyp:gmock', 'test/gmock.gyp:gmock_main', 'test/gtest.gyp:gtest', diff --git a/cdm/include/content_decryption_module.h b/cdm/include/content_decryption_module.h index 12a7ffa9..c5dc42fa 100644 --- a/cdm/include/content_decryption_module.h +++ b/cdm/include/content_decryption_module.h @@ -14,6 +14,9 @@ typedef __int64 int64_t; #include #endif +#include +#include + // Define CDM_EXPORT so that functionality implemented by the CDM module // can be exported to consumers. #if defined(WIN32) diff --git a/cdm/include/wv_cdm_version.h b/cdm/include/wv_cdm_version.h index 6500c01a..cc7ad56a 100644 --- a/cdm/include/wv_cdm_version.h +++ b/cdm/include/wv_cdm_version.h @@ -1,3 +1,3 @@ // Widevine CDM Kit Version - #define WV_CDM_VERSION "v2.1.1-0-738" + #define WV_CDM_VERSION "v2.1.2-0-773" diff --git a/cdm/src/wv_content_decryption_module.cpp b/cdm/src/wv_content_decryption_module.cpp index 445b65e2..dd5f1929 100644 --- a/cdm/src/wv_content_decryption_module.cpp +++ b/cdm/src/wv_content_decryption_module.cpp @@ -97,7 +97,7 @@ cdm::Status WvContentDecryptionModule::GenerateKeyRequest( std::string security_level; std::string privacy_mode; - kVectorBytes service_certificate; + VectorBytes service_certificate; host_->GetPlatformString("SecurityLevel", &security_level); host_->GetPlatformString("PrivacyOn", &privacy_mode); @@ -353,10 +353,22 @@ cdm::Status WvContentDecryptionModule::DoSubsampleDecrypt( CdmSessionId session_id; // it's empty but cdm_engine will locate via key_id. CdmResponseType status = NO_ERROR; - uint8_t* output_buffer = decrypted_block - ? reinterpret_cast( - decrypted_block->DecryptedBuffer()->Data()) - : NULL; + + size_t output_size = 0; + for (int i = 0; i < encrypted_buffer.num_subsamples; ++i) { + const cdm::SubsampleEntry& subsample = encrypted_buffer.subsamples[i]; + output_size += subsample.cipher_bytes + subsample.clear_bytes; + } + parameters.decrypt_buffer_length = output_size; + + if (decrypted_block) { + cdm::Buffer* buffer = host_->Allocate(output_size); + decrypted_block->SetDecryptedBuffer(buffer); + parameters.decrypt_buffer = buffer->Data(); + } else { + parameters.decrypt_buffer = NULL; + } + size_t offset = 0; size_t encrypted_offset = 0; uint32_t block_ctr = 0; @@ -377,13 +389,10 @@ cdm::Status WvContentDecryptionModule::DoSubsampleDecrypt( block_ctr = counter; } - parameters.encrypt_buffer = &encrypted_buffer.data[encrypted_buffer - .data_offset + offset]; - if (output_buffer) - parameters.decrypt_buffer = &output_buffer[offset]; - + parameters.encrypt_buffer = + &encrypted_buffer.data[encrypted_buffer.data_offset + offset]; + parameters.decrypt_buffer_offset = offset; parameters.encrypt_length = bytes; - parameters.decrypt_buffer_length = encrypted_buffer.data_size - offset; parameters.block_offset = encrypted_offset % kIvSize; offset += bytes; diff --git a/cdm/test/cdm_api_test.cpp b/cdm/test/cdm_api_test.cpp index 8f96716f..6f335c5f 100644 --- a/cdm/test/cdm_api_test.cpp +++ b/cdm/test/cdm_api_test.cpp @@ -47,8 +47,8 @@ double GetCurrentTestTime() { return tv.tv_sec; } -using wvcdm::kStringPairs; -using wvcdm::kVectorPairs; +using wvcdm::StringPairs; +using wvcdm::VectorPairs; // These classes below are naive implementation of the abstract classes defined // in the CDM interface (content_decryptiom_module.h), which are used for tests @@ -357,8 +357,8 @@ class TestHost : public cdm::Host, public IHostTime { cdm::ContentDecryptionModule* host_cdm_ptr_; // These are containers for the platform sharing data. - std::set platform_strings_set_; - std::set platform_vectors_set_; + std::set platform_strings_set_; + std::set platform_vectors_set_; TestHostFile test_host_file_; void LoadPersistentValues(); SimplePThread simple_thread_; @@ -473,12 +473,12 @@ void TestHost::GetPrivateData(int32_t* instance, int TestHost::GetPlatformString(const std::string& name, std::string* value) { - std::set::iterator it; + std::set::iterator it; for (it = platform_strings_set_.begin(); it != platform_strings_set_.end(); ++it) { - kStringPairs sp = *it; + StringPairs sp = *it; std::string x = sp.first; std::string y = sp.second; if (x == name) { @@ -492,14 +492,14 @@ int TestHost::GetPlatformString(const std::string& name, int TestHost::SetPlatformString(const std::string& name, const std::string& value) { - kStringPairs sp(name, value); + StringPairs sp(name, value); platform_strings_set_.insert(sp); return 1; } int TestHost::PersistPlatformString(const std::string& name, const std::string& value) { - kStringPairs sp(name, value); + StringPairs sp(name, value); // Write the pairs to a file such that they can be retrieved by // GetPlatformString() even after a power cycle. return 1; @@ -508,17 +508,17 @@ int TestHost::PersistPlatformString(const std::string& name, int TestHost::SetPlatformByteArray(const std::string& name, const std::vector& value) { // A zero value pair is an erase only. - std::set::iterator it; + std::set::iterator it; for (it = platform_vectors_set_.begin(); it != platform_vectors_set_.end(); ++it) { - kVectorPairs vp = *it; + VectorPairs vp = *it; if (vp.first == name) { platform_vectors_set_.erase(vp); } } if (value.size() > 0) { - kVectorPairs vp(name, value); + VectorPairs vp(name, value); platform_vectors_set_.insert(vp); } @@ -540,7 +540,7 @@ int TestHost::PersistPlatformByteArray(const std::string& name, memcpy(&string_buffer[0], &value[0], value.size()); test_host_file_.Write(&string_buffer[0], value.size()); } - kVectorPairs vp(name, value); + VectorPairs vp(name, value); SetPlatformByteArray(name, value); // open file and persist this so that it can be retrieved as a string/vector @@ -550,11 +550,11 @@ int TestHost::PersistPlatformByteArray(const std::string& name, int TestHost::GetPlatformByteArray(const std::string& name, std::vector* value) { - std::set::iterator it; + std::set::iterator it; for (it = platform_vectors_set_.begin(); it != platform_vectors_set_.end(); ++it) { - kVectorPairs vp = *it; + VectorPairs vp = *it; std::string x = vp.first; if (x == name) { *value = vp.second; @@ -693,7 +693,6 @@ std::string g_client_auth; wvcdm::KeyId g_key_id; wvcdm::CdmKeySystem g_key_system; 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 @@ -735,7 +734,7 @@ class WvCdmApiTest : public testing::Test { // emulate pulling a certificate out of persistent storage. static const size_t kPrivacyCertSize = 256; - kVectorBytes cert(kPrivacyCertSize); + VectorBytes cert(kPrivacyCertSize); for (size_t i = 0; i < cert.size(); i++) { cert[i] = i; } @@ -756,18 +755,19 @@ class WvCdmApiTest : public testing::Test { if (status == cdm::kNeedsDeviceCertificate) { std::string provisioning_server_url; std::string prov_request; - if (cdm_->GetProvisioningRequest(&prov_request, - &provisioning_server_url) == cdm::kSuccess) { - - UrlRequest url_request(provisioning_server_url, - kDefaultHttpsPort, true, true); - + status = cdm_->GetProvisioningRequest(&prov_request, + &provisioning_server_url); + if (status == cdm::kSuccess) { + UrlRequest url_request(provisioning_server_url); url_request.PostCertRequestInQueryString(prov_request); - std::string message; - int resp_bytes = url_request.GetResponse(&message); - if (resp_bytes) { - if (cdm_->HandleProvisioningResponse(message) == cdm::kSuccess) { + std::string message; + bool ok = url_request.GetResponse(&message); + EXPECT_TRUE(ok); + + if (ok) { + status = cdm_->HandleProvisioningResponse(message); + if (status == cdm::kSuccess) { status = cdm_->GenerateKeyRequest(NULL, 0, (const uint8_t*)init_data.data(), init_data.length()); } @@ -782,7 +782,7 @@ class WvCdmApiTest : public testing::Test { const std::string& client_auth, int expected_response) { // Use secure connection and chunk transfer coding. - UrlRequest url_request(server_url + client_auth, g_port, true, true); + UrlRequest url_request(server_url + client_auth); if (!url_request.is_connected()) { return ""; } @@ -887,20 +887,12 @@ class WvCdmApiTest : public testing::Test { buf.num_subsamples = 1; buf.timestamp = 10; - TestBuffer* out_buf = - dynamic_cast(host_->Allocate(buf.data_size)); - memset(out_buf->Data(), 0, buf.data_size); - TestDecryptedBlock output; - output.SetDecryptedBuffer(out_buf); - - cdm::Status status; - status = cdm_->Decrypt(buf, &output); + cdm::Status status = cdm_->Decrypt(buf, &output); EXPECT_EQ(cdm::kSuccess, status); EXPECT_EQ(0, memcmp(output.DecryptedBuffer()->Data(), &data.decrypt_data[0], buf.data_size)); - LOGI("Verbose Acknowledgement of Test Pass!!!\n"); } // Level 1 / Level 2 payload comes back in the cpu memory as cleartext. @@ -976,22 +968,14 @@ class WvCdmApiTest : public testing::Test { buf.num_subsamples = sub.size(); buf.timestamp = 10; - TestBuffer* out_buf = dynamic_cast(host_->Allocate( - buf.data_size)); - memset(out_buf->Data(), 0, buf.data_size); - TestDecryptedBlock output; - output.SetDecryptedBuffer(out_buf); - - cdm::Status status; - status = cdm_->Decrypt(buf, &output); + cdm::Status status = cdm_->Decrypt(buf, &output); EXPECT_EQ(cdm::kSuccess, status); EXPECT_EQ( 0, memcmp(output.DecryptedBuffer()->Data(), &data.decrypt_data[0], buf.data_size)); - LOGI("Verbose Acknowledgement of Test Pass!!!\n"); } void DecryptClearSubsampleTestWithMissingSubsampleInfo() { @@ -1064,15 +1048,9 @@ class WvCdmApiTest : public testing::Test { //buf.num_subsamples = sub.size(); buf.timestamp = 10; - TestBuffer* out_buf = - dynamic_cast(host_->Allocate(buf.data_size)); - memset(out_buf->Data(), 0, buf.data_size); - TestDecryptedBlock output; - output.SetDecryptedBuffer(out_buf); - cdm::Status status; - status = cdm_->Decrypt(buf, &output); + cdm::Status status = cdm_->Decrypt(buf, &output); EXPECT_EQ(cdm::kDecryptError, status); buf.subsamples = &sub[0]; @@ -1088,7 +1066,6 @@ class WvCdmApiTest : public testing::Test { buf.subsamples = NULL; status = cdm_->Decrypt(buf, &output); EXPECT_EQ(cdm::kDecryptError, status); - LOGI("Verbose Acknowledgement of Test Pass!!!\n"); } // Level 1 passes encrypted payload straight through. By calling the @@ -1157,8 +1134,6 @@ class WvCdmApiTest : public testing::Test { EXPECT_EQ(cdm::kSuccess, status); status = cdm_->DecryptDecodeAndRenderFrame(buf); EXPECT_EQ(cdm::kSuccess, status); - - LOGI("Verbose Acknowledgement of Level 1 Test Pass!!!\n"); } // Level 1 passes encrypted payload straight through. By calling the @@ -1240,8 +1215,6 @@ class WvCdmApiTest : public testing::Test { EXPECT_EQ(cdm::kSuccess, status); status = cdm_->DecryptDecodeAndRenderFrame(buf); EXPECT_EQ(cdm::kSuccess, status); - - LOGI("Verbose Acknowledgement of Level 1 Test Pass!!!\n"); } void WithMissingSubsampleInfoTest() { @@ -1335,8 +1308,6 @@ class WvCdmApiTest : public testing::Test { EXPECT_EQ(cdm::kDecryptError, status); status = cdm_->DecryptDecodeAndRenderFrame(buf); EXPECT_EQ(cdm::kDecryptError, status); - - LOGI("Verbose Acknowledgement of Level 1 Test Pass!!!\n"); } void TimerTest(void* session_id) { host_->SetTimer(1000, session_id); } @@ -1505,14 +1476,12 @@ int main(int argc, char** argv) { // The following variables are configurable through command line options. g_license_server.assign(config.license_server()); g_key_id.assign(config.key_id()); - g_port.assign(config.port()); std::string license_server(g_license_server); int show_usage = 0; static const struct option long_options[] = { {"use_full_path", no_argument, &g_use_full_path, 0}, {"keyid", required_argument, NULL, 'k'}, - {"port", required_argument, NULL, 'p'}, {"server", required_argument, NULL, 's'}, {"vmodule", required_argument, NULL, 0}, {"v", required_argument, NULL, 0}, @@ -1528,11 +1497,6 @@ int main(int argc, char** argv) { g_key_id.assign(optarg); break; } - case 'p': { - g_port.clear(); - g_port.assign(optarg); - break; - } case 's': { g_license_server.clear(); g_license_server.assign(optarg); @@ -1559,11 +1523,6 @@ int main(int argc, char** argv) { std::cout << " e.g. adb shell '" << argv[0] << " --server=\"url\"'" << std::endl << std::endl; - std::cout << std::setw(30) << std::left << " --port="; - std::cout << "specifies the port number, in decimal format" << std::endl; - std::cout << std::setw(30) << std::left << " "; - std::cout << "default: " << g_port << std::endl; - std::cout << std::setw(30) << std::left << " --server="; std::cout << "configure the license server url, please include http[s] in the url" @@ -1584,12 +1543,10 @@ int main(int argc, char** argv) { std::cout << std::endl; std::cout << "Server: " << g_license_server << std::endl; - std::cout << "Port: " << g_port << std::endl; std::cout << "KeyID: " << g_key_id << std::endl << std::endl; g_key_id = wvcdm::a2bs_hex(g_key_id); config.set_license_server(g_license_server); - config.set_port(g_port); config.set_key_id(g_key_id); return RUN_ALL_TESTS(); diff --git a/core/include/cdm_engine.h b/core/include/cdm_engine.h index 6b1c6f7f..e9c8ff84 100644 --- a/core/include/cdm_engine.h +++ b/core/include/cdm_engine.h @@ -88,10 +88,11 @@ class CdmEngine { std::string* cert, std::string* wrapped_key); - // Secure stop related methods - CdmResponseType GetSecureStops(CdmSecureStops* secure_stops); - CdmResponseType ReleaseSecureStops( - const CdmSecureStopReleaseMessage& message); + CdmResponseType Unprovision(CdmSecurityLevel security_level); + + // Usage related methods for streaming licenses + CdmResponseType GetUsageInfo(CdmUsageInfo* usage_info); + CdmResponseType ReleaseUsageInfo(const CdmUsageInfoReleaseMessage& message); // Decryption and key related methods // Accept encrypted buffer and return decrypted data. @@ -124,6 +125,9 @@ class CdmEngine { CdmReleaseKeySetMap release_key_sets_; CertificateProvisioning cert_provisioning_; SecurityLevel cert_provisioning_requested_security_level_; + CdmSession* usage_session_; + + int64_t last_usage_information_update_time; CORE_DISALLOW_COPY_AND_ASSIGN(CdmEngine); }; diff --git a/core/include/cdm_session.h b/core/include/cdm_session.h index 9a8abc47..2c51efba 100644 --- a/core/include/cdm_session.h +++ b/core/include/cdm_session.h @@ -28,6 +28,8 @@ class CdmSession { CdmResponseType RestoreOfflineSession(const CdmKeySetId& key_set_id, const CdmLicenseType license_type); + CdmResponseType RestoreUsageSession(const CdmKeyMessage& key_request, + const CdmKeyResponse& key_response); void set_key_system(const CdmKeySystem& ksystem) { key_system_ = ksystem; } const CdmKeySystem& key_system() { return key_system_; } @@ -86,6 +88,12 @@ class CdmSession { void OnKeyReleaseEvent(const CdmKeySetId& key_set_id); SecurityLevel GetRequestedSecurityLevel(); + CdmSecurityLevel GetSecurityLevel(); + + CdmResponseType UpdateUsageInformation(); + + bool is_usage_update_needed() { return is_usage_update_needed_; } + void reset_is_usage_update_needed() { is_usage_update_needed_ = false; } private: @@ -93,7 +101,9 @@ class CdmSession { CdmSessionId GenerateSessionId(); bool GenerateKeySetId(CdmKeySetId* key_set_id); + CdmResponseType StoreLicense(); bool StoreLicense(DeviceFiles::LicenseState state); + bool DeleteLicense(); // instance variables const CdmSessionId session_id_; @@ -103,13 +113,16 @@ class CdmSession { PolicyEngine policy_engine_; bool license_received_; bool reinitialize_session_; + bool is_offline_; + bool is_release_; + bool is_usage_update_needed_; - CdmLicenseType license_type_; + // information useful for offline and usage scenarios + CdmKeyMessage key_request_; + CdmKeyResponse key_response_; // license type offline related information CdmInitData offline_init_data_; - CdmKeyMessage offline_key_request_; - CdmKeyResponse offline_key_response_; CdmKeyMessage offline_key_renewal_request_; CdmKeyResponse offline_key_renewal_response_; std::string offline_release_server_url_; diff --git a/core/include/crypto_session.h b/core/include/crypto_session.h index 17b98cf9..3c9ac281 100644 --- a/core/include/crypto_session.h +++ b/core/include/crypto_session.h @@ -45,7 +45,8 @@ class CryptoSession { const std::string& signature, const std::string& mac_key_iv, const std::string& mac_key, - int num_keys, const CryptoKey* key_array); + const std::vector& key_array, + const std::string& provider_session_token); bool LoadCertificatePrivateKey(std::string& wrapped_key); bool RefreshKeys(const std::string& message, const std::string& signature, int num_keys, const CryptoKey* key_array); @@ -63,6 +64,15 @@ class CryptoSession { // Media data path CdmResponseType Decrypt(const CdmDecryptionParameters& parameters); + CdmResponseType UpdateUsageInformation(); + CdmResponseType GenerateUsageReport( + const std::string& provider_session_token, + std::string* usage_report); + CdmResponseType ReleaseUsageInformation( + const std::string& message, + const std::string& signature, + const std::string& provider_session_token); + bool GetRandom(size_t data_length, uint8_t* random_data); private: diff --git a/core/include/device_files.h b/core/include/device_files.h index a438ebf7..fe03a32e 100644 --- a/core/include/device_files.h +++ b/core/include/device_files.h @@ -5,6 +5,10 @@ #include "wv_cdm_types.h" +#if defined(UNIT_TEST) +#include +#endif + namespace wvcdm { class File; @@ -18,10 +22,10 @@ class DeviceFiles { } LicenseState; DeviceFiles(): file_(NULL), security_level_(kSecurityLevelUninitialized), - initialized_(false) {} - virtual ~DeviceFiles() {} + initialized_(false), test_file_(false) {} + virtual ~DeviceFiles(); - virtual bool Init(const File* handle, CdmSecurityLevel security_level); + virtual bool Init(CdmSecurityLevel security_level); virtual bool StoreCertificate(const std::string& certificate, const std::string& wrapped_private_key); @@ -49,25 +53,52 @@ class DeviceFiles { virtual bool DeleteAllLicenses(); virtual bool LicenseExists(const std::string& key_set_id); - // For testing only - static std::string GetCertificateFileName(); - static std::string GetLicenseFileNameExtension(); - - protected: - bool Hash(const std::string& data, std::string* hash); - bool StoreFile(const char* name, const std::string& data); - bool RetrieveFile(const char* name, std::string* data); + virtual bool StoreUsageInfo(const std::string& provider_session_token, + const CdmKeyMessage& key_request, + const CdmKeyResponse& key_response); + virtual bool DeleteUsageInfo(const std::string& provider_session_token); + virtual bool DeleteUsageInfo(); + virtual bool RetrieveUsageInfo( + std::vector >* usage_info); private: + bool StoreFile(const char* name, const std::string& serialized_file); + bool RetrieveFile(const char* name, std::string* serialized_file); + // Certificate and offline licenses are now stored in security // level specific directories. In an earlier version they were // stored in a common directory and need to be copied over. virtual void SecurityLevelPathBackwardCompatibility(); + // For testing only: + static std::string GetCertificateFileName(); + static std::string GetLicenseFileNameExtension(); + static std::string GetUsageInfoFileName(); + void SetTestFile(File* file); +#if defined(UNIT_TEST) + FRIEND_TEST(DeviceFilesSecurityLevelTest, SecurityLevel); + FRIEND_TEST(DeviceFilesStoreTest, StoreCertificate); + FRIEND_TEST(DeviceFilesStoreTest, StoreLicense); + FRIEND_TEST(DeviceFilesTest, DeleteLicense); + FRIEND_TEST(DeviceFilesTest, ReadCertificate); + FRIEND_TEST(DeviceFilesTest, RetrieveLicenses); + FRIEND_TEST(DeviceFilesTest, SecurityLevelPathBackwardCompatibility); + FRIEND_TEST(DeviceFilesTest, StoreLicenses); + FRIEND_TEST(DeviceFilesTest, UpdateLicenseState); + FRIEND_TEST(DeviceFilesUsageInfoTest, Delete); + FRIEND_TEST(DeviceFilesUsageInfoTest, Read); + FRIEND_TEST(DeviceFilesUsageInfoTest, Store); + FRIEND_TEST(WvCdmRequestLicenseTest, UnprovisionTest); + FRIEND_TEST(WvCdmRequestLicenseTest, ForceL3Test); + FRIEND_TEST(WvCdmUsageInfoTest, DISABLED_UsageInfo); +#endif + File* file_; CdmSecurityLevel security_level_; bool initialized_; + bool test_file_; + CORE_DISALLOW_COPY_AND_ASSIGN(DeviceFiles); }; diff --git a/core/include/file_store.h b/core/include/file_store.h index 5794240d..65f4f3d4 100644 --- a/core/include/file_store.h +++ b/core/include/file_store.h @@ -7,6 +7,9 @@ #include "wv_cdm_types.h" +#include +#include + namespace wvcdm { // File class. The implementation is platform dependent. diff --git a/core/include/license.h b/core/include/license.h index 79c8e1a4..677c73e5 100644 --- a/core/include/license.h +++ b/core/include/license.h @@ -40,12 +40,16 @@ class CdmLicense { CdmResponseType HandleKeyUpdateResponse( bool is_renewal, const CdmKeyResponse& license_response); - bool RestoreOfflineLicense(CdmKeyMessage& license_request, - CdmKeyResponse& license_response, - CdmKeyResponse& license_renewal_response); + bool RestoreOfflineLicense(const CdmKeyMessage& license_request, + const CdmKeyResponse& license_response, + const CdmKeyResponse& license_renewal_response); + bool RestoreUsageLicense(const CdmKeyMessage& license_request, + const CdmKeyResponse& license_response); bool HasInitData() { return !stored_init_data_.empty(); } bool IsKeyLoaded(const KeyId& key_id); + std::string provider_session_token() { return provider_session_token_; } + private: bool PrepareServiceCertificateRequest(CdmKeyMessage* signed_request, std::string* server_url); @@ -67,6 +71,7 @@ class CdmLicense { std::string stored_init_data_; bool initialized_; std::set loaded_keys_; + std::string provider_session_token_; // Used for certificate based licensing CdmKeyMessage key_request_; diff --git a/core/include/log.h b/core/include/log.h index 642ad710..c531d267 100644 --- a/core/include/log.h +++ b/core/include/log.h @@ -17,6 +17,8 @@ typedef enum { LOG_VERBOSE } LogPriority; +extern LogPriority g_cutoff; + // Enable/disable verbose logging (LOGV). // This function is supplied for cases where the system layer does not // initialize logging. This is also needed to initialize logging in diff --git a/core/include/privacy_crypto.h b/core/include/privacy_crypto.h index 69e292a7..309aeecc 100644 --- a/core/include/privacy_crypto.h +++ b/core/include/privacy_crypto.h @@ -24,30 +24,27 @@ #include -#include "openssl/evp.h" -#include "openssl/rsa.h" #include "wv_cdm_types.h" namespace wvcdm { class AesCbcKey { public: - AesCbcKey() : initialized_(false) {}; - ~AesCbcKey() {}; + AesCbcKey(); + ~AesCbcKey(); bool Init(const std::string& key); bool Encrypt(const std::string& in, std::string* out, std::string* iv); private: - EVP_CIPHER_CTX ctx_; - bool initialized_; + std::string key_; CORE_DISALLOW_COPY_AND_ASSIGN(AesCbcKey); }; class RsaPublicKey { public: - RsaPublicKey() : key_(NULL) {} + RsaPublicKey(); ~RsaPublicKey(); // Initializes an RsaPublicKey object using a DER encoded PKCS#1 RSAPublicKey @@ -64,7 +61,7 @@ class RsaPublicKey { const std::string& signature); private: - RSA* key_; + std::string serialized_key_; CORE_DISALLOW_COPY_AND_ASSIGN(RsaPublicKey); }; diff --git a/core/include/wv_cdm_types.h b/core/include/wv_cdm_types.h index 48a0f937..f4931d2a 100644 --- a/core/include/wv_cdm_types.h +++ b/core/include/wv_cdm_types.h @@ -23,15 +23,15 @@ typedef uint32_t CryptoSessionId; typedef std::string CryptoKeyId; typedef std::map CdmAppParameterMap; typedef std::map CdmQueryMap; -typedef std::vector CdmSecureStops; -typedef std::vector CdmSecureStopReleaseMessage; +typedef std::vector CdmUsageInfo; +typedef std::string CdmUsageInfoReleaseMessage; typedef std::string CdmProvisioningRequest; typedef std::string CdmProvisioningResponse; // Types for shared host/cdm interface pairs used to shared vendor data. -typedef std::pair kStringPairs; -typedef std::vector kVectorBytes; -typedef std::pair kVectorPairs; +typedef std::pair StringPairs; +typedef std::vector VectorBytes; +typedef std::pair VectorPairs; enum CdmResponseType { NO_ERROR, diff --git a/core/src/cdm_engine.cpp b/core/src/cdm_engine.cpp index 6e4b0f33..6cec34aa 100644 --- a/core/src/cdm_engine.cpp +++ b/core/src/cdm_engine.cpp @@ -6,6 +6,8 @@ #include #include "cdm_session.h" +#include "clock.h" +#include "device_files.h" #include "license_protocol.pb.h" #include "log.h" #include "properties.h" @@ -14,14 +16,24 @@ #include "wv_cdm_constants.h" #include "wv_cdm_event_listener.h" +namespace { + const uint32_t kUpdateUsageInformationPeriod = 60; // seconds + const size_t kMinNoncesPerSession = 4; +} // unnamed namespace + namespace wvcdm { CdmEngine::CdmEngine() - : cert_provisioning_requested_security_level_(kLevelDefault) { + : cert_provisioning_requested_security_level_(kLevelDefault), + usage_session_(NULL), + last_usage_information_update_time(0) { Properties::Init(); } CdmEngine::~CdmEngine() { + if (NULL != usage_session_) + delete usage_session_; + CdmSessionMap::iterator i(sessions_.begin()); for (; i != sessions_.end(); ++i) { delete i->second; @@ -478,13 +490,87 @@ CdmResponseType CdmEngine::HandleProvisioningResponse( wrapped_key); } -CdmResponseType CdmEngine::GetSecureStops( - CdmSecureStops* secure_stops) { +CdmResponseType CdmEngine::Unprovision(CdmSecurityLevel security_level) { + DeviceFiles handle; + if (!handle.Init(security_level)) { + LOGE("CdmEngine::Unprovision: unable to initialize device files"); + return UNKNOWN_ERROR; + } + + if (!handle.DeleteAllFiles()) { + LOGE("CdmEngine::Unprovision: unable to delete files"); + return UNKNOWN_ERROR; + } return NO_ERROR; } -CdmResponseType CdmEngine::ReleaseSecureStops( - const CdmSecureStopReleaseMessage& message) { +CdmResponseType CdmEngine::GetUsageInfo(CdmUsageInfo* usage_info) { + if (NULL == usage_session_) { + usage_session_ = new CdmSession(NULL); + } + + CdmResponseType status = usage_session_->Init(); + if (NO_ERROR != status) { + LOGE("CdmEngine::GetUsageInfo: session init error"); + return status; + } + + DeviceFiles handle; + if (!handle.Init(usage_session_->GetSecurityLevel())) { + LOGE("CdmEngine::GetUsageInfo: unable to initialize device files"); + return status; + } + + std::vector > license_info; + if (!handle.RetrieveUsageInfo(&license_info)) { + LOGE("CdmEngine::GetUsageInfo: unable to read usage information"); + return UNKNOWN_ERROR; + } + + if (0 == license_info.size()) { + usage_info->resize(0); + return NO_ERROR; + } + + std::string server_url; + // rate limit secure stop messages based on minimum nonce + // table size per session + usage_info->resize(license_info.size() >= kMinNoncesPerSession - 1 + ? kMinNoncesPerSession - 1 + : license_info.size()); + for (size_t i = 0; i < usage_info->size(); ++i) { + status = usage_session_->RestoreUsageSession(license_info[i].first, + license_info[i].second); + if (KEY_ADDED != status) { + LOGE("CdmEngine::GetUsageInfo: restore usage session error: %ld", + status); + usage_info->clear(); + return status; + } + status = usage_session_->GenerateReleaseRequest(&(*usage_info)[i], + &server_url); + if (KEY_MESSAGE != status) { + LOGE("CdmEngine::GetUsageInfo: generate release request error: %ld", + status); + usage_info->clear(); + return status; + } + } + return KEY_MESSAGE; +} + +CdmResponseType CdmEngine::ReleaseUsageInfo( + const CdmUsageInfoReleaseMessage& message) { + if (NULL == usage_session_) { + LOGE("CdmEngine::ReleaseUsageInfo: cdm session not initialized"); + return UNKNOWN_ERROR; + } + + CdmResponseType status = usage_session_->ReleaseKey(message); + if (NO_ERROR != status) { + LOGE("CdmEngine::ReleaseUsageInfo: release key error: %ld", status); + return UNKNOWN_ERROR; + } return NO_ERROR; } @@ -527,8 +613,8 @@ CdmResponseType CdmEngine::Decrypt( } if (iter == sessions_.end()) { LOGE("CdmEngine::Decrypt: session not found: id=%s, id size=%d", - session_id.size(), - session_id.c_str()); + session_id.c_str(), + session_id.size()); return KEY_ERROR; } @@ -604,9 +690,31 @@ bool CdmEngine::ValidateKeySystem(const CdmKeySystem& key_system) { } void CdmEngine::OnTimerEvent() { + Clock clock; + uint64_t current_time = clock.GetCurrentTime(); + bool update_usage_information = false; + + if (current_time - last_usage_information_update_time > + kUpdateUsageInformationPeriod) { + update_usage_information = true; + last_usage_information_update_time = current_time; + } + for (CdmSessionMap::iterator iter = sessions_.begin(); - iter != sessions_.end(); ++iter) { + iter != sessions_.end(); ++iter) { iter->second->OnTimerEvent(); + + if (update_usage_information && iter->second->is_usage_update_needed()) { + // usage is updated for all sessions so this needs to be + // called only once per update usage information period + CdmResponseType status = iter->second->UpdateUsageInformation(); + if (NO_ERROR != status) { + LOGW("Update usage information failed: %u", status); + } else { + update_usage_information = false; + } + } + iter->second->reset_is_usage_update_needed(); } } diff --git a/core/src/cdm_session.cpp b/core/src/cdm_session.cpp index c350e8f8..333c32f5 100644 --- a/core/src/cdm_session.cpp +++ b/core/src/cdm_session.cpp @@ -30,7 +30,9 @@ CdmSession::CdmSession(const CdmClientPropertySet* cdm_client_property_set) crypto_session_(NULL), license_received_(false), reinitialize_session_(false), - license_type_(kLicenseTypeStreaming), + is_offline_(false), + is_release_(false), + is_usage_update_needed_(false), is_certificate_loaded_(false) { if (cdm_client_property_set) { Properties::AddSessionPropertySet(session_id_, cdm_client_property_set); @@ -47,9 +49,8 @@ CdmResponseType CdmSession::Init() { std::string token; if (Properties::use_certificates_as_identification()) { - File file; DeviceFiles handle; - if (!handle.Init(&file, session.get()->GetSecurityLevel()) || + if (!handle.Init(session.get()->GetSecurityLevel()) || !handle.RetrieveCertificate(&token, &wrapped_key_)) { return NEED_PROVISIONING; } @@ -71,15 +72,14 @@ CdmResponseType CdmSession::RestoreOfflineSession( key_set_id_ = key_set_id; // Retrieve license information from persistent store - File file; DeviceFiles handle; - if (!handle.Init(&file, crypto_session_->GetSecurityLevel())) + if (!handle.Init(crypto_session_->GetSecurityLevel())) return UNKNOWN_ERROR; DeviceFiles::LicenseState license_state; if (!handle.RetrieveLicense(key_set_id, &license_state, &offline_init_data_, - &offline_key_request_, &offline_key_response_, + &key_request_, &key_response_, &offline_key_renewal_request_, &offline_key_renewal_response_, &offline_release_server_url_)) { @@ -102,14 +102,39 @@ CdmResponseType CdmSession::RestoreOfflineSession( } } - if (!license_parser_.RestoreOfflineLicense(offline_key_request_, - offline_key_response_, + if (!license_parser_.RestoreOfflineLicense(key_request_, key_response_, offline_key_renewal_response_)) { return UNKNOWN_ERROR; } license_received_ = true; - license_type_ = license_type; + is_offline_ = true; + is_release_ = license_type == kLicenseTypeRelease; + return KEY_ADDED; +} + +CdmResponseType CdmSession::RestoreUsageSession( + const CdmKeyMessage& key_request, + const CdmKeyResponse& key_response) { + + key_request_ = key_request; + key_response_ = key_response; + if (Properties::use_certificates_as_identification()) { + if (is_certificate_loaded_ || + crypto_session_->LoadCertificatePrivateKey(wrapped_key_)) { + is_certificate_loaded_ = true; + } else { + return NEED_PROVISIONING; + } + } + + if (!license_parser_.RestoreUsageLicense(key_request_, key_response_)) { + return UNKNOWN_ERROR; + } + + license_received_ = true; + is_offline_ = false; + is_release_ = true; return KEY_ADDED; } @@ -135,9 +160,17 @@ CdmResponseType CdmSession::GenerateKeyRequest( return UNKNOWN_ERROR; } - license_type_ = license_type; + switch (license_type) { + case kLicenseTypeStreaming: is_offline_ = false; break; + case kLicenseTypeOffline: is_offline_ = true; break; + case kLicenseTypeRelease: is_release_ = true; break; + default: + LOGE("CdmSession::GenerateKeyRequest: unrecognized license type: %ld", + license_type); + return UNKNOWN_ERROR; + } - if (license_type_ == kLicenseTypeRelease) { + if (is_release_) { return GenerateReleaseRequest(key_request, server_url); } else if (license_received_) { // renewal return GenerateRenewalRequest(key_request, server_url); @@ -168,9 +201,9 @@ CdmResponseType CdmSession::GenerateKeyRequest( return KEY_ERROR; } - if (license_type_ == kLicenseTypeOffline) { + key_request_ = *key_request; + if (is_offline_) { offline_init_data_ = init_data.data(); - offline_key_request_ = *key_request; offline_release_server_url_ = *server_url; } @@ -191,7 +224,7 @@ CdmResponseType CdmSession::AddKey(const CdmKeyResponse& key_response, return UNKNOWN_ERROR; } - if (license_type_ == kLicenseTypeRelease) { + if (is_release_) { return ReleaseKey(key_response); } else if (license_received_) { // renewal return RenewKey(key_response); @@ -201,25 +234,11 @@ CdmResponseType CdmSession::AddKey(const CdmKeyResponse& key_response, if (sts != KEY_ADDED) return sts; license_received_ = true; + key_response_ = key_response; - if (license_type_ == kLicenseTypeOffline) { - offline_key_response_ = key_response; - if (!GenerateKeySetId(&key_set_id_)) { - LOGE("CdmSession::AddKey: Unable to generate key set Id"); - return UNKNOWN_ERROR; - } - - if (!StoreLicense(DeviceFiles::kLicenseStateActive)) { - LOGE("CdmSession::AddKey: Unable to store license"); - CdmResponseType sts = Init(); - if (sts != NO_ERROR) { - LOGW("CdmSession::AddKey: Reinitialization failed"); - return sts; - } - - key_set_id_.clear(); - return UNKNOWN_ERROR; - } + if (is_offline_ || !license_parser_.provider_session_token().empty()) { + sts = StoreLicense(); + if (sts != NO_ERROR) return sts; } *key_set_id = key_set_id_; @@ -290,7 +309,17 @@ CdmResponseType CdmSession::CancelKeyRequest() { CdmResponseType CdmSession::Decrypt(const CdmDecryptionParameters& params) { if (crypto_session_.get() == NULL || !crypto_session_->IsOpen()) return UNKNOWN_ERROR; - return crypto_session_->Decrypt(params); + + CdmResponseType status = crypto_session_->Decrypt(params); + + if (NO_ERROR == status) { + if (!is_usage_update_needed_) { + is_usage_update_needed_ = + !license_parser_.provider_session_token().empty(); + } + } + + return status; } // License renewal @@ -303,7 +332,7 @@ CdmResponseType CdmSession::GenerateRenewalRequest(CdmKeyMessage* key_request, return KEY_ERROR; } - if (license_type_ == kLicenseTypeOffline) { + if (is_offline_) { offline_key_renewal_request_ = *key_request; } return KEY_MESSAGE; @@ -315,7 +344,7 @@ CdmResponseType CdmSession::RenewKey(const CdmKeyResponse& key_response) { license_parser_.HandleKeyUpdateResponse(true, key_response); if (sts != KEY_ADDED) return sts; - if (license_type_ == kLicenseTypeOffline) { + if (is_offline_) { offline_key_renewal_response_ = key_response; if (!StoreLicense(DeviceFiles::kLicenseStateActive)) return UNKNOWN_ERROR; } @@ -324,23 +353,28 @@ CdmResponseType CdmSession::RenewKey(const CdmKeyResponse& key_response) { CdmResponseType CdmSession::GenerateReleaseRequest(CdmKeyMessage* key_request, std::string* server_url) { - if (license_parser_.PrepareKeyUpdateRequest(false, key_request, server_url)) { - // Mark license as being released - if (StoreLicense(DeviceFiles::kLicenseStateReleasing)) return KEY_MESSAGE; + is_release_ = true; + if (!license_parser_.PrepareKeyUpdateRequest(false, key_request, server_url)) + return UNKNOWN_ERROR; + + if (is_offline_) { // Mark license as being released + if (!StoreLicense(DeviceFiles::kLicenseStateReleasing)) + return UNKNOWN_ERROR; } - return UNKNOWN_ERROR; + return KEY_MESSAGE; } // ReleaseKey() - Accept release response and release license. CdmResponseType CdmSession::ReleaseKey(const CdmKeyResponse& key_response) { - CdmResponseType sts = - license_parser_.HandleKeyUpdateResponse(false, key_response); - File file; - DeviceFiles handle; - if (handle.Init(&file, crypto_session_->GetSecurityLevel())) - handle.DeleteLicense(key_set_id_); + CdmResponseType sts = license_parser_.HandleKeyUpdateResponse(false, + key_response); + if (NO_ERROR != sts) + return sts; - return sts; + if (is_offline_ || !license_parser_.provider_session_token().empty()) { + DeleteLicense(); + } + return NO_ERROR; } bool CdmSession::IsKeyLoaded(const KeyId& key_id) { @@ -361,9 +395,8 @@ bool CdmSession::GenerateKeySetId(CdmKeySetId* key_set_id) { std::vector random_data( (kKeySetIdLength - sizeof(KEY_SET_ID_PREFIX)) / 2, 0); - File file; DeviceFiles handle; - if (!handle.Init(&file, crypto_session_->GetSecurityLevel())) + if (!handle.Init(crypto_session_->GetSecurityLevel())) return false; while (key_set_id->empty()) { @@ -380,18 +413,75 @@ bool CdmSession::GenerateKeySetId(CdmKeySetId* key_set_id) { return true; } -bool CdmSession::StoreLicense(DeviceFiles::LicenseState state) { - File file; +CdmResponseType CdmSession::StoreLicense() { + if (is_offline_) { + if (!GenerateKeySetId(&key_set_id_)) { + LOGE("CdmSession::StoreLicense: Unable to generate key set Id"); + return UNKNOWN_ERROR; + } + + if (!StoreLicense(DeviceFiles::kLicenseStateActive)) { + LOGE("CdmSession::StoreLicense: Unable to store license"); + CdmResponseType sts = Init(); + if (sts != NO_ERROR) { + LOGW("CdmSession::StoreLicense: Reinitialization failed"); + return sts; + } + + key_set_id_.clear(); + return UNKNOWN_ERROR; + } + return NO_ERROR; + } + + std::string provider_session_token = license_parser_.provider_session_token(); + if (provider_session_token.empty()) { + LOGE("CdmSession::StoreLicense: No provider session token and not offline"); + return UNKNOWN_ERROR; + } + DeviceFiles handle; - if (!handle.Init(&file, crypto_session_->GetSecurityLevel())) + if (!handle.Init(crypto_session_->GetSecurityLevel())) { + LOGE("CdmSession::StoreLicense: Unable to initialize device files"); + return UNKNOWN_ERROR; + } + + if (!handle.StoreUsageInfo(provider_session_token, key_request_, + key_response_)) { + LOGE("CdmSession::StoreLicense: Unable to store usage info"); + return UNKNOWN_ERROR; + } + return NO_ERROR; +} + +bool CdmSession::StoreLicense(DeviceFiles::LicenseState state) { + DeviceFiles handle; + if (!handle.Init(crypto_session_->GetSecurityLevel())) return false; return handle.StoreLicense( - key_set_id_, state, offline_init_data_, offline_key_request_, - offline_key_response_, offline_key_renewal_request_, + key_set_id_, state, offline_init_data_, key_request_, + key_response_, offline_key_renewal_request_, offline_key_renewal_response_, offline_release_server_url_); } +bool CdmSession::DeleteLicense() { + if (!is_offline_ && license_parser_.provider_session_token().empty()) + return false; + + DeviceFiles handle; + if (!handle.Init(crypto_session_->GetSecurityLevel())) { + LOGE("CdmSession::DeleteLicense: Unable to initialize device files"); + return false; + } + + if (is_offline_) + return handle.DeleteLicense(key_set_id_); + else + return handle.DeleteUsageInfo( + license_parser_.provider_session_token()); +} + bool CdmSession::AttachEventListener(WvCdmEventListener* listener) { std::pair result = listeners_.insert(listener); return result.second; @@ -433,4 +523,15 @@ SecurityLevel CdmSession::GetRequestedSecurityLevel() { return kLevelDefault; } +CdmSecurityLevel CdmSession::GetSecurityLevel() { + if (NULL == crypto_session_.get()) + return kSecurityLevelUninitialized; + + return crypto_session_.get()->GetSecurityLevel(); +} + +CdmResponseType CdmSession::UpdateUsageInformation() { + return crypto_session_->UpdateUsageInformation(); +} + } // namespace wvcdm diff --git a/core/src/certificate_provisioning.cpp b/core/src/certificate_provisioning.cpp index 25bd3837..6469761f 100644 --- a/core/src/certificate_provisioning.cpp +++ b/core/src/certificate_provisioning.cpp @@ -28,10 +28,9 @@ using video_widevine_server::sdk::ProvisioningResponse; using video_widevine_server::sdk::SignedProvisioningMessage; /* - * This function converts SignedProvisioningRequest into base64 string. - * It then wraps it in JSON format expected by the Apiary frontend. - * Apiary requires the base64 encoding to replace '+' with minus '-', - * and '/' with underscore '_'; opposite to stubby's. + * This function converts SignedProvisioningRequest into base64 string. It then + * wraps it in JSON format expected by the frontend. This server requires a + * "web-safe" base 64 encoding, where '+' becomes '-' and '/' becomes '_'. * * Returns the JSON formated string in *request. The JSON string will be * appended as a query parameter, i.e. signedRequest=set_certificate_type( - video_widevine_server::sdk::ProvisioningOptions_CertificateType_RSA_WIDEVINE); + video_widevine_server::sdk:: + ProvisioningOptions_CertificateType_WIDEVINE_DRM); break; case kCertificateX509: options->set_certificate_type( @@ -262,9 +262,8 @@ CdmResponseType CertificateProvisioning::HandleProvisioningResponse( const std::string& device_certificate = provisioning_response.device_certificate(); - File file; DeviceFiles handle; - if (!handle.Init(&file, crypto_session_.GetSecurityLevel())) { + if (!handle.Init(crypto_session_.GetSecurityLevel())) { LOGE("HandleProvisioningResponse: failed to init DeviceFiles"); return UNKNOWN_ERROR; } diff --git a/core/src/crypto_session.cpp b/core/src/crypto_session.cpp index 5be5615f..75e0860d 100644 --- a/core/src/crypto_session.cpp +++ b/core/src/crypto_session.cpp @@ -155,7 +155,7 @@ bool CryptoSession::GetDeviceUniqueId(std::string* device_id) { return false; } - device_id->assign(reinterpret_cast(&id[0]), id_length); + device_id->assign(reinterpret_cast(&id[0]), id_length); return true; } @@ -340,12 +340,11 @@ size_t CryptoSession::GetOffset(std::string message, std::string field) { return pos; } -CdmResponseType CryptoSession::LoadKeys(const std::string& message, - const std::string& signature, - const std::string& mac_key_iv, - const std::string& mac_key, - int num_keys, - const CryptoKey* key_array) { +CdmResponseType CryptoSession::LoadKeys( + const std::string& message, const std::string& signature, + const std::string& mac_key_iv, const std::string& mac_key, + const std::vector& keys, + const std::string& provider_session_token) { LOGV("CryptoSession::LoadKeys: Lock"); AutoLock auto_lock(crypto_lock_); @@ -358,10 +357,10 @@ CdmResponseType CryptoSession::LoadKeys(const std::string& message, } else { LOGV("CryptoSession::LoadKeys: enc_mac_key not set"); } - std::vector load_key_array(num_keys); - for (int i = 0; i < num_keys; ++i) { - const CryptoKey* ki = &key_array[i]; - OEMCrypto_KeyObject* ko = &load_key_array[i]; + std::vector load_keys(keys.size()); + for (size_t i = 0; i < keys.size(); ++i) { + const CryptoKey* ki = &keys[i]; + OEMCrypto_KeyObject* ko = &load_keys[i]; ko->key_id = msg + GetOffset(message, ki->key_id()); ko->key_id_length = ki->key_id().length(); ko->key_data_iv = msg + GetOffset(message, ki->key_data_iv()); @@ -377,11 +376,17 @@ CdmResponseType CryptoSession::LoadKeys(const std::string& message, ko->key_control = NULL; } } + uint8_t* pst = NULL; + if (!provider_session_token.empty()) { + pst = + const_cast(msg) + GetOffset(message, provider_session_token); + } LOGV("LoadKeys: id=%ld", (uint32_t)oec_session_id_); OEMCryptoResult sts = OEMCrypto_LoadKeys( oec_session_id_, msg, message.size(), reinterpret_cast(signature.data()), signature.size(), - enc_mac_key_iv, enc_mac_key, num_keys, &load_key_array[0], NULL, 0); + enc_mac_key_iv, enc_mac_key, keys.size(), &load_keys[0], pst, + provider_session_token.length()); if (OEMCrypto_SUCCESS == sts) { return KEY_ADDED; @@ -605,7 +610,8 @@ CdmResponseType CryptoSession::Decrypt(const CdmDecryptionParameters& params) { buffer_descriptor.buffer.clear.address = static_cast(params.decrypt_buffer) + params.decrypt_buffer_offset; - buffer_descriptor.buffer.clear.max_length = params.decrypt_buffer_length; + buffer_descriptor.buffer.clear.max_length = + params.decrypt_buffer_length - params.decrypt_buffer_offset; break; case OEMCrypto_BufferType_Secure: buffer_descriptor.buffer.secure.handle = params.decrypt_buffer; @@ -635,6 +641,84 @@ CdmResponseType CryptoSession::Decrypt(const CdmDecryptionParameters& params) { } } +CdmResponseType CryptoSession::UpdateUsageInformation() { + return (OEMCrypto_UpdateUsageTable() == OEMCrypto_SUCCESS) ? NO_ERROR + : UNKNOWN_ERROR; +} + +CdmResponseType CryptoSession::GenerateUsageReport( + const std::string& provider_session_token, std::string* usage_report) { + LOGV("GenerateUsageReport: id=%ld", (uint32_t)oec_session_id_); + + if (NULL == usage_report) { + LOGE("usage_report parameter is null"); + return UNKNOWN_ERROR; + } + + AutoLock auto_lock(crypto_lock_); + uint8_t* pst = reinterpret_cast( + const_cast(provider_session_token.data())); + + OEMCryptoResult status = + OEMCrypto_DeactivateUsageEntry(pst, provider_session_token.length()); + + if (OEMCrypto_SUCCESS != status) { + LOGE("CryptoSession::GenerateUsageReport: Deactivate Usage Entry error=%ld", + status); + return UNKNOWN_ERROR; + } + + size_t usage_length = 0; + status = OEMCrypto_ReportUsage(oec_session_id_, pst, + provider_session_token.length(), NULL, + &usage_length); + + if (OEMCrypto_ERROR_SHORT_BUFFER != status) { + LOGE("CryptoSession::GenerateUsageReport: Report Usage error=%ld", status); + return UNKNOWN_ERROR; + } + + usage_report->resize(usage_length); + OEMCrypto_PST_Report* report = reinterpret_cast( + const_cast(usage_report->data())); + status = OEMCrypto_ReportUsage(oec_session_id_, pst, + provider_session_token.length(), report, + &usage_length); + + if (OEMCrypto_SUCCESS != status) { + LOGE("CryptoSession::GenerateUsageReport: Report Usage error=%ld", status); + return UNKNOWN_ERROR; + } + + if (usage_length < usage_report->length()) { + usage_report->resize(usage_length); + } + + return NO_ERROR; +} + +CdmResponseType CryptoSession::ReleaseUsageInformation( + const std::string& message, const std::string& signature, + const std::string& provider_session_token) { + LOGV("ReleaseUsageInformation: id=%ld", (uint32_t)oec_session_id_); + AutoLock auto_lock(crypto_lock_); + const uint8_t* msg = reinterpret_cast(message.data()); + const uint8_t* sig = reinterpret_cast(signature.data()); + const uint8_t* pst = msg + GetOffset(message, provider_session_token); + + OEMCryptoResult status = OEMCrypto_DeleteUsageEntry( + oec_session_id_, pst, provider_session_token.length(), msg, + message.length(), sig, signature.length()); + + if (OEMCrypto_SUCCESS != status) { + LOGE("CryptoSession::ReleaseUsageInformation: Report Usage error=%ld", + status); + return UNKNOWN_ERROR; + } + + return NO_ERROR; +} + bool CryptoSession::GenerateNonce(uint32_t* nonce) { if (!nonce) { LOGE("input parameter is null"); diff --git a/core/src/device_files.cpp b/core/src/device_files.cpp index f79cc5e5..02d2fd6c 100644 --- a/core/src/device_files.cpp +++ b/core/src/device_files.cpp @@ -2,14 +2,22 @@ #include "device_files.h" +#if defined(__APPLE__) +# include +# define SHA256 CC_SHA256 +# define SHA256_DIGEST_LENGTH CC_SHA256_DIGEST_LENGTH +#else +# include +#endif + #include #include #include "device_files.pb.h" #include "file_store.h" #include "log.h" -#include "openssl/sha.h" #include "properties.h" +#include "string_conversions.h" // Protobuf generated classes. using video_widevine_client::sdk::DeviceCertificate; @@ -17,9 +25,13 @@ using video_widevine_client::sdk::HashedFile; using video_widevine_client::sdk::License; using video_widevine_client::sdk::License_LicenseState_ACTIVE; using video_widevine_client::sdk::License_LicenseState_RELEASING; +using video_widevine_client::sdk::UsageInfo; +using video_widevine_client::sdk::UsageInfo_ProviderSession; namespace { + const char kCertificateFileName[] = "cert.bin"; +const char kUsageInfoFileName[] = "usage.bin"; const char kLicenseFileNameExt[] = ".lic"; const char kWildcard[] = "*"; const char kDirectoryDelimiter = '/'; @@ -27,15 +39,27 @@ const char* kSecurityLevelPathCompatibilityExclusionList[] = {"ay64.dat"}; size_t kSecurityLevelPathCompatibilityExclusionListSize = sizeof(kSecurityLevelPathCompatibilityExclusionList) / sizeof(*kSecurityLevelPathCompatibilityExclusionList); -} // namespace + +bool Hash(const std::string& data, std::string* hash) { + if (!hash) return false; + hash->resize(SHA256_DIGEST_LENGTH); + + const unsigned char* input = + reinterpret_cast(data.data()); + unsigned char* output = reinterpret_cast(&(*hash)[0]); + SHA256(input, data.size(), output); + return true; +} + +} // unnamed namespace namespace wvcdm { -bool DeviceFiles::Init(const File* handle, CdmSecurityLevel security_level) { - if (handle == NULL) { - LOGW("DeviceFiles::Init: Invalid file handle parameter"); - return false; - } +DeviceFiles::~DeviceFiles() { + if (!file_ && !test_file_) delete file_; +} + +bool DeviceFiles::Init(CdmSecurityLevel security_level) { switch (security_level) { case kSecurityLevelL1: case kSecurityLevelL2: @@ -45,7 +69,7 @@ bool DeviceFiles::Init(const File* handle, CdmSecurityLevel security_level) { LOGW("DeviceFiles::Init: Unsupported security level %d", security_level); return false; } - file_ = const_cast(handle); + file_ = new File(); security_level_ = security_level; initialized_ = true; return true; @@ -68,24 +92,10 @@ bool DeviceFiles::StoreCertificate(const std::string& certificate, device_certificate->set_certificate(certificate); device_certificate->set_wrapped_private_key(wrapped_private_key); - std::string serialized_string; - file.SerializeToString(&serialized_string); + std::string serialized_file; + file.SerializeToString(&serialized_file); - // calculate SHA hash - std::string hash; - if (!Hash(serialized_string, &hash)) { - LOGW("DeviceFiles::StoreCertificate: Hash computation failed"); - return false; - } - - // Fill 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(kCertificateFileName, serialized_string); + return StoreFile(kCertificateFileName, serialized_file); } bool DeviceFiles::RetrieveCertificate(std::string* certificate, @@ -99,29 +109,12 @@ bool DeviceFiles::RetrieveCertificate(std::string* certificate, SecurityLevelPathBackwardCompatibility(); } - std::string serialized_hashed_file; - if (!RetrieveFile(kCertificateFileName, &serialized_hashed_file)) + std::string serialized_file; + if (!RetrieveFile(kCertificateFileName, &serialized_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())) { + if (!file.ParseFromString(serialized_file)) { LOGW("DeviceFiles::RetrieveCertificate: Unable to parse file"); return false; } @@ -187,25 +180,11 @@ bool DeviceFiles::StoreLicense(const std::string& key_set_id, license->set_renewal(license_renewal); license->set_release_server_url(release_server_url); - std::string serialized_string; - file.SerializeToString(&serialized_string); - - // calculate SHA hash - std::string hash; - if (!Hash(serialized_string, &hash)) { - LOGW("DeviceFiles::StoreLicense: 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); + std::string serialized_file; + file.SerializeToString(&serialized_file); std::string file_name = key_set_id + kLicenseFileNameExt; - return StoreFile(file_name.c_str(), serialized_string); + return StoreFile(file_name.c_str(), serialized_file); } bool DeviceFiles::RetrieveLicense(const std::string& key_set_id, @@ -220,29 +199,12 @@ bool DeviceFiles::RetrieveLicense(const std::string& key_set_id, return false; } - std::string serialized_hashed_file; + std::string serialized_file; std::string file_name = key_set_id + kLicenseFileNameExt; - if (!RetrieveFile(file_name.c_str(), &serialized_hashed_file)) return false; - - HashedFile hashed_file; - if (!hashed_file.ParseFromString(serialized_hashed_file)) { - LOGW("DeviceFiles::RetrieveLicense: Unable to parse hash file"); - return false; - } - - std::string hash; - if (!Hash(hashed_file.file(), &hash)) { - LOGW("DeviceFiles::RetrieveLicense: Hash computation failed"); - return false; - } - - if (hash.compare(hashed_file.hash())) { - LOGW("DeviceFiles::RetrieveLicense: Hash mismatch"); - return false; - } + if (!RetrieveFile(file_name.c_str(), &serialized_file)) return false; video_widevine_client::sdk::File file; - if (!file.ParseFromString(hashed_file.file())) { + if (!file.ParseFromString(serialized_file)) { LOGW("DeviceFiles::RetrieveLicense: Unable to parse file"); return false; } @@ -352,18 +314,144 @@ bool DeviceFiles::LicenseExists(const std::string& key_set_id) { return file_->Exists(path); } -bool DeviceFiles::Hash(const std::string& data, std::string* hash) { - if (!hash) return false; +bool DeviceFiles::StoreUsageInfo(const std::string& provider_session_token, + const CdmKeyMessage& key_request, + const CdmKeyResponse& key_response) { + if (!initialized_) { + LOGW("DeviceFiles::StoreUsageInfo: not initialized"); + return false; + } + + std::string serialized_file; + video_widevine_client::sdk::File file; + if (!RetrieveFile(kUsageInfoFileName, &serialized_file)) { + file.set_type(video_widevine_client::sdk::File::USAGE_INFO); + file.set_version(video_widevine_client::sdk::File::VERSION_1); + } else { + if (!file.ParseFromString(serialized_file)) { + LOGW("DeviceFiles::StoreUsageInfo: Unable to parse file"); + return false; + } + } + + UsageInfo* usage_info = file.mutable_usage_info(); + UsageInfo_ProviderSession* provider_session = usage_info->add_sessions(); + + provider_session->set_token(provider_session_token.data(), + provider_session_token.size()); + provider_session->set_license_request(key_request.data(), + key_request.size()); + provider_session->set_license(key_response.data(), + key_response.size()); + + file.SerializeToString(&serialized_file); + return StoreFile(kUsageInfoFileName, serialized_file); +} + +bool DeviceFiles::DeleteUsageInfo(const std::string& provider_session_token) { + if (!initialized_) { + LOGW("DeviceFiles::DeleteUsageInfo: not initialized"); + return false; + } + + std::string serialized_file; + if (!RetrieveFile(kUsageInfoFileName, &serialized_file)) return false; + + video_widevine_client::sdk::File file; + if (!file.ParseFromString(serialized_file)) { + LOGW("DeviceFiles::DeleteUsageInfo: Unable to parse file"); + return false; + } + + UsageInfo* updated_info = file.mutable_usage_info(); + UsageInfo info(*(const_cast(updated_info))); + updated_info->clear_sessions(); + bool found = false; + for (int i = 0; i < info.sessions_size(); ++i) { + if (info.sessions(i).token().compare(provider_session_token) == 0) { + found = true; + } else { + updated_info->add_sessions()->set_token(info.sessions(i).token()); + updated_info->add_sessions()->set_license_request( + info.sessions(i).license_request()); + updated_info->add_sessions()->set_license(info.sessions(i).license()); + } + } + + if (!found) { + LOGW("DeviceFiles::DeleteUsageInfo: Unable to find provider session " + "token: %s", b2a_hex(provider_session_token).c_str()); + return false; + } + + file.SerializeToString(&serialized_file); + return StoreFile(kUsageInfoFileName, serialized_file); +} + +bool DeviceFiles::DeleteUsageInfo() { + if (!initialized_) { + LOGW("DeviceFiles::DeleteUsageInfo: not initialized"); + return false; + } + + std::string path; + if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) { + LOGW("DeviceFiles::DeleteUsageInfo: Unable to get base path"); + return false; + } + path.append(kUsageInfoFileName); + + return file_->Remove(path); +} + +bool DeviceFiles::RetrieveUsageInfo(std::vector< + std::pair >* usage_info) { + if (!initialized_) { + LOGW("DeviceFiles::RetrieveUsageInfo: not initialized"); + return false; + } + + if (NULL == usage_info) { + LOGW("DeviceFiles::RetrieveUsageInfo: license destination not " + "provided"); + return false; + } + + std::string serialized_file; + if (!RetrieveFile(kUsageInfoFileName, &serialized_file)) { + std::string path; + if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) { + return false; + } + + path += kUsageInfoFileName; + + if (!file_->Exists(path) || 0 == file_->FileSize(path)) { + usage_info->resize(0); + return true; + } + + return false; + } + + video_widevine_client::sdk::File file; + if (!file.ParseFromString(serialized_file)) { + LOGW("DeviceFiles::RetrieveUsageInfo: Unable to parse file"); + return false; + } + + usage_info->resize(file.usage_info().sessions_size()); + for (int i = 0; i < file.usage_info().sessions_size(); ++i) { + (*usage_info)[i] = + std::make_pair(file.usage_info().sessions(i).license_request(), + file.usage_info().sessions(i).license()); + } - hash->resize(SHA256_DIGEST_LENGTH); - SHA256_CTX sha256; - SHA256_Init(&sha256); - SHA256_Update(&sha256, data.data(), data.size()); - SHA256_Final(reinterpret_cast(&(*hash)[0]), &sha256); return true; } -bool DeviceFiles::StoreFile(const char* name, const std::string& data) { +bool DeviceFiles::StoreFile(const char* name, + const std::string& serialized_file) { if (!file_) { LOGW("DeviceFiles::StoreFile: Invalid file handle"); return false; @@ -374,6 +462,21 @@ bool DeviceFiles::StoreFile(const char* name, const std::string& data) { return false; } + // calculate SHA hash + std::string hash; + if (!Hash(serialized_file, &hash)) { + LOGW("DeviceFiles::StoreFile: Hash computation failed"); + return false; + } + + // Fill in hashed file data + HashedFile hash_file; + hash_file.set_file(serialized_file); + hash_file.set_hash(hash); + + std::string serialized_hash_file; + hash_file.SerializeToString(&serialized_hash_file); + std::string path; if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) { LOGW("DeviceFiles::StoreFile: Unable to get base path"); @@ -391,19 +494,24 @@ bool DeviceFiles::StoreFile(const char* name, const std::string& data) { return false; } - ssize_t bytes = file_->Write(data.data(), data.size()); + ssize_t bytes = file_->Write(serialized_hash_file.data(), + serialized_hash_file.size()); file_->Close(); - if (bytes != static_cast(data.size())) { - LOGW("DeviceFiles::StoreFile: write failed: %d %d", data.size(), bytes); + if (bytes != static_cast(serialized_hash_file.size())) { + LOGW("DeviceFiles::StoreFile: write failed: (actual: %d, expected: %d)", + bytes, + serialized_hash_file.size()); return false; } - LOGV("DeviceFiles::StoreFile: success: %s (%db)", path.c_str(), data.size()); + LOGV("DeviceFiles::StoreFile: success: %s (%db)", + path.c_str(), + serialized_hash_file.size()); return true; } -bool DeviceFiles::RetrieveFile(const char* name, std::string* data) { +bool DeviceFiles::RetrieveFile(const char* name, std::string* serialized_file) { if (!file_) { LOGW("DeviceFiles::RetrieveFile: Invalid file handle"); return false; @@ -414,8 +522,8 @@ bool DeviceFiles::RetrieveFile(const char* name, std::string* data) { return false; } - if (!data) { - LOGW("DeviceFiles::RetrieveFile: Unspecified data parameter"); + if (!serialized_file) { + LOGW("DeviceFiles::RetrieveFile: Unspecified serialized_file parameter"); return false; } @@ -434,7 +542,7 @@ bool DeviceFiles::RetrieveFile(const char* name, std::string* data) { ssize_t bytes = file_->FileSize(path); if (bytes <= 0) { - LOGW("DeviceFiles::RetrieveFile: File size invalid: %d", path.c_str()); + LOGW("DeviceFiles::RetrieveFile: File size invalid: %s", path.c_str()); return false; } @@ -442,17 +550,37 @@ bool DeviceFiles::RetrieveFile(const char* name, std::string* data) { return false; } - data->resize(bytes); - bytes = file_->Read(&(*data)[0], data->size()); + std::string serialized_hash_file; + serialized_hash_file.resize(bytes); + bytes = file_->Read(&serialized_hash_file[0], serialized_hash_file.size()); file_->Close(); - if (bytes != static_cast(data->size())) { + if (bytes != static_cast(serialized_hash_file.size())) { LOGW("DeviceFiles::RetrieveFile: read failed"); return false; } LOGV("DeviceFiles::RetrieveFile: success: %s (%db)", path.c_str(), - data->size()); + serialized_hash_file.size()); + + HashedFile hash_file; + if (!hash_file.ParseFromString(serialized_hash_file)) { + LOGW("DeviceFiles::RetrieveFile: Unable to parse hash file"); + return false; + } + + std::string hash; + if (!Hash(hash_file.file(), &hash)) { + LOGW("DeviceFiles::RetrieveFile: Hash computation failed"); + return false; + } + + if (hash.compare(hash_file.hash())) { + LOGW("DeviceFiles::RetrieveFile: Hash mismatch"); + return false; + } + + *serialized_file = hash_file.file(); return true; } @@ -528,4 +656,14 @@ std::string DeviceFiles::GetLicenseFileNameExtension() { return kLicenseFileNameExt; } +std::string DeviceFiles::GetUsageInfoFileName() { + return kUsageInfoFileName; +} + +void DeviceFiles::SetTestFile(File* file) { + if (file_) delete file_; + file_ = file; + test_file_ = true; +} + } // namespace wvcdm diff --git a/core/src/device_files.proto b/core/src/device_files.proto index d561c831..fb57ca9e 100644 --- a/core/src/device_files.proto +++ b/core/src/device_files.proto @@ -33,10 +33,21 @@ message License { optional bytes release_server_url = 7; } +message UsageInfo { + message ProviderSession { + optional bytes token = 1; + optional bytes license_request = 2; + optional bytes license = 3; + } + + repeated ProviderSession sessions = 1; +} + message File { enum FileType { DEVICE_CERTIFICATE = 1; LICENSE = 2; + USAGE_INFO = 3; } enum FileVersion { @@ -47,9 +58,11 @@ message File { optional FileVersion version = 2 [default = VERSION_1]; optional DeviceCertificate device_certificate = 3; optional License license = 4; + optional UsageInfo usage_info = 5; } message HashedFile { optional bytes file = 1; + // A raw (not hex-encoded) SHA256, taken over the bytes of 'file'. optional bytes hash = 2; } diff --git a/core/src/license.cpp b/core/src/license.cpp index 0e9f4226..232c1189 100644 --- a/core/src/license.cpp +++ b/core/src/license.cpp @@ -6,6 +6,7 @@ #include "crypto_key.h" #include "crypto_session.h" +#include "device_files.h" #include "log.h" #include "policy_engine.h" #include "properties.h" @@ -81,15 +82,16 @@ using video_widevine_server::sdk::ClientIdentification; using video_widevine_server::sdk::ClientIdentification_NameValue; using video_widevine_server::sdk::DeviceCertificate; using video_widevine_server::sdk::EncryptedClientIdentification; +using video_widevine_server::sdk::License; +using video_widevine_server::sdk::License_KeyContainer; +using video_widevine_server::sdk::LicenseError; +using video_widevine_server::sdk::LicenseIdentification; using video_widevine_server::sdk::LicenseRequest; using video_widevine_server::sdk::LicenseRequest_ContentIdentification; using video_widevine_server::sdk::LicenseRequest_ContentIdentification_CENC; using video_widevine_server::sdk::LicenseRequest_ContentIdentification_WebM; using video_widevine_server::sdk:: LicenseRequest_ContentIdentification_ExistingLicense; -using video_widevine_server::sdk::License; -using video_widevine_server::sdk::License_KeyContainer; -using video_widevine_server::sdk::LicenseError; using video_widevine_server::sdk::SignedDeviceCertificate; using video_widevine_server::sdk::SignedMessage; @@ -424,7 +426,19 @@ bool CdmLicense::PrepareKeyUpdateRequest(bool is_renewal, LicenseRequest_ContentIdentification_ExistingLicense* current_license = license_request.mutable_content_id()->mutable_license(); - current_license->mutable_license_id()->CopyFrom(policy_engine_->license_id()); + LicenseIdentification license_id = policy_engine_->license_id(); + current_license->mutable_license_id()->CopyFrom(license_id); + + if (!is_renewal) { + if (license_id.has_provider_session_token()) { + std::string usage_report; + if (!session_->GenerateUsageReport(license_id.provider_session_token(), + &usage_report)) { + return false; + } + current_license->set_session_usage_table_entry(usage_report); + } + } // Get/set the nonce. This value will be reflected in the Key Control Block // of the license response. @@ -488,7 +502,7 @@ CdmResponseType CdmLicense::HandleKeyResponse( break; case SignedMessage::SERVICE_CERTIFICATE: return CdmLicense::HandleServiceCertificateResponse(signed_response); - case SignedMessage::ERROR: + case SignedMessage::ERROR_RESPONSE: return HandleKeyErrorResponse(signed_response); default: LOGE("CdmLicense::HandleKeyResponse: unrecognized signed message type: %d" @@ -546,6 +560,10 @@ CdmResponseType CdmLicense::HandleKeyResponse( return KEY_ERROR; } + std::string provider_session_token; + if (license.id().has_provider_session_token()) + provider_session_token_ = license.id().provider_session_token(); + if (license.policy().has_renewal_server_url()) { server_url_ = license.policy().renewal_server_url(); } @@ -556,8 +574,8 @@ CdmResponseType CdmLicense::HandleKeyResponse( signed_response.signature(), mac_key_iv, mac_key, - key_array.size(), - &key_array[0]); + key_array, + provider_session_token); if (KEY_ADDED == resp) { loaded_keys_.clear(); @@ -587,7 +605,7 @@ CdmResponseType CdmLicense::HandleKeyUpdateResponse( return KEY_ERROR; } - if (signed_response.type() == SignedMessage::ERROR) { + if (signed_response.type() == SignedMessage::ERROR_RESPONSE) { return HandleKeyErrorResponse(signed_response); } @@ -615,6 +633,14 @@ CdmResponseType CdmLicense::HandleKeyUpdateResponse( server_url_ = license.policy().renewal_server_url(); } } + else { + if (license.id().has_provider_session_token()) { + provider_session_token_ = license.id().provider_session_token(); + session_->ReleaseUsageInformation(signed_response.msg(), + signed_response.signature(), + provider_session_token_); + } + } policy_engine_->UpdateLicense(license); @@ -631,8 +657,9 @@ CdmResponseType CdmLicense::HandleKeyUpdateResponse( } bool CdmLicense::RestoreOfflineLicense( - CdmKeyMessage& license_request, CdmKeyResponse& license_response, - CdmKeyResponse& license_renewal_response) { + const CdmKeyMessage& license_request, + const CdmKeyResponse& license_response, + const CdmKeyResponse& license_renewal_response) { if (license_request.empty() || license_response.empty()) { LOGE( @@ -675,6 +702,77 @@ bool CdmLicense::RestoreOfflineLicense( return true; } +bool CdmLicense::RestoreUsageLicense(const CdmKeyMessage& license_request, + const CdmKeyResponse& license_response) { + + if (license_request.empty() || license_response.empty()) { + LOGE( + "CdmLicense::RestoreUsageLicense: key_request or response empty: %u %u", + license_request.size(), license_response.size()); + return false; + } + + SignedMessage signed_request; + if (!signed_request.ParseFromString(license_request)) { + LOGE("CdmLicense::RestoreUsageLicense: license_request parse failed"); + return false; + } + + if (signed_request.type() != SignedMessage::LICENSE_REQUEST) { + LOGE( + "CdmLicense::RestoreUsageLicense: license request type: expected = %d," + " actual = %d", + SignedMessage::LICENSE_REQUEST, signed_request.type()); + return false; + } + + key_request_ = signed_request.msg(); + + SignedMessage signed_response; + if (!signed_response.ParseFromString(license_response)) { + LOGE("CdmLicense::RestoreUsageLicense: unable to parse signed license" + " response"); + return false; + } + + if (SignedMessage::LICENSE != signed_response.type()) { + LOGE("CdmLicense::RestoreUsageLicense: unrecognized signed message type: %d" + , signed_response.type()); + return false; + } + + if (Properties::use_certificates_as_identification()) { + if (!signed_response.has_session_key()) { + LOGE("CdmLicense::RestoreUsageLicense: no session keys present"); + return false; + } + + if (!session_->GenerateDerivedKeys(key_request_, + signed_response.session_key())) + return false; + } else { + if (!session_->GenerateDerivedKeys(key_request_)) return false; + } + + if (!signed_response.has_signature()) { + LOGE("CdmLicense::RestoreUsageLicense: license response is not signed"); + return false; + } + + License license; + if (!license.ParseFromString(signed_response.msg())) { + LOGE("CdmLicense::RestoreUsageLicense: unable to parse license response"); + return false; + } + + policy_engine_->SetLicense(license); + return true; +} + +bool CdmLicense::IsKeyLoaded(const KeyId& key_id) { + return loaded_keys_.find(key_id) != loaded_keys_.end(); +} + bool CdmLicense::PrepareServiceCertificateRequest(CdmKeyMessage* signed_request, std::string* server_url) { if (!initialized_) { @@ -798,8 +896,4 @@ bool CdmLicense::PrepareContentId(const CdmLicenseType license_type, return true; } -bool CdmLicense::IsKeyLoaded(const KeyId& key_id) { - return loaded_keys_.find(key_id) != loaded_keys_.end(); -} - } // namespace wvcdm diff --git a/core/src/license_protocol.proto b/core/src/license_protocol.proto index b6a9cd2c..6dec3bfd 100644 --- a/core/src/license_protocol.proto +++ b/core/src/license_protocol.proto @@ -27,6 +27,7 @@ message LicenseIdentification { optional bytes purchase_id = 3; optional LicenseType type = 4; optional int32 version = 5; + optional bytes provider_session_token = 6; } message License { @@ -126,6 +127,8 @@ message License { HDCP_NONE = 0; HDCP_V1 = 1; HDCP_V2 = 2; + HDCP_V2_1 = 3; + HDCP_V2_2 = 4; } optional HDCP hdcp = 1 [default = HDCP_NONE]; @@ -139,6 +142,15 @@ message License { optional CGMS cgms_flags = 2 [default = CGMS_NONE]; } + message VideoResolutionConstraint { + // Minimum and maximum video resolutions in the range (height x width). + optional uint32 min_resolution_pixels = 1; + optional uint32 max_resolution_pixels = 2; + // Optional output protection requirements for this range. If not + // specified, the OutputProtection in the KeyContainer applies. + optional OutputProtection required_protection = 3; + } + message OperatorSessionKeyPermissions { // Permissions/key usage flags for operator service keys // (type = OPERATOR_SESSION). @@ -157,12 +169,20 @@ message License { optional OutputProtection requested_protection = 7; optional KeyControl key_control = 8; optional OperatorSessionKeyPermissions operator_session_key_permissions = 9; + // Optional video resolution constraints. If the video resolution of the + // content being decrypted/decoded falls within one of the specified ranges, + // the optional required_protections may be applied. Otherwise an error will + // be reported. + repeated VideoResolutionConstraint video_resolution_constraints = 10; } optional LicenseIdentification id = 1; optional Policy policy = 2; repeated KeyContainer key = 3; optional int64 license_start_time = 4; + optional bool remote_attestation_verified = 5 [default = false]; + // Client token generated by the content provider. Optional. + optional bytes provider_client_token = 6; } enum ProtocolVersion { @@ -187,6 +207,8 @@ message LicenseRequest { message ExistingLicense { optional LicenseIdentification license_id = 1; optional int64 seconds_since_started = 2; + optional int64 seconds_since_last_played = 3; + optional bytes session_usage_table_entry = 4; } // Exactly one of these must be present. @@ -233,11 +255,22 @@ message LicenseError { optional Error error_code = 1; } +message RemoteAttestation { + // Encrypted ClientIdentification message containing the device remote + // attestation certificate. Required. + optional EncryptedClientIdentification certificate = 1; + // Bytes of salt which were added to the remote attestation challenge prior to + // signing it. Required. + optional bytes salt = 2; + // Signed remote attestation challenge + salt. Required. + optional bytes signature = 3; +} + message SignedMessage { enum MessageType { LICENSE_REQUEST = 1; LICENSE = 2; - ERROR = 3; + ERROR_RESPONSE = 3; SERVICE_CERTIFICATE_REQUEST = 4; SERVICE_CERTIFICATE = 5; } @@ -246,10 +279,20 @@ message SignedMessage { optional bytes msg = 2; optional bytes signature = 3; optional bytes session_key = 4; + // Remote attestation data which will be present in the initial license + // request for ChromeOS client devices operating in verified mode. Remote + // attestation challenge data is |msg| field above. Optional. + optional RemoteAttestation remote_attestation = 5; } // This message is used to pass optional data on initial license issuance. message SessionInit { + enum ReplayControl { + NO_SESSION_USAGE = 0; + NONCE_REQUIRED_AND_NEW_SESSION_USAGE = 1; + NONCE_REQUIRED_OR_EXISTING_SESSION_USAGE = 2; + } + optional bytes session_id = 1; optional bytes purchase_id = 2; // master_signing_key should be 128 bits in length. @@ -258,6 +301,19 @@ message SessionInit { // (server || client) HMAC-SHA256 keys. optional bytes signing_key = 4; optional int64 license_start_time = 5; + // Client token for the session. This session is for use by the license + // provider, and is akin to a client cookie. It will be copied to + // License::provider_client_token, and sent back by the client in + // ClientIdentification::provider_client_token in all license requests + // thereafter. + optional bytes provider_client_token = 6; + // Session token for the session. This token is for use by the license + // provider, and is akin to a session cookie. It will be copied to + // LicenseIdentfication::provider_session_token, and sent back in all + // license renewal and release requests for the session thereafter. + optional bytes provider_session_token = 7; + // Replay control indicator which will be encoded into V9+ KeyControl blocks. + optional ReplayControl replay_control = 8 [default = NO_SESSION_USAGE]; } // This message is used by the server to preserve and restore session state. @@ -280,7 +336,7 @@ message SessionState { // in the case of X509 certificates, the certificate authority to use. message ProvisioningOptions { enum CertificateType { - RSA_WIDEVINE = 0; // Default. The original certificate type. + WIDEVINE_DRM = 0; // Default. The original certificate type. X509 = 1; // X.509 certificate. } @@ -336,6 +392,7 @@ message ClientIdentification { enum TokenType { KEYBOX = 0; DEVICE_CERTIFICATE = 1; + REMOTE_ATTESTATION_CERTIFICATE = 2; } message NameValue { @@ -343,12 +400,36 @@ message ClientIdentification { optional string value = 2; } + // Capabilities which not all clients may support. Used for the license + // exchange protocol only. + message ClientCapabilities { + enum HdcpVersion { + HDCP_NONE = 0; + HDCP_V1 = 1; + HDCP_V2 = 2; + HDCP_V2_1 = 3; + HDCP_V2_2 = 4; + } + + optional bool client_token = 1 [default = false]; + optional bool session_token = 2 [default = false]; + optional bool video_resolution_constraints = 3 [default = false]; + optional HdcpVersion max_hdcp_version = 4 [default = HDCP_NONE]; + } + // Type of factory-provisioned device root of trust. Optional. optional TokenType type = 1 [default = KEYBOX]; // Factory-provisioned device root of trust. Required. optional bytes token = 2; // Optional client information name/value pairs. repeated NameValue client_info = 3; + // Client token generated by the content provider. Optional. + optional bytes provider_client_token = 4; + // Number of licenses received by the client to which the token above belongs. + // Only present if client_token is specified. + optional uint32 license_counter = 5; + // List of non-baseline client capabilities. + optional ClientCapabilities client_capabilities = 6; } // EncryptedClientIdentification message used to hold ClientIdentification @@ -359,16 +440,16 @@ message EncryptedClientIdentification { optional string service_id = 1; // Serial number for the service certificate for which ClientIdentification is // encrypted. - optional string service_certificate_serial_number = 2; - // Serialized ClientIdentification message, encrypted with the privacy key - // using AES-128-CBC with PKCS#5 padding. + optional bytes service_certificate_serial_number = 2; + // Serialized ClientIdentification message, encrypted with the privacy key using + // AES-128-CBC with PKCS#5 padding. optional bytes encrypted_client_id = 3; // Initialization vector needed to decrypt encrypted_client_id. optional bytes encrypted_client_id_iv = 4; // AES-128 privacy key, encrytped with the service public public key using // RSA-OAEP. optional bytes encrypted_privacy_key = 5; -}; +} // ---------------------------------------------------------------------------- // device_certificate.proto @@ -400,9 +481,10 @@ message DeviceCertificate { // Widevine system ID for the device. Required for intermediate and // user device certificates. optional uint32 system_id = 5; - // True if the certificate corresponds to a test (non production) device or - // service. Optional. - optional bool test_device = 6 [default = false]; + // Deprecated field, which used to indicate whether the device was a test + // (non-production) device. The test_device field in ProvisionedDeviceInfo + // below should be observed instead. + optional bool test_device_deprecated = 6 [deprecated = true]; // Service identifier (web origin) for the service which owns the certificate. // Required for service certificates. optional string service_id = 7; diff --git a/core/src/privacy_crypto.cpp b/core/src/privacy_crypto.cpp index 422e63dd..70bfd5d3 100644 --- a/core/src/privacy_crypto.cpp +++ b/core/src/privacy_crypto.cpp @@ -7,38 +7,60 @@ #include "privacy_crypto.h" +#include +#include +#include +#include +#include +#include +#include + #include "log.h" -#include "openssl/aes.h" -#include "openssl/bio.h" -#include "openssl/err.h" -#include "openssl/pem.h" -#include "openssl/sha.h" namespace { const int kPssSaltLength = 20; const int kRsaPkcs1OaepPaddingLength = 41; + +RSA* GetKey(const std::string& serialized_key) { + BIO* bio = BIO_new_mem_buf(const_cast(serialized_key.data()), + serialized_key.size()); + if (bio == NULL) { + LOGE("GetKey: BIO_new_mem_buf returned NULL"); + return NULL; + } + RSA* key = d2i_RSAPublicKey_bio(bio, NULL); + BIO_free(bio); + + if (key == NULL) { + LOGE("GetKey: RSA key deserialization failure: %s", + ERR_error_string(ERR_get_error(), NULL)); + return NULL; + } + + return key; +} + +void FreeKey(RSA* key) { + if (key != NULL) { + RSA_free(key); + } +} + } // namespace namespace wvcdm { +AesCbcKey::AesCbcKey() {} + +AesCbcKey::~AesCbcKey() {} + bool AesCbcKey::Init(const std::string& key) { - if (key.empty()) { - LOGE("AesCbcKey::Init: no key provided"); - return false; - } if (key.size() != AES_BLOCK_SIZE) { LOGE("AesCbcKey::Init: unexpected key size: %d", key.size()); return false; } - EVP_CIPHER_CTX_init(&ctx_); - if (EVP_EncryptInit(&ctx_, EVP_aes_128_cbc(), - reinterpret_cast(&key[0]), NULL) == 0) { - LOGE("AesCbcKey::Init: AES CBC key setup failure: %s", - ERR_error_string(ERR_get_error(), NULL)); - return false; - } - initialized_ = true; + key_ = key; return true; } @@ -60,14 +82,16 @@ bool AesCbcKey::Encrypt(const std::string& in, std::string* out, LOGE("AesCbcKey::Encrypt: crypttext destination not provided"); return false; } - if (!initialized_) { + if (key_.empty()) { LOGE("AesCbcKey::Encrypt: AES key not initialized"); return false; } - if (EVP_EncryptInit(&ctx_, NULL, NULL, - reinterpret_cast(iv->data())) == 0) { - LOGE("AesCbcKey::Encrypt: AES CBC iv setup failure: %s", + EVP_CIPHER_CTX ctx; + if (EVP_EncryptInit(&ctx, EVP_aes_128_cbc(), + reinterpret_cast(&key_[0]), + reinterpret_cast(&(*iv)[0])) == 0) { + LOGE("AesCbcKey::Encrypt: AES CBC setup failure: %s", ERR_error_string(ERR_get_error(), NULL)); return false; } @@ -75,7 +99,7 @@ bool AesCbcKey::Encrypt(const std::string& in, std::string* out, out->resize(in.size() + AES_BLOCK_SIZE); int out_length = out->size(); if (EVP_EncryptUpdate( - &ctx_, reinterpret_cast(&(*out)[0]), &out_length, + &ctx, reinterpret_cast(&(*out)[0]), &out_length, reinterpret_cast(const_cast(in.data())), in.size()) == 0) { LOGE("AesCbcKey::Encrypt: encryption failure: %s", @@ -84,7 +108,7 @@ bool AesCbcKey::Encrypt(const std::string& in, std::string* out, } int padding = 0; - if (EVP_EncryptFinal(&ctx_, reinterpret_cast(&(*out)[out_length]), + if (EVP_EncryptFinal(&ctx, reinterpret_cast(&(*out)[out_length]), &padding) == 0) { LOGE("AesCbcKey::Encrypt: PKCS7 padding failure: %s", ERR_error_string(ERR_get_error(), NULL)); @@ -95,34 +119,17 @@ bool AesCbcKey::Encrypt(const std::string& in, std::string* out, return true; } -RsaPublicKey::~RsaPublicKey() { - if (key_ != NULL) { - RSA_free(key_); - } -} +RsaPublicKey::RsaPublicKey() {} + +RsaPublicKey::~RsaPublicKey() {} bool RsaPublicKey::Init(const std::string& serialized_key) { - if (serialized_key.empty()) { LOGE("RsaPublicKey::Init: no serialized key provided"); return false; } - BIO* bio = BIO_new_mem_buf(const_cast(serialized_key.data()), - serialized_key.size()); - if (bio == NULL) { - LOGE("RsaPublicKey::Init: BIO_new_mem_buf returned NULL"); - return false; - } - key_ = d2i_RSAPublicKey_bio(bio, NULL); - BIO_free(bio); - - if (key_ == NULL) { - LOGE("RsaPublicKey::Init: RSA key deserialization failure: %s", - ERR_error_string(ERR_get_error(), NULL)); - return false; - } - + serialized_key_ = serialized_key; return true; } @@ -136,36 +143,46 @@ bool RsaPublicKey::Encrypt(const std::string& clear_message, LOGE("RsaPublicKey::Encrypt: no encrypt message buffer provided"); return false; } - if (key_ == NULL) { + if (serialized_key_.empty()) { LOGE("RsaPublicKey::Encrypt: RSA key not initialized"); return false; } - int rsa_size = RSA_size(key_); + RSA* key = GetKey(serialized_key_); + if (key == NULL) { + // Error already logged by GetKey. + return false; + } + + int rsa_size = RSA_size(key); if (static_cast(clear_message.size()) > rsa_size - kRsaPkcs1OaepPaddingLength) { LOGE("RsaPublicKey::Encrypt: message too large to be encrypted (actual %d", " max allowed %d)", clear_message.size(), rsa_size - kRsaPkcs1OaepPaddingLength); + FreeKey(key); return false; } + encrypted_message->assign(rsa_size, 0); if (RSA_public_encrypt( clear_message.size(), const_cast( reinterpret_cast(clear_message.data())), - reinterpret_cast(&(*encrypted_message)[0]), key_, + reinterpret_cast(&(*encrypted_message)[0]), key, RSA_PKCS1_OAEP_PADDING) != rsa_size) { LOGE("RsaPublicKey::Encrypt: encrypt failure: %s", ERR_error_string(ERR_get_error(), NULL)); + FreeKey(key); return false; } + return true; } bool RsaPublicKey::VerifySignature(const std::string& message, const std::string& signature) { - if (key_ == NULL) { + if (serialized_key_.empty()) { LOGE("RsaPublicKey::VerifySignature: RSA key not initialized"); return false; } @@ -173,25 +190,33 @@ bool RsaPublicKey::VerifySignature(const std::string& message, LOGE("RsaPublicKey::VerifySignature: signed message is empty"); return false; } + RSA* key = GetKey(serialized_key_); + if (key == NULL) { + // Error already logged by GetKey. + return false; + } - int rsa_size = RSA_size(key_); + int rsa_size = RSA_size(key); if (static_cast(signature.size()) != rsa_size) { LOGE( "RsaPublicKey::VerifySignature: message signature is of the wrong " "size (expected %d, actual %d)", rsa_size, signature.size()); + FreeKey(key); return false; } + // Decrypt the signature. std::string padded_digest(signature.size(), 0); if (RSA_public_decrypt( signature.size(), const_cast( reinterpret_cast(signature.data())), - reinterpret_cast(&padded_digest[0]), key_, + reinterpret_cast(&padded_digest[0]), key, RSA_NO_PADDING) != rsa_size) { LOGE("RsaPublicKey::VerifySignature: RSA public decrypt failure: %s", ERR_error_string(ERR_get_error(), NULL)); + FreeKey(key); return false; } @@ -202,12 +227,13 @@ bool RsaPublicKey::VerifySignature(const std::string& message, // Verify PSS padding. if (RSA_verify_PKCS1_PSS( - key_, reinterpret_cast(message_digest.data()), + key, reinterpret_cast(message_digest.data()), EVP_sha1(), reinterpret_cast(padded_digest.data()), kPssSaltLength) == 0) { LOGE("RsaPublicKey::VerifySignature: RSA verify failure: %s", ERR_error_string(ERR_get_error(), NULL)); + FreeKey(key); return false; } diff --git a/core/src/privacy_crypto_dummy.cpp b/core/src/privacy_crypto_dummy.cpp new file mode 100644 index 00000000..d59d8ddf --- /dev/null +++ b/core/src/privacy_crypto_dummy.cpp @@ -0,0 +1,43 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// +// Description: +// Dummy version of privacy crypto classes for systems which +// can't tolerate OpenSSL as a dependency. +// + +#include "privacy_crypto.h" + +namespace wvcdm { + +AesCbcKey::AesCbcKey() {} + +AesCbcKey::~AesCbcKey() {} + +bool AesCbcKey::Init(const std::string& key) { + return false; +} + +bool AesCbcKey::Encrypt(const std::string& in, std::string* out, + std::string* iv) { + return false; +} + +RsaPublicKey::RsaPublicKey() {} + +RsaPublicKey::~RsaPublicKey() {} + +bool RsaPublicKey::Init(const std::string& serialized_key) { + return false; +} + +bool RsaPublicKey::Encrypt(const std::string& clear_message, + std::string* encrypted_message) { + return false; +} + +bool RsaPublicKey::VerifySignature(const std::string& message, + const std::string& signature) { + return false; +} + +} // namespace wvcdm diff --git a/core/src/string_conversions.cpp b/core/src/string_conversions.cpp index 77034eb0..8fc04124 100644 --- a/core/src/string_conversions.cpp +++ b/core/src/string_conversions.cpp @@ -77,11 +77,11 @@ std::string b2a_hex(const std::string& byte) { // Filename-friendly base64 encoding (RFC4648), commonly referred to // as Base64WebSafeEncode. -// This is the encoding required to interface with the provisioning -// server's Apiary interface as well as 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 '_'. +// +// This is the encoding required to interface with the provisioning server, as +// well as 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) { if (bin_input.empty()) { return std::string(); diff --git a/core/test/cdm_engine_test.cpp b/core/test/cdm_engine_test.cpp index a985bd1c..8793720e 100644 --- a/core/test/cdm_engine_test.cpp +++ b/core/test/cdm_engine_test.cpp @@ -33,7 +33,6 @@ wvcdm::KeyId g_key_id_pssh; wvcdm::KeyId g_key_id_unwrapped; wvcdm::CdmKeySystem g_key_system; 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 @@ -138,16 +137,16 @@ class WvCdmEngineTest : public testing::Test { std::string GetKeyRequestResponse(const std::string& server_url, const std::string& client_auth) { // Use secure connection and chunk transfer coding. - UrlRequest url_request(server_url + client_auth, g_port, true, true); + UrlRequest url_request(server_url + client_auth); if (!url_request.is_connected()) { return ""; } url_request.PostRequest(key_msg_); std::string response; - int resp_bytes = url_request.GetResponse(&response); - LOGD("response:\r\n%s", response.c_str()); - LOGD("end %d bytes response dump", resp_bytes); + bool ok = url_request.GetResponse(&response); + LOGD("response: %s\n", response.c_str()); + EXPECT_TRUE(ok); int status_code = url_request.GetStatusCode(response); EXPECT_EQ(kHttpOk, status_code); @@ -254,14 +253,12 @@ int main(int argc, char **argv) { // The following variables are configurable through command line options. g_license_server.assign(config.license_server()); g_key_id_pssh.assign(config.key_id()); - g_port.assign(config.port()); std::string license_server(g_license_server); int show_usage = 0; static const struct option long_options[] = { { "use_full_path", no_argument, &g_use_full_path, 0 }, { "keyid", required_argument, NULL, 'k' }, - { "port", required_argument, NULL, 'p' }, { "server", required_argument, NULL, 's' }, { "vmodule", required_argument, NULL, 0 }, { "v", required_argument, NULL, 0 }, @@ -277,11 +274,6 @@ int main(int argc, char **argv) { g_key_id_pssh.assign(optarg); break; } - case 'p': { - g_port.clear(); - g_port.assign(optarg); - break; - } case 's': { g_license_server.clear(); g_license_server.assign(optarg); @@ -304,11 +296,6 @@ int main(int argc, char **argv) { std::cout << " enclose multiple arguments in '' when using adb shell" << std::endl; std::cout << " e.g. adb shell '" << argv[0] << " --server=\"url\"'" << std::endl << std::endl; - std::cout << std::setw(30) << std::left << " --port="; - std::cout << "specifies the port number, in decimal format" << std::endl; - std::cout << std::setw(30) << std::left << " "; - std::cout << "default: " << g_port << std::endl; - std::cout << std::setw(30) << std::left << " --server="; std::cout << "configure the license server url, please include http[s] in the url" << std::endl; std::cout << std::setw(30) << std::left << " "; @@ -327,12 +314,10 @@ int main(int argc, char **argv) { std::cout << std::endl; std::cout << "Server: " << g_license_server << std::endl; - std::cout << "Port: " << g_port << std::endl; std::cout << "KeyID: " << g_key_id_pssh << std::endl << std::endl; g_key_id_pssh = wvcdm::a2bs_hex(g_key_id_pssh); config.set_license_server(g_license_server); - config.set_port(g_port); config.set_key_id(g_key_id_pssh); // Extract the key ID from the PSSH box. diff --git a/core/test/config_test_env.cpp b/core/test/config_test_env.cpp index ba168000..927eb7f7 100644 --- a/core/test/config_test_env.cpp +++ b/core/test/config_test_env.cpp @@ -3,14 +3,23 @@ #include "config_test_env.h" namespace { +const std::string kWidevineKeySystem = "com.widevine.alpha"; + // Youtube Content Protection license server data const std::string kYtCpLicenseServer = "http://wv-ref-eme-player.appspot.com/proxy"; const std::string kYtCpClientAuth = ""; const std::string kYtCpKeyId = - "000000347073736800000000" // blob size and pssh - "EDEF8BA979D64ACEA3C827DCD51D21ED00000014" // Widevine system id - "0801121030313233343536373839616263646566"; // pssh data + "000000427073736800000000" // blob size and pssh + "EDEF8BA979D64ACEA3C827DCD51D21ED00000022" // Widevine system id + "08011a0d7769646576696e655f7465737422" // pssh data (streaming) + "0f73747265616d696e675f636c697031"; + +const std::string kYtCpOfflineKeyId = + "000000407073736800000000" // blob size and pssh + "EDEF8BA979D64ACEA3C827DCD51D21ED00000020" // Widevine system id + "08011a0d7769646576696e655f7465737422" //pssh data (offline) + "0d6f66666c696e655f636c697031"; // Youtube license server data const std::string kYtLicenseServer = @@ -34,6 +43,13 @@ const std::string kGpLicenseServer = const std::string kGpClientAuth = "?source=YOUTUBE&video_id=EGHC6OHNbOo&oauth=ya.gtsqawidevine"; +const std::string kGpClientOfflineQueryParameters = + "&offline=true"; +const std::string kGpClientOfflineRenewalQueryParameters = + "&offline=true&renewal=true"; +const std::string kGpClientOfflineReleaseQueryParameters = + "&offline=true&release=true"; + const std::string kGpKeyId = "000000347073736800000000" // blob size and pssh "edef8ba979d64acea3c827dcd51d21ed00000014" // Widevine system id @@ -63,23 +79,49 @@ const std::string kServerSdkLicenseServer = const wvcdm::ConfigTestEnv::LicenseServerConfiguration license_servers[] = { { wvcdm::kGooglePlayServer, kGpLicenseServer, kGpClientAuth, kGpKeyId, - kDefaultHttpsPort, true, true }, + kGpKeyId }, { wvcdm::kYouTubeContentProtectionServer, kYtCpLicenseServer, - kYtCpClientAuth, kYtCpKeyId, kDefaultHttpPort, false, false } + kYtCpClientAuth, kYtCpKeyId, kYtCpOfflineKeyId } }; } // namespace namespace wvcdm { -ConfigTestEnv::ConfigTestEnv(LicenseServerId server_id) - : client_auth_(license_servers[server_id].client_tag), - key_id_(license_servers[server_id].key_id), - key_system_("com.widevine.alpha"), - license_server_(license_servers[server_id].url), - port_(license_servers[server_id].port), - provisioning_server_url_(kProductionProvisioningServerUrl), - use_chunked_transfer_(license_servers[server_id].use_chunked_transfer), - use_secure_transfer_(license_servers[server_id].use_secure_transfer), - wrong_key_id_(kWrongKeyId) {} +ConfigTestEnv::ConfigTestEnv(LicenseServerId server_id) { + Init(server_id); +} + +ConfigTestEnv::ConfigTestEnv(LicenseServerId server_id, bool streaming) { + Init(server_id); + if (!streaming) + key_id_ = license_servers[server_id].offline_key_id; +} + +ConfigTestEnv::ConfigTestEnv(LicenseServerId server_id, bool streaming, + bool renew, bool release) { + Init(server_id); + if (!streaming) { + key_id_ = license_servers[server_id].offline_key_id; + + if (wvcdm::kGooglePlayServer == server_id) { + if (renew) { + client_auth_.append(kGpClientOfflineRenewalQueryParameters); + } else if (release) { + client_auth_.append(kGpClientOfflineReleaseQueryParameters); + } else { + client_auth_.append(kGpClientOfflineQueryParameters); + } + } + } +} + +void ConfigTestEnv::Init(LicenseServerId server_id) { + client_auth_ = license_servers[server_id].client_tag; + key_id_ = license_servers[server_id].key_id; + key_system_ = kWidevineKeySystem; + license_server_ = license_servers[server_id].url; + provisioning_server_url_ = kProductionProvisioningServerUrl; + wrong_key_id_= kWrongKeyId; +} } // namespace wvcdm diff --git a/core/test/config_test_env.h b/core/test/config_test_env.h index 230c86cd..34db6a2f 100644 --- a/core/test/config_test_env.h +++ b/core/test/config_test_env.h @@ -6,11 +6,6 @@ #include #include "wv_cdm_types.h" -namespace { -const std::string kDefaultHttpsPort = "443"; -const std::string kDefaultHttpPort = "80"; -} - namespace wvcdm { typedef enum { kGooglePlayServer, @@ -25,24 +20,22 @@ class ConfigTestEnv { std::string url; std::string client_tag; std::string key_id; - std::string port; - bool use_chunked_transfer; - bool use_secure_transfer; + std::string offline_key_id; } LicenseServerConfiguration; explicit ConfigTestEnv(LicenseServerId server_id); + ConfigTestEnv(LicenseServerId server_id, bool streaming); + ConfigTestEnv(LicenseServerId server_id, bool streaming, bool renew, + bool release); ~ConfigTestEnv() {}; const std::string& client_auth() const { return client_auth_; } const KeyId& key_id() const { return key_id_; } const CdmKeySystem& key_system() const { return key_system_; } const std::string& license_server() const { return license_server_; } - const std::string& port() const { return port_; } const std::string& provisioning_server_url() const { return provisioning_server_url_; } - bool use_chunked_transfer() { return use_chunked_transfer_; } - bool use_secure_transfer() { return use_secure_transfer_; } const KeyId& wrong_key_id() const { return wrong_key_id_; } void set_key_id(KeyId& key_id) { key_id_.assign(key_id); } @@ -52,17 +45,15 @@ class ConfigTestEnv { void set_license_server(std::string& license_server) { license_server_.assign(license_server); } - void set_port(std::string& port) { port_.assign(port); } private: + void Init(LicenseServerId server_id); + std::string client_auth_; KeyId key_id_; CdmKeySystem key_system_; std::string license_server_; - std::string port_; std::string provisioning_server_url_; - bool use_chunked_transfer_; - bool use_secure_transfer_; KeyId wrong_key_id_; CORE_DISALLOW_COPY_AND_ASSIGN(ConfigTestEnv); diff --git a/core/test/device_files_unittest.cpp b/core/test/device_files_unittest.cpp index 43e4baf7..0d89a2db 100644 --- a/core/test/device_files_unittest.cpp +++ b/core/test/device_files_unittest.cpp @@ -26,7 +26,11 @@ using ::testing::StrEq; namespace { const uint32_t kCertificateLen = 700; const uint32_t kWrappedKeyLen = 500; -const uint32_t kProtobufEstimatedLen = 75; + +const uint32_t kProtobufEstimatedOverhead = 75; +const uint32_t kLicenseRequestLen = 300; +const uint32_t kLicenseLen = 500; +const uint32_t kProviderSessionTokenLen = 128; // Structurally valid test certificate. // The data elements in this module are used to test the storage and @@ -901,8 +905,7 @@ LicenseInfo license_update_test_data[] = { "D30B08B2C4673551293A2E68747470733A2F2F746573742E676F6F676C65" "2E636F6D2F6C6963656E73652F47657443656E634C6963656E736512200A" "1C78D0E574D0827C3AE78A05EEC90BAC31D10686EC19EB0599F75B2D1AB4" - "C5" -)}, + "C5")}, // license being released. all fields are identical except for license // state and hashed file data {"", DeviceFiles::kLicenseStateReleasing, "", "", "", "", "", "", @@ -1001,6 +1004,287 @@ LicenseInfo license_update_test_data[] = { "7186A244EF561E3B07DC459BC681A0798B180667EA448327F6BBBD30212A" "49")}}; +struct UsageInfo { + std::string provider_session_token; + std::string license_request; + std::string license; + std::string file_data; +}; + +UsageInfo kUsageInfoTestData[] = { + {"", "", "", // 0 usage info records + wvcdm::a2bs_hex( + "0A06080210012A00122095053501C5FA405B7EF01DA94685C6B20CB36493" + "A9CF1653B720E2BEA3B77929")}, + {// 1 usage info record + wvcdm::a2bs_hex( + "924B035FBDA56AE5EF0ED05A08DE7AECC8ABE1835E0C4A548F7803937F4C3B4520EB7" + "F3334FFCDFA00DE56408F09D5019FCE87072D0DC6789817468974B2EA51EE3944B8D7" + "E0A88E4F16EBB80F03BD845231A01E6146841CBAEF0134DCD9300DB2D92732992C0F2" + "310D8E386FB31C67B9477010DEF9D99C4272589572A26A17E"), + wvcdm::a2bs_hex( + "1E6FFBE66FC6153E7749906EC8F684E819467E16CAF317F315DB32B6D3FDD1A8E8A09" + "4174D92D063B88E4835EAB78BD09541EA7FE72F132EB7364E154BC1548FC40EC70927" + "75531508C95F9ED5D76F36BC0C198C3A33A1F9415B343905D6BE37645E6800F053B1D" + "A9A20286EFCBBC320424ADF7FB6E3D5D8E86C35E576A1A2A37D344A419C0F0034A1B5" + "F767D3C61D90DCA1119E5024C34EDE8FA7DD128696D8C435410F218E52A853AD214FD" + "05D0F8B3CB4832CFCD97FE159E6DEE64CE82CDAEC0321AE71B3BCBAE42DF9EA65E42E" + "151827086EADE71C138B972CC3992CF9ADA944C063816352ED8658D3FA07BE0F32239" + "E74A65932B069AAC4E8386DB59154AF9AEF71448128C66E510445294F44E511BD9B1A" + "F19D4D67E99363093BE888D4B2AB841CAFF252CAD13EDF8E"), + wvcdm::a2bs_hex( + "40FC62339728520E6C0C09907C26F3FB78287231661952A8B699E47AE241B999C029F" + "D2067836DC4BC64F66998A3ECD197DAE36F808A2E5A4C5BF25DD580E52B1C39A8B037" + "72BF82D58929766F2DA04F0E616F92B3A0EB75661B8FF5DE1EB807C990F9E6BA991C8" + "BAD5EB63B37E8663A4E22AA9DB2015D3DF8BED1C8313F85F13B9483C7A39C592436C8" + "B13C23F78F55CE812795335059F7F527CA580306A0AEE5A6D957A91F498F64AA2EFC6" + "780716400E17C7EEA30B2E6523B902986995E003C2D919A7DC7C0122CE9410037A660" + "2B59A63B5C89473D4E02DE35C1F01B12ADB48A3D94D43693F08268FECCC78DAF6F4C3" + "5FA32C538CD73FBF3CEA274B01179C02473486311956E5A0C78E44C59B2F34FF24B06" + "53A6379A2F5F6F51467CAE26D55CC5BBDCFC9BCFA7B8C5CBF82EBE7BD340C3DAE6374" + "D0692052C529AA33D7A6799C8F1F59C78575E51F707013026CC4F83F6B3328EE6FB1A" + "C91929A4491338E93D10EE6193014A73BA241A9A833EA835217894EB4FD4BDB8904A5" + "999928325D0AC31B6D58609EDD9D85E88F74B5BD6FA7BDD83C51EEB91633ED267ACA2" + "E103904BBE4C031A6483858FBAD74DACD01711F7B882749FFFBA0DB6C7D7109D82989" + "C7D4DB5A0F1E7506AC24C89CECAF231EFF99F96AD76E57DABDD3C2DFBA7BAA869A771" + "F561B165987E552824B0C914E708E425C3"), + wvcdm::a2bs_hex( + "0AB307080210012AAC070AA9070A8001924B035FBDA56AE5EF0ED05A08DE7AECC8ABE" + "1835E0C4A548F7803937F4C3B4520EB7F3334FFCDFA00DE56408F09D5019FCE87072D" + "0DC6789817468974B2EA51EE3944B8D7E0A88E4F16EBB80F03BD845231A01E6146841" + "CBAEF0134DCD9300DB2D92732992C0F2310D8E386FB31C67B9477010DEF9D99C42725" + "89572A26A17E12AC021E6FFBE66FC6153E7749906EC8F684E819467E16CAF317F315D" + "B32B6D3FDD1A8E8A094174D92D063B88E4835EAB78BD09541EA7FE72F132EB7364E15" + "4BC1548FC40EC7092775531508C95F9ED5D76F36BC0C198C3A33A1F9415B343905D6B" + "E37645E6800F053B1DA9A20286EFCBBC320424ADF7FB6E3D5D8E86C35E576A1A2A37D" + "344A419C0F0034A1B5F767D3C61D90DCA1119E5024C34EDE8FA7DD128696D8C435410" + "F218E52A853AD214FD05D0F8B3CB4832CFCD97FE159E6DEE64CE82CDAEC0321AE71B3" + "BCBAE42DF9EA65E42E151827086EADE71C138B972CC3992CF9ADA944C063816352ED8" + "658D3FA07BE0F32239E74A65932B069AAC4E8386DB59154AF9AEF71448128C66E5104" + "45294F44E511BD9B1AF19D4D67E99363093BE888D4B2AB841CAFF252CAD13EDF8E1AF" + "40340FC62339728520E6C0C09907C26F3FB78287231661952A8B699E47AE241B999C0" + "29FD2067836DC4BC64F66998A3ECD197DAE36F808A2E5A4C5BF25DD580E52B1C39A8B" + "03772BF82D58929766F2DA04F0E616F92B3A0EB75661B8FF5DE1EB807C990F9E6BA99" + "1C8BAD5EB63B37E8663A4E22AA9DB2015D3DF8BED1C8313F85F13B9483C7A39C59243" + "6C8B13C23F78F55CE812795335059F7F527CA580306A0AEE5A6D957A91F498F64AA2E" + "FC6780716400E17C7EEA30B2E6523B902986995E003C2D919A7DC7C0122CE9410037A" + "6602B59A63B5C89473D4E02DE35C1F01B12ADB48A3D94D43693F08268FECCC78DAF6F" + "4C35FA32C538CD73FBF3CEA274B01179C02473486311956E5A0C78E44C59B2F34FF24" + "B0653A6379A2F5F6F51467CAE26D55CC5BBDCFC9BCFA7B8C5CBF82EBE7BD340C3DAE6" + "374D0692052C529AA33D7A6799C8F1F59C78575E51F707013026CC4F83F6B3328EE6F" + "B1AC91929A4491338E93D10EE6193014A73BA241A9A833EA835217894EB4FD4BDB890" + "4A5999928325D0AC31B6D58609EDD9D85E88F74B5BD6FA7BDD83C51EEB91633ED267A" + "CA2E103904BBE4C031A6483858FBAD74DACD01711F7B882749FFFBA0DB6C7D7109D82" + "989C7D4DB5A0F1E7506AC24C89CECAF231EFF99F96AD76E57DABDD3C2DFBA7BAA869A" + "771F561B165987E552824B0C914E708E425C3122051C8F84C5713500997DC5B325BAE" + "D208B224DFAEB2B034E58046A62F503FED6E")}, + {// 2 usage info records + wvcdm::a2bs_hex( + "7290396E183156BDF830B7BF31BA762CB2675528C9004FD24A61DAFB587ABCF1D36F8" + "7795EE0B3DA0B425616A66C82349B2E3BB8841C1335536865F919ED2AE671487B608B" + "21A362D888E0AB4F7AB7175B82F108617C3503F175435788AECAF7FFBFE76995D93CD" + "79424A843A247A8D8A6054A5B5404C9C057AACAD91A203229"), + wvcdm::a2bs_hex( + "3478A2D76DEB90BE713B03A11037EA7C305D1AF65099E3F2B92C4D4443A8F481C1177" + "DEF0A3CB49BA5F1448A10AF1207AD2D361B4A1F961B4B1F215B76A9A5005B414EF45E" + "AFBCF2636ABFC01413B27DD11871103579F8C041A799E22888D9ADB798E92A5E29BC4" + "6DECBC90991C65FE151C49F18068C1B65D0E90A9ECDA9248B87C120D5FD8EC81D4D36" + "B529FB2DAD39E0D39578B13B158E2B07C752D86F1A9D8160C93930C1F4F9E1D0D8E2C" + "5AB308732EB27722A6BF8BE852624C2BE3E4FE85819B89BEBA6535FCFBE85FA63A57B" + "D0FBAF284C64FFD97A146B76B3F37B576FC091C03E2222FBD24C2211344B7E2417EFC" + "36C4A54DCCC460CF810E7EA8AC6386D6AB567C819FED88A22CE55EF9BBE62C2CBC7AE" + "EDE5E5A69FF3472418CE2F4514496C59D26E72F3BFE0131F"), + wvcdm::a2bs_hex( + "C45FDCB3296A0EBE24FF381E027E6E2EF1AC289C67D3B858330669A81E8131583D2F1" + "40FD64615BDED0ED8316ABFD9C7E887433E1CAA6EA8E0C4F87ADB2A7FC3CF6FF87A7F" + "02AFF03BF5DB640AD8DDB572C41532E673618DCD8C33EF2BFE4E25EE821DF7D742B09" + "90398543B16EFCDBB03C6327B79D3664CED442E894020F4410ECC178C92AAEDFE39DC" + "563AC226FE9E0EF22E1C896C4F2835CDFDCD50B6C4DBA2B27A3B65DE3963D0A5F6E44" + "2A3C32008AB9D1ACBE4F366990EB43F8EE213B71E98DA090282680ABDD649BECA8970" + "0764561379F1DD23490CE967632ECA349AF8E1CBFA1F3A4F39F453614C8FFB5A17975" + "6243CB1FDB515834229BC64917C47A2F2E1116FAAC13368015312C31FD41215106469" + "BEE77D0EF2FE10CF645B3E82902EAF53A676933D0EC433949C1833BE52E76602CC3E4" + "E784C002E20624BCE0F38F9CBC478439899DA7F15554D0ACADEC140C00C8FA8FC9886" + "2D9933938781B30CB9C76899B3A48DBF170DDA0A18ED37D77F048ABBC85CB19469638" + "C2A32AA3180CF3943BD6B8C5CB26F2EA70868F18B0707C882054141086997A1AE5B70" + "9D4D0AA2B358990F244BA76C8E40791D29A0C63C9EF620B97FDFFA9B671E5A65AFCC1" + "C94CAACE0443E9D91F14028935BEA3988831BEBBFD3EB7C3A5AC9605B3534712A0912" + "4345ACB09665E357E58946871BC140D365"), + wvcdm::a2bs_hex( + "0ADF0E080210012AD80E0AA9070A8001924B035FBDA56AE5EF0ED05A08DE7AECC8ABE" + "1835E0C4A548F7803937F4C3B4520EB7F3334FFCDFA00DE56408F09D5019FCE87072D" + "0DC6789817468974B2EA51EE3944B8D7E0A88E4F16EBB80F03BD845231A01E6146841" + "CBAEF0134DCD9300DB2D92732992C0F2310D8E386FB31C67B9477010DEF9D99C42725" + "89572A26A17E12AC021E6FFBE66FC6153E7749906EC8F684E819467E16CAF317F315D" + "B32B6D3FDD1A8E8A094174D92D063B88E4835EAB78BD09541EA7FE72F132EB7364E15" + "4BC1548FC40EC7092775531508C95F9ED5D76F36BC0C198C3A33A1F9415B343905D6B" + "E37645E6800F053B1DA9A20286EFCBBC320424ADF7FB6E3D5D8E86C35E576A1A2A37D" + "344A419C0F0034A1B5F767D3C61D90DCA1119E5024C34EDE8FA7DD128696D8C435410" + "F218E52A853AD214FD05D0F8B3CB4832CFCD97FE159E6DEE64CE82CDAEC0321AE71B3" + "BCBAE42DF9EA65E42E151827086EADE71C138B972CC3992CF9ADA944C063816352ED8" + "658D3FA07BE0F32239E74A65932B069AAC4E8386DB59154AF9AEF71448128C66E5104" + "45294F44E511BD9B1AF19D4D67E99363093BE888D4B2AB841CAFF252CAD13EDF8E1AF" + "40340FC62339728520E6C0C09907C26F3FB78287231661952A8B699E47AE241B999C0" + "29FD2067836DC4BC64F66998A3ECD197DAE36F808A2E5A4C5BF25DD580E52B1C39A8B" + "03772BF82D58929766F2DA04F0E616F92B3A0EB75661B8FF5DE1EB807C990F9E6BA99" + "1C8BAD5EB63B37E8663A4E22AA9DB2015D3DF8BED1C8313F85F13B9483C7A39C59243" + "6C8B13C23F78F55CE812795335059F7F527CA580306A0AEE5A6D957A91F498F64AA2E" + "FC6780716400E17C7EEA30B2E6523B902986995E003C2D919A7DC7C0122CE9410037A" + "6602B59A63B5C89473D4E02DE35C1F01B12ADB48A3D94D43693F08268FECCC78DAF6F" + "4C35FA32C538CD73FBF3CEA274B01179C02473486311956E5A0C78E44C59B2F34FF24" + "B0653A6379A2F5F6F51467CAE26D55CC5BBDCFC9BCFA7B8C5CBF82EBE7BD340C3DAE6" + "374D0692052C529AA33D7A6799C8F1F59C78575E51F707013026CC4F83F6B3328EE6F" + "B1AC91929A4491338E93D10EE6193014A73BA241A9A833EA835217894EB4FD4BDB890" + "4A5999928325D0AC31B6D58609EDD9D85E88F74B5BD6FA7BDD83C51EEB91633ED267A" + "CA2E103904BBE4C031A6483858FBAD74DACD01711F7B882749FFFBA0DB6C7D7109D82" + "989C7D4DB5A0F1E7506AC24C89CECAF231EFF99F96AD76E57DABDD3C2DFBA7BAA869A" + "771F561B165987E552824B0C914E708E425C30AA9070A80017290396E183156BDF830" + "B7BF31BA762CB2675528C9004FD24A61DAFB587ABCF1D36F87795EE0B3DA0B425616A" + "66C82349B2E3BB8841C1335536865F919ED2AE671487B608B21A362D888E0AB4F7AB7" + "175B82F108617C3503F175435788AECAF7FFBFE76995D93CD79424A843A247A8D8A60" + "54A5B5404C9C057AACAD91A20322912AC023478A2D76DEB90BE713B03A11037EA7C30" + "5D1AF65099E3F2B92C4D4443A8F481C1177DEF0A3CB49BA5F1448A10AF1207AD2D361" + "B4A1F961B4B1F215B76A9A5005B414EF45EAFBCF2636ABFC01413B27DD11871103579" + "F8C041A799E22888D9ADB798E92A5E29BC46DECBC90991C65FE151C49F18068C1B65D" + "0E90A9ECDA9248B87C120D5FD8EC81D4D36B529FB2DAD39E0D39578B13B158E2B07C7" + "52D86F1A9D8160C93930C1F4F9E1D0D8E2C5AB308732EB27722A6BF8BE852624C2BE3" + "E4FE85819B89BEBA6535FCFBE85FA63A57BD0FBAF284C64FFD97A146B76B3F37B576F" + "C091C03E2222FBD24C2211344B7E2417EFC36C4A54DCCC460CF810E7EA8AC6386D6AB" + "567C819FED88A22CE55EF9BBE62C2CBC7AEEDE5E5A69FF3472418CE2F4514496C59D2" + "6E72F3BFE0131F1AF403C45FDCB3296A0EBE24FF381E027E6E2EF1AC289C67D3B8583" + "30669A81E8131583D2F140FD64615BDED0ED8316ABFD9C7E887433E1CAA6EA8E0C4F8" + "7ADB2A7FC3CF6FF87A7F02AFF03BF5DB640AD8DDB572C41532E673618DCD8C33EF2BF" + "E4E25EE821DF7D742B0990398543B16EFCDBB03C6327B79D3664CED442E894020F441" + "0ECC178C92AAEDFE39DC563AC226FE9E0EF22E1C896C4F2835CDFDCD50B6C4DBA2B27" + "A3B65DE3963D0A5F6E442A3C32008AB9D1ACBE4F366990EB43F8EE213B71E98DA0902" + "82680ABDD649BECA89700764561379F1DD23490CE967632ECA349AF8E1CBFA1F3A4F3" + "9F453614C8FFB5A179756243CB1FDB515834229BC64917C47A2F2E1116FAAC1336801" + "5312C31FD41215106469BEE77D0EF2FE10CF645B3E82902EAF53A676933D0EC433949" + "C1833BE52E76602CC3E4E784C002E20624BCE0F38F9CBC478439899DA7F15554D0ACA" + "DEC140C00C8FA8FC98862D9933938781B30CB9C76899B3A48DBF170DDA0A18ED37D77" + "F048ABBC85CB19469638C2A32AA3180CF3943BD6B8C5CB26F2EA70868F18B0707C882" + "054141086997A1AE5B709D4D0AA2B358990F244BA76C8E40791D29A0C63C9EF620B97" + "FDFFA9B671E5A65AFCC1C94CAACE0443E9D91F14028935BEA3988831BEBBFD3EB7C3A" + "5AC9605B3534712A09124345ACB09665E357E58946871BC140D3651220464E4A1BB23" + "1A5B0287888B34CA0A8CF5396EB2B8313377DC5ED5C41A9B389A9")}, + {// 3 usage info records + wvcdm::a2bs_hex( + "983358221FB8DBF892047F00AA661F217EEC4E7A1626E8F98E025509E4D65A685E7D9" + "B169B98B16934F6E43E0E0E854A3FA9EB8E9A9D08E9D9B3A6C766AA44F7C655879BA2" + "DF5F38732FB7EDCA66D8C13A855B15E32CC9389B7DD119BA1F2417825FF1F52970F8E" + "985D34DD353D2AC8B24267353E5B8406C098427C4559A90CC"), + wvcdm::a2bs_hex( + "483EAC68243092009D06FAB41DB594ACB22E068C9524810758ECFF8BAB7E1B1ACA988" + "C3987023F01EFEC11529C7326279742E805E755A08EBBD9AA322F305805BE1166AB45" + "CB156FB0A9E6734371F4028707EE01CF2FB08465707E7E5613DD90D74B0D02536E26C" + "F1261CDDA8713943F3620ECC54095C76F8CD3CE31948C3CC0C9EB5582A4D087A54B39" + "1B4CDCBC98E35830B5932F6CF8D16427EF115CFF0A99499513702DD54C758E53248BB" + "5D195F2A2DD1DB18F97562F1F9034E223CEDB1E09ED1B0FE26089C20ED43B5D87B51F" + "6FC6C9F86255FBF70DF233F2665D604355BF9740A3B755521102E0B485C5CCCA607A9" + "A1BEB757BEDEF12327C637D17D6401E3756719F99BBE69B9CE4C8E47C2AC771F35A8E" + "E3FC4D58B2B2269CF85728E4DA7231BC8F0FD7C50E2A1EE9"), + wvcdm::a2bs_hex( + "5826D3A95F78879292612BCE06D845D64285CD45A7EAA6C87A9DBC3290B0B6AC95315" + "809F8CC7938768F9BD342C62CD4CE055866394489D955247CB0535001D50EFF4FEDF0" + "9501C58569B1EB9AA2305A113A5F4D4524AD34148A2DC48D2F522937F44A57FC76F57" + "EB1D4819C438EA42C7F8974FC7D2FE61CAAB3E1F27172FE6B8675DF4CCF1329A6EFB3" + "1F686FB0DC0F8B552D78970708D50C82ADBE333B585F6DE5A0D01D106F8232EB9ED45" + "42A2DC5AA031CC44652E8A42EDCA5AB08B0B5CA61A922E69A119E556F6014642522EA" + "1550F6D6E63EB25ACC03A4DD3F22F4686ED525F994FABA87629AF5939C16BA68C0F09" + "3EFE033CD319180BF69FCB72AC5123EBCB9DCF1AF00F0A68E31FF5B18FA8CFF3DFBB7" + "DA45413799105D67FA78217710D2F6C33394DD4088100013295FF43CF0598E6FE5C05" + "F03417CCD031F01CF63BECD444C750DF198345F155AB2B2AB94394A3C0C0AE05E386D" + "E6CC565AE82398BD0E377D6ABE103B9D5E84582C3772584B759891FC4B121A113370E" + "2DF5372DD81FB6358C64B0F6EB8F26193CA119E4D9D3D38036FA450EE2047CB2CE265" + "0FF37DF85BE23D58C17379FEC08DC0648236A107AE66178EEBF78F05F3B898424FA02" + "668B51F838AFA90D367B5CB425372D8CC3790BEA8AFB8795251FA09340D85A7F0B003" + "134C838F08BB1054D18404C3F69130700E"), + wvcdm::a2bs_hex( + "0A8B16080210012A84160AA9070A8001924B035FBDA56AE5EF0ED05A08DE7AECC8ABE" + "1835E0C4A548F7803937F4C3B4520EB7F3334FFCDFA00DE56408F09D5019FCE87072D" + "0DC6789817468974B2EA51EE3944B8D7E0A88E4F16EBB80F03BD845231A01E6146841" + "CBAEF0134DCD9300DB2D92732992C0F2310D8E386FB31C67B9477010DEF9D99C42725" + "89572A26A17E12AC021E6FFBE66FC6153E7749906EC8F684E819467E16CAF317F315D" + "B32B6D3FDD1A8E8A094174D92D063B88E4835EAB78BD09541EA7FE72F132EB7364E15" + "4BC1548FC40EC7092775531508C95F9ED5D76F36BC0C198C3A33A1F9415B343905D6B" + "E37645E6800F053B1DA9A20286EFCBBC320424ADF7FB6E3D5D8E86C35E576A1A2A37D" + "344A419C0F0034A1B5F767D3C61D90DCA1119E5024C34EDE8FA7DD128696D8C435410" + "F218E52A853AD214FD05D0F8B3CB4832CFCD97FE159E6DEE64CE82CDAEC0321AE71B3" + "BCBAE42DF9EA65E42E151827086EADE71C138B972CC3992CF9ADA944C063816352ED8" + "658D3FA07BE0F32239E74A65932B069AAC4E8386DB59154AF9AEF71448128C66E5104" + "45294F44E511BD9B1AF19D4D67E99363093BE888D4B2AB841CAFF252CAD13EDF8E1AF" + "40340FC62339728520E6C0C09907C26F3FB78287231661952A8B699E47AE241B999C0" + "29FD2067836DC4BC64F66998A3ECD197DAE36F808A2E5A4C5BF25DD580E52B1C39A8B" + "03772BF82D58929766F2DA04F0E616F92B3A0EB75661B8FF5DE1EB807C990F9E6BA99" + "1C8BAD5EB63B37E8663A4E22AA9DB2015D3DF8BED1C8313F85F13B9483C7A39C59243" + "6C8B13C23F78F55CE812795335059F7F527CA580306A0AEE5A6D957A91F498F64AA2E" + "FC6780716400E17C7EEA30B2E6523B902986995E003C2D919A7DC7C0122CE9410037A" + "6602B59A63B5C89473D4E02DE35C1F01B12ADB48A3D94D43693F08268FECCC78DAF6F" + "4C35FA32C538CD73FBF3CEA274B01179C02473486311956E5A0C78E44C59B2F34FF24" + "B0653A6379A2F5F6F51467CAE26D55CC5BBDCFC9BCFA7B8C5CBF82EBE7BD340C3DAE6" + "374D0692052C529AA33D7A6799C8F1F59C78575E51F707013026CC4F83F6B3328EE6F" + "B1AC91929A4491338E93D10EE6193014A73BA241A9A833EA835217894EB4FD4BDB890" + "4A5999928325D0AC31B6D58609EDD9D85E88F74B5BD6FA7BDD83C51EEB91633ED267A" + "CA2E103904BBE4C031A6483858FBAD74DACD01711F7B882749FFFBA0DB6C7D7109D82" + "989C7D4DB5A0F1E7506AC24C89CECAF231EFF99F96AD76E57DABDD3C2DFBA7BAA869A" + "771F561B165987E552824B0C914E708E425C30AA9070A80017290396E183156BDF830" + "B7BF31BA762CB2675528C9004FD24A61DAFB587ABCF1D36F87795EE0B3DA0B425616A" + "66C82349B2E3BB8841C1335536865F919ED2AE671487B608B21A362D888E0AB4F7AB7" + "175B82F108617C3503F175435788AECAF7FFBFE76995D93CD79424A843A247A8D8A60" + "54A5B5404C9C057AACAD91A20322912AC023478A2D76DEB90BE713B03A11037EA7C30" + "5D1AF65099E3F2B92C4D4443A8F481C1177DEF0A3CB49BA5F1448A10AF1207AD2D361" + "B4A1F961B4B1F215B76A9A5005B414EF45EAFBCF2636ABFC01413B27DD11871103579" + "F8C041A799E22888D9ADB798E92A5E29BC46DECBC90991C65FE151C49F18068C1B65D" + "0E90A9ECDA9248B87C120D5FD8EC81D4D36B529FB2DAD39E0D39578B13B158E2B07C7" + "52D86F1A9D8160C93930C1F4F9E1D0D8E2C5AB308732EB27722A6BF8BE852624C2BE3" + "E4FE85819B89BEBA6535FCFBE85FA63A57BD0FBAF284C64FFD97A146B76B3F37B576F" + "C091C03E2222FBD24C2211344B7E2417EFC36C4A54DCCC460CF810E7EA8AC6386D6AB" + "567C819FED88A22CE55EF9BBE62C2CBC7AEEDE5E5A69FF3472418CE2F4514496C59D2" + "6E72F3BFE0131F1AF403C45FDCB3296A0EBE24FF381E027E6E2EF1AC289C67D3B8583" + "30669A81E8131583D2F140FD64615BDED0ED8316ABFD9C7E887433E1CAA6EA8E0C4F8" + "7ADB2A7FC3CF6FF87A7F02AFF03BF5DB640AD8DDB572C41532E673618DCD8C33EF2BF" + "E4E25EE821DF7D742B0990398543B16EFCDBB03C6327B79D3664CED442E894020F441" + "0ECC178C92AAEDFE39DC563AC226FE9E0EF22E1C896C4F2835CDFDCD50B6C4DBA2B27" + "A3B65DE3963D0A5F6E442A3C32008AB9D1ACBE4F366990EB43F8EE213B71E98DA0902" + "82680ABDD649BECA89700764561379F1DD23490CE967632ECA349AF8E1CBFA1F3A4F3" + "9F453614C8FFB5A179756243CB1FDB515834229BC64917C47A2F2E1116FAAC1336801" + "5312C31FD41215106469BEE77D0EF2FE10CF645B3E82902EAF53A676933D0EC433949" + "C1833BE52E76602CC3E4E784C002E20624BCE0F38F9CBC478439899DA7F15554D0ACA" + "DEC140C00C8FA8FC98862D9933938781B30CB9C76899B3A48DBF170DDA0A18ED37D77" + "F048ABBC85CB19469638C2A32AA3180CF3943BD6B8C5CB26F2EA70868F18B0707C882" + "054141086997A1AE5B709D4D0AA2B358990F244BA76C8E40791D29A0C63C9EF620B97" + "FDFFA9B671E5A65AFCC1C94CAACE0443E9D91F14028935BEA3988831BEBBFD3EB7C3A" + "5AC9605B3534712A09124345ACB09665E357E58946871BC140D3650AA9070A8001983" + "358221FB8DBF892047F00AA661F217EEC4E7A1626E8F98E025509E4D65A685E7D9B16" + "9B98B16934F6E43E0E0E854A3FA9EB8E9A9D08E9D9B3A6C766AA44F7C655879BA2DF5" + "F38732FB7EDCA66D8C13A855B15E32CC9389B7DD119BA1F2417825FF1F52970F8E985" + "D34DD353D2AC8B24267353E5B8406C098427C4559A90CC12AC02483EAC68243092009" + "D06FAB41DB594ACB22E068C9524810758ECFF8BAB7E1B1ACA988C3987023F01EFEC11" + "529C7326279742E805E755A08EBBD9AA322F305805BE1166AB45CB156FB0A9E673437" + "1F4028707EE01CF2FB08465707E7E5613DD90D74B0D02536E26CF1261CDDA8713943F" + "3620ECC54095C76F8CD3CE31948C3CC0C9EB5582A4D087A54B391B4CDCBC98E35830B" + "5932F6CF8D16427EF115CFF0A99499513702DD54C758E53248BB5D195F2A2DD1DB18F" + "97562F1F9034E223CEDB1E09ED1B0FE26089C20ED43B5D87B51F6FC6C9F86255FBF70" + "DF233F2665D604355BF9740A3B755521102E0B485C5CCCA607A9A1BEB757BEDEF1232" + "7C637D17D6401E3756719F99BBE69B9CE4C8E47C2AC771F35A8EE3FC4D58B2B2269CF" + "85728E4DA7231BC8F0FD7C50E2A1EE91AF4035826D3A95F78879292612BCE06D845D6" + "4285CD45A7EAA6C87A9DBC3290B0B6AC95315809F8CC7938768F9BD342C62CD4CE055" + "866394489D955247CB0535001D50EFF4FEDF09501C58569B1EB9AA2305A113A5F4D45" + "24AD34148A2DC48D2F522937F44A57FC76F57EB1D4819C438EA42C7F8974FC7D2FE61" + "CAAB3E1F27172FE6B8675DF4CCF1329A6EFB31F686FB0DC0F8B552D78970708D50C82" + "ADBE333B585F6DE5A0D01D106F8232EB9ED4542A2DC5AA031CC44652E8A42EDCA5AB0" + "8B0B5CA61A922E69A119E556F6014642522EA1550F6D6E63EB25ACC03A4DD3F22F468" + "6ED525F994FABA87629AF5939C16BA68C0F093EFE033CD319180BF69FCB72AC5123EB" + "CB9DCF1AF00F0A68E31FF5B18FA8CFF3DFBB7DA45413799105D67FA78217710D2F6C3" + "3394DD4088100013295FF43CF0598E6FE5C05F03417CCD031F01CF63BECD444C750DF" + "198345F155AB2B2AB94394A3C0C0AE05E386DE6CC565AE82398BD0E377D6ABE103B9D" + "5E84582C3772584B759891FC4B121A113370E2DF5372DD81FB6358C64B0F6EB8F2619" + "3CA119E4D9D3D38036FA450EE2047CB2CE2650FF37DF85BE23D58C17379FEC08DC064" + "8236A107AE66178EEBF78F05F3B898424FA02668B51F838AFA90D367B5CB425372D8C" + "C3790BEA8AFB8795251FA09340D85A7F0B003134C838F08BB1054D18404C3F6913070" + "0E12202FF1FBA9926A24A1F79970EC427DDF87B4421488F7952499BC33CEB282D9E48" + "A")}}; + } // namespace class MockFile : public File { @@ -1053,6 +1337,9 @@ class DeviceFilesSecurityLevelTest : public DeviceFilesTest, public ::testing::WithParamInterface {}; +class DeviceFilesUsageInfoTest : public DeviceFilesTest, + public ::testing::WithParamInterface {}; + MATCHER(IsCreateFileFlagSet, "") { return File::kCreate & arg; } MATCHER(IsBinaryFileFlagSet, "") { return File::kBinary & arg; } MATCHER_P(IsStrEq, str, "") { @@ -1060,18 +1347,29 @@ MATCHER_P(IsStrEq, str, "") { // as well as pointer to data but that will introduce a dependency on tr1 return memcmp(arg, str.c_str(), str.size()) == 0; } -MATCHER_P2(Contains, str1, str2, "") { +MATCHER_P3(Contains, str1, str2, size, "") { // Estimating the length of data. We can have gmock provide length // as well as pointer to data but that will introduce a dependency on tr1 - std::string data(arg, str1.size() + str2.size() + kProtobufEstimatedLen); + std::string data( + arg, size + str1.size() + str2.size() + kProtobufEstimatedOverhead); return (data.find(str1) != std::string::npos && data.find(str2) != std::string::npos); } +MATCHER_P4(Contains, str1, str2, str3, size, "") { + // Estimating the length of data. We can have gmock provide length + // as well as pointer to data but that will introduce a dependency on tr1 + std::string data(arg, size + str1.size() + str2.size() + str3.size() + + kProtobufEstimatedOverhead); + return (data.find(str1) != std::string::npos && + data.find(str2) != std::string::npos && + data.find(str3) != std::string::npos); +} MATCHER_P6(Contains, str1, str2, str3, str4, str5, str6, "") { // Estimating the length of data. We can have gmock provide length // as well as pointer to data but that will introduce a dependency on tr1 std::string data(arg, str1.size() + str2.size() + str3.size() + str4.size() + - str5.size() + str6.size() + kProtobufEstimatedLen); + str5.size() + str6.size() + + kProtobufEstimatedOverhead); return (data.find(str1) != std::string::npos && data.find(str2) != std::string::npos && data.find(str3) != std::string::npos && @@ -1100,14 +1398,15 @@ TEST_P(DeviceFilesStoreTest, StoreCertificate) { EXPECT_CALL(file, Open(StrEq(device_certificate_path), AllOf(IsCreateFileFlagSet(), IsBinaryFileFlagSet()))) .WillOnce(Return(true)); - EXPECT_CALL(file, Write(Contains(certificate, wrapped_private_key), + EXPECT_CALL(file, Write(Contains(certificate, wrapped_private_key, 0), Gt(certificate.size() + wrapped_private_key.size()))) .WillOnce(ReturnArg<1>()); EXPECT_CALL(file, Close()).Times(1); EXPECT_CALL(file, Read(_, _)).Times(0); DeviceFiles device_files; - EXPECT_TRUE(device_files.Init(&file, kSecurityLevelL1)); + EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); + device_files.SetTestFile(&file); EXPECT_TRUE(device_files.StoreCertificate(certificate, wrapped_private_key)); } @@ -1132,7 +1431,8 @@ TEST_F(DeviceFilesTest, ReadCertificate) { EXPECT_CALL(file, Write(_, _)).Times(0); DeviceFiles device_files; - EXPECT_TRUE(device_files.Init(&file, kSecurityLevelL1)); + EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); + device_files.SetTestFile(&file); std::string certificate, wrapped_private_key; ASSERT_TRUE( @@ -1161,14 +1461,15 @@ TEST_P(DeviceFilesSecurityLevelTest, SecurityLevel) { EXPECT_CALL(file, Open(StrEq(device_certificate_path), AllOf(IsCreateFileFlagSet(), IsBinaryFileFlagSet()))) .WillOnce(Return(true)); - EXPECT_CALL(file, Write(Contains(certificate, wrapped_private_key), + EXPECT_CALL(file, Write(Contains(certificate, wrapped_private_key, 0), Gt(certificate.size() + wrapped_private_key.size()))) .WillOnce(ReturnArg<1>()); EXPECT_CALL(file, Close()).Times(1); EXPECT_CALL(file, Read(_, _)).Times(0); DeviceFiles device_files; - EXPECT_TRUE(device_files.Init(&file, security_level)); + EXPECT_TRUE(device_files.Init(security_level)); + device_files.SetTestFile(&file); EXPECT_TRUE(device_files.StoreCertificate(certificate, wrapped_private_key)); } @@ -1208,7 +1509,8 @@ TEST_P(DeviceFilesStoreTest, StoreLicense) { EXPECT_CALL(file, Read(_, _)).Times(0); DeviceFiles device_files; - EXPECT_TRUE(device_files.Init(&file, kSecurityLevelL1)); + EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); + device_files.SetTestFile(&file); EXPECT_TRUE(device_files.StoreLicense( license_test_data[license_num].key_set_id, license_test_data[license_num].license_state, @@ -1226,7 +1528,8 @@ INSTANTIATE_TEST_CASE_P(StoreLicense, DeviceFilesStoreTest, TEST_F(DeviceFilesTest, StoreLicenses) { MockFile file; EXPECT_CALL(file, IsDirectory(StrEq(device_base_path_))) - .Times(kNumberOfLicenses).WillRepeatedly(Return(true)); + .Times(kNumberOfLicenses) + .WillRepeatedly(Return(true)); EXPECT_CALL(file, CreateDirectory(_)).Times(0); for (size_t i = 0; i < kNumberOfLicenses; ++i) { @@ -1251,7 +1554,8 @@ TEST_F(DeviceFilesTest, StoreLicenses) { EXPECT_CALL(file, Read(_, _)).Times(0); DeviceFiles device_files; - EXPECT_TRUE(device_files.Init(&file, kSecurityLevelL1)); + EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); + device_files.SetTestFile(&file); for (size_t i = 0; i < kNumberOfLicenses; i++) { EXPECT_TRUE(device_files.StoreLicense( license_test_data[i].key_set_id, license_test_data[i].license_state, @@ -1286,7 +1590,8 @@ TEST_F(DeviceFilesTest, RetrieveLicenses) { EXPECT_CALL(file, Write(_, _)).Times(0); DeviceFiles device_files; - EXPECT_TRUE(device_files.Init(&file, kSecurityLevelL1)); + EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); + device_files.SetTestFile(&file); DeviceFiles::LicenseState license_state; CdmInitData pssh_data; CdmKeyMessage key_request; @@ -1338,12 +1643,11 @@ TEST_F(DeviceFilesTest, SecurityLevelPathBackwardCompatibility) { std::string old_path = base_path + DeviceFiles::GetCertificateFileName(); old_files.push_back(DeviceFiles::GetCertificateFileName()); - EXPECT_CALL(file, IsRegularFile(StrEq(old_path))) - .WillOnce(Return(true)); + EXPECT_CALL(file, IsRegularFile(StrEq(old_path))).WillOnce(Return(true)); EXPECT_CALL(file, Remove(StrEq(old_path))).WillOnce(Return(true)); for (size_t i = 0; i < security_dirs.size(); ++i) { - new_path = base_path + security_dirs[i] + - DeviceFiles::GetCertificateFileName(); + new_path = + base_path + security_dirs[i] + DeviceFiles::GetCertificateFileName(); EXPECT_CALL(file, Copy(StrEq(old_path), StrEq(new_path))) .WillOnce(Return(true)); } @@ -1377,7 +1681,8 @@ TEST_F(DeviceFilesTest, SecurityLevelPathBackwardCompatibility) { EXPECT_CALL(file, Write(_, _)).Times(0); DeviceFiles device_files; - EXPECT_TRUE(device_files.Init(&file, kSecurityLevelL1)); + EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); + device_files.SetTestFile(&file); Properties::Init(); std::string certificate, wrapped_private_key; @@ -1391,12 +1696,14 @@ TEST_F(DeviceFilesTest, UpdateLicenseState) { license_update_test_data[0].key_set_id + DeviceFiles::GetLicenseFileNameExtension(); - EXPECT_CALL(file, IsDirectory(StrEq(device_base_path_))).Times(2) + EXPECT_CALL(file, IsDirectory(StrEq(device_base_path_))) + .Times(2) .WillRepeatedly(Return(true)); EXPECT_CALL(file, CreateDirectory(_)).Times(0); EXPECT_CALL(file, Open(StrEq(license_path), AllOf(IsCreateFileFlagSet(), IsBinaryFileFlagSet()))) - .Times(2).WillRepeatedly(Return(true)); + .Times(2) + .WillRepeatedly(Return(true)); EXPECT_CALL(file, Write(IsStrEq(license_update_test_data[0].file_data), Eq(license_update_test_data[0].file_data.size()))) .WillOnce(ReturnArg<1>()); @@ -1407,7 +1714,8 @@ TEST_F(DeviceFilesTest, UpdateLicenseState) { EXPECT_CALL(file, Read(_, _)).Times(0); DeviceFiles device_files; - EXPECT_TRUE(device_files.Init(&file, kSecurityLevelL1)); + EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); + device_files.SetTestFile(&file); EXPECT_TRUE(device_files.StoreLicense( license_update_test_data[0].key_set_id, license_update_test_data[0].license_state, @@ -1437,7 +1745,9 @@ TEST_F(DeviceFilesTest, DeleteLicense) { size_t size = license_test_data[0].file_data.size(); - EXPECT_CALL(file, Exists(StrEq(license_path))).Times(2).WillOnce(Return(true)) + EXPECT_CALL(file, Exists(StrEq(license_path))) + .Times(2) + .WillOnce(Return(true)) .WillOnce(Return(false)); EXPECT_CALL(file, FileSize(StrEq(license_path))).WillOnce(Return(size)); EXPECT_CALL(file, Open(StrEq(license_path), IsBinaryFileFlagSet())) @@ -1451,7 +1761,8 @@ TEST_F(DeviceFilesTest, DeleteLicense) { EXPECT_CALL(file, Write(_, _)).Times(0); DeviceFiles device_files; - EXPECT_TRUE(device_files.Init(&file, kSecurityLevelL1)); + EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); + device_files.SetTestFile(&file); DeviceFiles::LicenseState license_state; CdmInitData pssh_data; CdmKeyMessage key_request; @@ -1475,4 +1786,154 @@ TEST_F(DeviceFilesTest, DeleteLicense) { EXPECT_FALSE(device_files.LicenseExists(license_test_data[0].key_set_id)); } +TEST_P(DeviceFilesUsageInfoTest, Read) { + MockFile file; + std::string path = device_base_path_ + DeviceFiles::GetUsageInfoFileName(); + + int index = GetParam(); + std::string data; + if (index >= 0) { + data = kUsageInfoTestData[index].file_data; + } + if (index >= 0) { + EXPECT_CALL(file, Exists(StrEq(path))).WillOnce(Return(true)); + EXPECT_CALL(file, FileSize(StrEq(path))).WillOnce(Return(data.size())); + EXPECT_CALL(file, Open(StrEq(path), IsBinaryFileFlagSet())) + .WillOnce(Return(true)); + EXPECT_CALL(file, Read(NotNull(), Eq(data.size()))).WillOnce(DoAll( + SetArrayArgument<0>(data.begin(), data.end()), Return(data.size()))); + EXPECT_CALL(file, Close()).Times(1); + } else { + EXPECT_CALL(file, Exists(StrEq(path))).Times(2).WillRepeatedly( + Return(false)); + EXPECT_CALL(file, FileSize(_)).Times(0); + EXPECT_CALL(file, Open(_, _)).Times(0); + EXPECT_CALL(file, Close()).Times(0); + } + + EXPECT_CALL(file, Write(_, _)).Times(0); + + DeviceFiles device_files; + EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); + device_files.SetTestFile(&file); + + std::vector > license_info; + ASSERT_TRUE(device_files.RetrieveUsageInfo(&license_info)); + if (index >= 0) { + EXPECT_EQ(index, license_info.size()); + for (size_t i = 0; i < license_info.size(); ++i) { + bool found = false; + for (size_t j = 0; j <= static_cast(index); ++j) { + if ((license_info[i].first.compare( + kUsageInfoTestData[j].license_request) == 0) && + (license_info[i].second.compare(kUsageInfoTestData[j].license) == + 0)) { + found = true; + } + } + EXPECT_TRUE(found); + } + } else { + EXPECT_EQ(0, license_info.size()); + } +} + +TEST_P(DeviceFilesUsageInfoTest, Store) { + MockFile file; + std::string pst(GenerateRandomData(kProviderSessionTokenLen)); + std::string license_request(GenerateRandomData(kLicenseRequestLen)); + std::string license(GenerateRandomData(kLicenseLen)); + std::string path = device_base_path_ + DeviceFiles::GetUsageInfoFileName(); + + int index = GetParam(); + std::string data; + if (index >= 0) { + data = kUsageInfoTestData[index].file_data; + } + EXPECT_CALL(file, IsDirectory(StrEq(device_base_path_))) + .WillRepeatedly(Return(true)); + EXPECT_CALL(file, CreateDirectory(_)).Times(0); + + EXPECT_CALL(file, Exists(StrEq(path))).WillOnce(Return(index >= 0)); + if (index >= 0) { + EXPECT_CALL(file, FileSize(StrEq(path))).WillOnce(Return(data.size())); + EXPECT_CALL(file, Open(StrEq(path), IsBinaryFileFlagSet())) + .Times(2) + .WillRepeatedly(Return(true)); + EXPECT_CALL(file, Read(NotNull(), Eq(data.size()))).WillOnce(DoAll( + SetArrayArgument<0>(data.begin(), data.end()), Return(data.size()))); + EXPECT_CALL(file, Close()).Times(2); + } else { + EXPECT_CALL(file, FileSize(_)).Times(0); + EXPECT_CALL(file, Open(_, _)).Times(1).WillOnce(Return(true)); + EXPECT_CALL(file, Close()).Times(1); + } + + EXPECT_CALL(file, Write(Contains(pst, license_request, license, data.size()), + Gt(pst.size() + license_request.size() + + license.size()))).WillOnce(ReturnArg<1>()); + + DeviceFiles device_files; + EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); + device_files.SetTestFile(&file); + + ASSERT_TRUE(device_files.StoreUsageInfo(pst, license_request, license)); +} + +TEST_P(DeviceFilesUsageInfoTest, Delete) { + MockFile file; + std::string path = device_base_path_ + DeviceFiles::GetUsageInfoFileName(); + + int index = GetParam(); + if (index < 0) return; + + std::string data, pst, prev_data, prev_pst, prev_license; + if (index >= 0) { + data = kUsageInfoTestData[index].file_data; + if (index >= 1) { + pst = kUsageInfoTestData[index].provider_session_token; + prev_data = kUsageInfoTestData[index - 1].file_data; + prev_pst = kUsageInfoTestData[index - 1].provider_session_token; + prev_license = kUsageInfoTestData[index - 1].license; + } + } + + EXPECT_CALL(file, IsDirectory(StrEq(device_base_path_))) + .WillRepeatedly(Return(true)); + EXPECT_CALL(file, CreateDirectory(_)).Times(0); + + EXPECT_CALL(file, Exists(StrEq(path))).WillOnce(Return(index >= 0)); + + EXPECT_CALL(file, FileSize(StrEq(path))).WillOnce(Return(data.size())); + if (index >= 1) { + EXPECT_CALL(file, Open(StrEq(path), IsBinaryFileFlagSet())) + .Times(2) + .WillRepeatedly(Return(true)); + EXPECT_CALL(file, Write(Contains(prev_pst, prev_license, prev_data.size()), + Gt(prev_pst.size() + prev_license.size()))) + .WillOnce(ReturnArg<1>()); + EXPECT_CALL(file, Close()).Times(2); + } else { + EXPECT_CALL(file, Open(StrEq(path), IsBinaryFileFlagSet())) + .WillOnce(Return(true)); + EXPECT_CALL(file, Write(_, _)).Times(0); + EXPECT_CALL(file, Close()).Times(1); + } + EXPECT_CALL(file, Read(NotNull(), Eq(data.size()))).WillOnce(DoAll( + SetArrayArgument<0>(data.begin(), data.end()), Return(data.size()))); + + DeviceFiles device_files; + EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); + device_files.SetTestFile(&file); + + if (index >= 1) { + ASSERT_TRUE(device_files.DeleteUsageInfo(pst)); + } else { + ASSERT_FALSE(device_files.DeleteUsageInfo(pst)); + } +} + +INSTANTIATE_TEST_CASE_P(UsageInfo, DeviceFilesUsageInfoTest, + ::testing::Values(-1, 0, 1, 2, 3)); + } // namespace wvcdm diff --git a/core/test/http_socket.cpp b/core/test/http_socket.cpp index e704eac3..392fc863 100644 --- a/core/test/http_socket.cpp +++ b/core/test/http_socket.cpp @@ -5,17 +5,35 @@ #include #include #include -#include +#include +#include #include +#include + +#include +#include +#include #include "log.h" -#include "openssl/bio.h" -#include "openssl/err.h" -#include "openssl/x509.h" -namespace wvcdm { +namespace { -SSL_CTX* HttpSocket::InitSslContext(void) { +// Helper function to tokenize a string. This makes it easier to avoid silly +// parsing bugs that creep in easily when each part of the string is parsed +// with its own piece of code. +bool Tokenize(const std::string& source, const std::string& delim, + const size_t offset, std::string* substring_output, + size_t* next_offset) { + size_t start_of_delim = source.find(delim, offset); + if (start_of_delim == std::string::npos) { + return false; + } + substring_output->assign(source, offset, start_of_delim - offset); + *next_offset = start_of_delim + delim.size(); + return true; +} + +SSL_CTX* InitSslContext() { const SSL_METHOD* method; SSL_CTX* ctx; @@ -23,21 +41,18 @@ SSL_CTX* HttpSocket::InitSslContext(void) { SSL_load_error_strings(); method = SSLv3_client_method(); ctx = SSL_CTX_new(method); - if (NULL == ctx) { + if (!ctx) LOGE("failed to create SSL context"); - } return ctx; } -void HttpSocket::ShowServerCertificate(const SSL* ssl) { - X509* cert; - char* line; - +// unused, may be useful for debugging SSL-related issues. +void ShowServerCertificate(const SSL* ssl) { // gets the server certificate - cert = SSL_get_peer_certificate(ssl); - if (cert != NULL) { + X509* cert = SSL_get_peer_certificate(ssl); + if (cert) { + char* line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0); LOGV("server certificate:"); - line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0); LOGV("subject: %s", line); free(line); line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0); @@ -49,183 +64,252 @@ void HttpSocket::ShowServerCertificate(const SSL* ssl) { } } -HttpSocket::HttpSocket() - : secure_connect_(true), - socket_fd_(-1), +// Wait for a socket to be ready for reading or writing. +// Establishing a connection counts as "ready for write". +// Returns false on select error or timeout. +// Returns true when the socket is ready. +bool SocketWait(int fd, bool for_read, int timeout_in_ms) { + fd_set fds; + FD_ZERO(&fds); + FD_SET(fd, &fds); + + struct timeval tv; + tv.tv_sec = timeout_in_ms / 1000; + tv.tv_usec = (timeout_in_ms % 1000) * 1000; + + fd_set *read_fds = NULL; + fd_set *write_fds = NULL; + if (for_read) { + read_fds = &fds; + } else { + write_fds = &fds; + } + + int ret = select(fd + 1, read_fds, write_fds, NULL, &tv); + if (ret == 0) { + LOGE("socket timed out"); + return false; + } else if (ret == -1) { + LOGE("select failed, errno = %d", errno); + return false; + } + + // socket ready. + return true; +} + +} // namespace + +namespace wvcdm { + +// Parses the URL and extracts all relevant information. +// static +bool HttpSocket::ParseUrl(const std::string& url, + std::string* scheme, + bool* secure_connect, + std::string* domain_name, + int* port, + std::string* path) { + size_t offset = 0; + + if (!Tokenize(url, "://", offset, scheme, &offset)) { + LOGE("Invalid URL, scheme not found: %s", url.c_str()); + return false; + } + + // If the scheme is http or https, set secure_connect and port accordingly. + // Otherwise, consider the scheme unsupported and fail. + if (*scheme == "http") { + *secure_connect = false; + *port = 80; + } else if (*scheme == "https") { + *secure_connect = true; + *port = 443; + } else { + LOGE("Invalid URL, scheme not supported: %s", url.c_str()); + return false; + } + + if (!Tokenize(url, "/", offset, domain_name, &offset)) { + // The rest of the URL belongs to the domain name. + domain_name->assign(url, offset, std::string::npos); + // No explicit path after the domain name. + path->assign("/"); + } else { + // The rest of the URL, including the preceding slash, belongs to the path. + path->assign(url, offset - 1, std::string::npos); + } + + // The domain name may optionally contain a port which overrides the default. + std::string domain_name_without_port; + size_t port_offset; + if (Tokenize(*domain_name, ":", 0, &domain_name_without_port, + &port_offset)) { + *port = atoi(domain_name->c_str() + port_offset); + if (*port <= 0 || *port >= 65536) { + LOGE("Invalid URL, port not valid: %s", url.c_str()); + return false; + } + domain_name->assign(domain_name_without_port); + } + + return true; +} + +HttpSocket::HttpSocket(const std::string& url) + : socket_fd_(-1), ssl_(NULL), - ssl_ctx_(NULL), - timeout_enabled_(false) { + ssl_ctx_(NULL) { + valid_url_ = ParseUrl(url, &scheme_, &secure_connect_, &domain_name_, &port_, + &resource_path_); SSL_library_init(); } -HttpSocket::~HttpSocket() { CloseSocket(); } +HttpSocket::~HttpSocket() { + CloseSocket(); +} void HttpSocket::CloseSocket() { if (socket_fd_ != -1) { close(socket_fd_); socket_fd_ = -1; } - if (secure_connect_) { - if (ssl_) { - SSL_free(ssl_); - ssl_ = NULL; - } - if (ssl_ctx_) { - CloseSslContext(ssl_ctx_); - ssl_ctx_ = NULL; - } + if (ssl_) { + SSL_free(ssl_); + ssl_ = NULL; + } + if (ssl_ctx_) { + SSL_CTX_free(ssl_ctx_); + ssl_ctx_ = NULL; } } -// Extracts the domain name and resource path from the input url parameter. -// The results are put in domain_name and resource_path respectively. -// The format of the url can begin with :://domain server/... -// or dowmain server/resource_path -void HttpSocket::GetDomainNameAndPathFromUrl(const std::string& url, - std::string& domain_name, - std::string& resource_path) { - domain_name.clear(); - resource_path.clear(); - - size_t start = url.find("//"); - size_t end = url.npos; - if (start != url.npos) { - end = url.find("/", start + 2); - if (end != url.npos) { - domain_name.assign(url, start + 2, end - start - 2); - resource_path.assign(url, end + 1, url.npos); - } else { - domain_name.assign(url, start + 2, url.npos); - } - } else { - // no scheme/protocol in url - end = url.find("/"); - if (end != url.npos) { - domain_name.assign(url, 0, end); - resource_path.assign(url, end + 1, url.npos); - } else { - domain_name.assign(url); - } +bool HttpSocket::Connect(int timeout_in_ms) { + if (!valid_url_) { + return false; } - // strips port number if present, e.g. https://www.domain.com:8888/... - end = domain_name.find(":"); - if (end != domain_name.npos) { - domain_name.erase(end); - } -} - -bool HttpSocket::Connect(const char* url, const std::string& port, - bool enable_timeout, bool secure_connection) { - secure_connect_ = secure_connection; - if (secure_connect_) ssl_ctx_ = InitSslContext(); - - GetDomainNameAndPathFromUrl(url, domain_name_, resource_path_); + // get a socket socket_fd_ = socket(AF_INET, SOCK_STREAM, 0); if (socket_fd_ < 0) { - LOGE("cannot open socket %d", errno); + LOGE("cannot open socket, errno = %d", errno); return false; } - int reuse = 1; - if (setsockopt(socket_fd_, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == - -1) { + // set the socket in non-blocking mode + int original_flags = fcntl(socket_fd_, F_GETFL, 0); + if (original_flags == -1) { + LOGE("fcntl error, errno = %d", errno); + CloseSocket(); + return false; + } + if (fcntl(socket_fd_, F_SETFL, original_flags | O_NONBLOCK) == -1) { + LOGE("fcntl error, errno = %d", errno); CloseSocket(); - LOGE("setsockopt error %d", errno); return false; } + // lookup the server IP struct addrinfo hints; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; + struct addrinfo* addr_info = NULL; - bool status = true; - int ret = getaddrinfo(domain_name_.c_str(), port.c_str(), &hints, &addr_info); + int ret = getaddrinfo(domain_name_.c_str(), NULL, &hints, &addr_info); if (ret != 0) { - CloseSocket(); - LOGE("getaddrinfo failed with %d", ret); - status = false; + LOGE("getaddrinfo failed, errno = %d", ret); + return false; + } + + // set the port + struct sockaddr_in* addr_ipv4 = reinterpret_cast( + addr_info->ai_addr); + addr_ipv4->sin_port = htons(port_); + + // connect to the server + ret = connect(socket_fd_, addr_info->ai_addr, addr_info->ai_addrlen); + freeaddrinfo(addr_info); + + if (ret == 0) { + // connected right away. } else { - if (connect(socket_fd_, addr_info->ai_addr, addr_info->ai_addrlen) == -1) { + if (errno != EINPROGRESS) { + // failed right away. + LOGE("cannot connect to %s, errno = %d", domain_name_.c_str(), errno); CloseSocket(); - LOGE("cannot connect socket to %s, error=%d", domain_name_.c_str(), - errno); - status = false; + return false; + } else { + // in progress. block until timeout expired or connection established. + if (!SocketWait(socket_fd_, /* for_read */ false, timeout_in_ms)) { + LOGE("cannot connect to %s", domain_name_.c_str()); + CloseSocket(); + return false; + } } } - timeout_enabled_ = enable_timeout; - if (addr_info != NULL) { - freeaddrinfo(addr_info); - } - if (!status) return false; + // set up SSL if needed + if (secure_connect_) { + ssl_ctx_ = InitSslContext(); + if (!ssl_ctx_) { + CloseSocket(); + return false; + } - // secures connection - if (secure_connect_ && ssl_ctx_) { ssl_ = SSL_new(ssl_ctx_); if (!ssl_) { LOGE("failed SSL_new"); + CloseSocket(); return false; } BIO* a_bio = BIO_new_socket(socket_fd_, BIO_NOCLOSE); if (!a_bio) { LOGE("BIO_new_socket error"); + CloseSocket(); return false; } SSL_set_bio(ssl_, a_bio, a_bio); - int ret = SSL_connect(ssl_); - if (1 != ret) { - char buf[256]; - LOGE("SSL_connect error:%s", ERR_error_string(ERR_get_error(), buf)); - return false; - } + do { + ret = SSL_connect(ssl_); + if (ret != 1) { + int ssl_err = SSL_get_error(ssl_, ret); + if (ssl_err != SSL_ERROR_WANT_READ && + ssl_err != SSL_ERROR_WANT_WRITE) { + char buf[256]; + LOGE("SSL_connect error: %s", ERR_error_string(ERR_get_error(), buf)); + CloseSocket(); + return false; + } + bool for_read = ssl_err == SSL_ERROR_WANT_READ; + if (!SocketWait(socket_fd_, for_read, timeout_in_ms)) { + LOGE("cannot connect to %s", domain_name_.c_str()); + CloseSocket(); + return false; + } + } + } while (ret != 1); } + return true; } -int HttpSocket::Read(char* data, int len) { return (Read(data, len, 0)); } - -// makes non-blocking mode only during read, it supports timeout for read -// returns -1 for error, number of bytes read for success +// Returns -1 for error, number of bytes read for success. +// The timeout here only applies to the span between packets of data, for the +// sake of simplicity. int HttpSocket::Read(char* data, int len, int timeout_in_ms) { - bool use_timeout = (timeout_enabled_ && (timeout_in_ms > 0)); - int original_flags = 0; - if (use_timeout) { - original_flags = fcntl(socket_fd_, F_GETFL, 0); - if (original_flags == -1) { - LOGE("fcntl error %d", errno); - return -1; - } - if (fcntl(socket_fd_, F_SETFL, original_flags | O_NONBLOCK) == -1) { - LOGE("fcntl error %d", errno); - return -1; - } - } - int total_read = 0; - int read = 0; int to_read = len; + while (to_read > 0) { - if (use_timeout) { - fd_set read_fds; - struct timeval tv; - tv.tv_sec = timeout_in_ms / 1000; - tv.tv_usec = (timeout_in_ms % 1000) * 1000; - FD_ZERO(&read_fds); - FD_SET(socket_fd_, &read_fds); - if (select(socket_fd_ + 1, &read_fds, NULL, NULL, &tv) == -1) { - LOGE("select failed"); - break; - } - if (!FD_ISSET(socket_fd_, &read_fds)) { - LOGD("socket read timeout"); - break; - } + if (!SocketWait(socket_fd_, /* for_read */ true, timeout_in_ms)) { + LOGE("unable to read from %s", domain_name_.c_str()); + return -1; } + int read; if (secure_connect_) read = SSL_read(ssl_, data, to_read); else @@ -236,27 +320,26 @@ int HttpSocket::Read(char* data, int len, int timeout_in_ms) { data += read; total_read += read; } else if (read == 0) { - // in blocking mode, zero read mean's peer closed. - // in non-blocking mode, select said that there is data. so it should not - // happen + // The connection has been closed. No more data. break; } else { - LOGE("recv returned %d, error = %d", read, errno); - break; + LOGE("recv returned %d, errno = %d", read, errno); + return -1; } } - if (use_timeout) { - fcntl(socket_fd_, F_SETFL, original_flags); // now blocking again - } return total_read; } -int HttpSocket::Write(const char* data, int len) { +// Returns -1 for error, number of bytes written for success. +// The timeout here only applies to the span between packets of data, for the +// sake of simplicity. +int HttpSocket::Write(const char* data, int len, int timeout_in_ms) { int total_sent = 0; - int sent = 0; int to_send = len; + while (to_send > 0) { + int sent; if (secure_connect_) sent = SSL_write(ssl_, data, to_send); else @@ -267,11 +350,17 @@ int HttpSocket::Write(const char* data, int len) { data += sent; total_sent += sent; } else if (sent == 0) { - usleep(10); // retry later + // We filled up the pipe. Wait for room to write. + if (!SocketWait(socket_fd_, /* for_read */ false, timeout_in_ms)) { + LOGE("unable to write to %s", domain_name_.c_str()); + return -1; + } } else { - LOGE("send returned error %d", errno); + LOGE("send returned %d, errno = %d", sent, errno); + return -1; } } + return total_sent; } diff --git a/core/test/http_socket.h b/core/test/http_socket.h index 79363b10..0b628e26 100644 --- a/core/test/http_socket.h +++ b/core/test/http_socket.h @@ -4,43 +4,52 @@ #define CDM_TEST_HTTP_SOCKET_H_ #include -#include "openssl/ssl.h" -#include "wv_cdm_types.h" + +#include +#include + +#include "wv_cdm_types.h" // CORE_DISALLOW_COPY_AND_ASSIGN namespace wvcdm { // Provides basic Linux based TCP socket interface. class HttpSocket { public: - HttpSocket(); + // A scheme (http:// or https://) is required for the URL. + explicit HttpSocket(const std::string& url); ~HttpSocket(); + bool Connect(int timeout_in_ms); void CloseSocket(); - bool Connect(const char* url, const std::string& port, bool enable_timeout, - bool secure_connection); - void GetDomainNameAndPathFromUrl(const std::string& url, - std::string& domain_name, - std::string& resource_path); - const std::string& domain_name() const { return domain_name_; }; - const std::string& resource_path() const { return resource_path_; }; - int Read(char* data, int len); + + const std::string& scheme() const { return scheme_; } + bool secure_connect() const { return secure_connect_; } + const std::string& domain_name() const { return domain_name_; } + int port() const { return port_; } + const std::string& resource_path() const { return resource_path_; } + int Read(char* data, int len, int timeout_in_ms); - int Write(const char* data, int len); + int Write(const char* data, int len, int timeout_in_ms); private: - void CloseSslContext(SSL_CTX* ctx) const { - if (ctx) SSL_CTX_free(ctx); - } - SSL_CTX* InitSslContext(void); - void ShowServerCertificate(const SSL* ssl); + static bool ParseUrl(const std::string& url, + std::string* scheme, + bool* secure_connect, + std::string* domain_name, + int* port, + std::string* path); + FRIEND_TEST(HttpSocketTest, ParseUrlTest); - std::string domain_name_; + std::string scheme_; bool secure_connect_; + std::string domain_name_; + int port_; std::string resource_path_; + bool valid_url_; + int socket_fd_; SSL* ssl_; SSL_CTX* ssl_ctx_; - bool timeout_enabled_; CORE_DISALLOW_COPY_AND_ASSIGN(HttpSocket); }; diff --git a/core/test/http_socket_test.cpp b/core/test/http_socket_test.cpp index fe8c5366..35624025 100644 --- a/core/test/http_socket_test.cpp +++ b/core/test/http_socket_test.cpp @@ -3,6 +3,7 @@ #include #include "gtest/gtest.h" #include "http_socket.h" +#include "scoped_ptr.h" #include "log.h" #include "string_conversions.h" #include "url_request.h" @@ -10,10 +11,14 @@ namespace { // Arbitrary URL for tests. const std::string kHttpsTestServer("https://www.google.com"); +const std::string kHttpTestServer("http://www.google.com"); +// This URL and data are used by RoundTripTest, and can be overridden on the +// command line. std::string gTestServer(kHttpsTestServer); std::string gTestData("Hello"); +// Arbitrary buffer size and timeout settings. const int kHttpBufferSize = 4096; -char gBuffer[kHttpBufferSize]; +const int kTimeout = 3000; } namespace wvcdm { @@ -21,153 +26,179 @@ namespace wvcdm { class HttpSocketTest : public testing::Test { public: HttpSocketTest() {} - ~HttpSocketTest() { socket_.CloseSocket(); } + ~HttpSocketTest() {} protected: - bool Connect(const std::string& server_url, bool secure_connection) { + bool Connect(const std::string& server_url) { + socket_.reset(new HttpSocket(server_url)); - std::string port = secure_connection ? "443" : "80"; - if (socket_.Connect(server_url.c_str(), port, true, secure_connection)) { - LOGD("connected to %s", socket_.domain_name().c_str()); + if (socket_->Connect(kTimeout)) { + LOGD("connected to %s", socket_->domain_name().c_str()); + return true; } else { - LOGE("failed to connect to %s", socket_.domain_name().c_str()); + LOGE("failed to connect to %s", server_url.c_str()); return false; } - return true; } bool PostRequest(const std::string& data) { std::string request("POST "); - if (socket_.resource_path().empty()) - request.append(socket_.domain_name()); - else - request.append(socket_.resource_path()); + request.append(socket_->resource_path()); request.append(" HTTP/1.1\r\n"); + request.append("Host: "); - request.append(socket_.domain_name()); - request.append("\r\nUser-Agent: httpSocketTest/1.0\r\n"); + request.append(socket_->domain_name()); + request.append("\r\n"); + + // Important! Otherwise, the HTTP 1.1 default behavior for a server is to + // keep the connection open for a subsequent request. + request.append("Connection: close\r\n"); + + request.append("User-Agent: httpSocketTest/1.0\r\n"); + + char buffer[32] = {0}; + snprintf(buffer, sizeof(buffer), "%d", static_cast(data.size())); request.append("Content-Length: "); - memset(gBuffer, 0, kHttpBufferSize); - snprintf(gBuffer, kHttpBufferSize, "%d\r\n", static_cast(data.size())); - request.append(gBuffer); + request.append(buffer); + request.append("\r\n"); + request.append("Content-Type: multipart/form-data\r\n"); - // newline terminates header + // an extra newline terminates HTTP headers. request.append("\r\n"); // append data request.append(data); - socket_.Write(request.c_str(), request.size()); + socket_->Write(request.c_str(), request.size(), kTimeout); + LOGD("request: %s", request.c_str()); return true; } - bool GetResponse() { - int bytes = socket_.Read(gBuffer, kHttpBufferSize, 1000); + bool GetResponse(std::string* response) { + char buffer[kHttpBufferSize]; + int bytes = socket_->Read(buffer, sizeof(buffer), kTimeout); if (bytes < 0) { - LOGE("read error = ", errno); + LOGE("read error, errno = %d", errno); return false; - } else { - LOGD("read %d bytes", bytes); - std::string response(gBuffer, bytes); - LOGD("response: %s", response.c_str()); - LOGD("end response dump"); - return true; } + + LOGD("read %d bytes", bytes); + response->assign(buffer, bytes); + return true; } - HttpSocket socket_; + scoped_ptr socket_; std::string domain_name_; std::string resource_path_; }; -TEST_F(HttpSocketTest, GetDomainNameAndPathFromUrlTest) { - socket_.GetDomainNameAndPathFromUrl( - "https://code.google.com/p/googletest/wiki/Primer", domain_name_, - resource_path_); - EXPECT_STREQ("code.google.com", domain_name_.c_str()); - EXPECT_STREQ("p/googletest/wiki/Primer", resource_path_.c_str()); +struct ParseUrlTests { + const char* url; + const char* scheme; + bool secure_connect; + const char* domain_name; + int port; + const char* path; +}; - socket_.GetDomainNameAndPathFromUrl( - "http://code.google.com/p/googletest/wiki/Primer/", domain_name_, - resource_path_); - EXPECT_STREQ("code.google.com", domain_name_.c_str()); - EXPECT_STREQ("p/googletest/wiki/Primer/", resource_path_.c_str()); +ParseUrlTests parse_url_tests[] = { + { + "https://code.google.com/p/googletest/wiki/Primer", // url + "https", // scheme + true, // secure_connect + "code.google.com", // domain_name + 443, // port + "/p/googletest/wiki/Primer", // path + }, + { + "http://code.google.com/p/googletest/wiki/Primer/", // url + "http", // scheme + false, // secure_connect + "code.google.com", // domain_name + 80, // port + "/p/googletest/wiki/Primer/", // path + }, + { + "http://code.google.com/", // url + "http", // scheme + false, // secure_connect + "code.google.com", // domain_name + 80, // port + "/", // path + }, + { + "http://code.google.com", // url + "http", // scheme + false, // secure_connect + "code.google.com", // domain_name + 80, // port + "/", // path + }, + { + "http://10.11.12.13:8888/drm", // url + "http", // scheme + false, // secure_connect + "10.11.12.13", // domain_name + 8888, // port + "/drm", // path + }, + { + "http://10.11.12.13:8888", // url + "http", // scheme + false, // secure_connect + "10.11.12.13", // domain_name + 8888, // port + "/", // path + }, + { + "https://10.11.12.13:8888", // url + "https", // scheme + true, // secure_connect + "10.11.12.13", // domain_name + 8888, // port + "/", // path + }, + { NULL } // list terminator +}; - socket_.GetDomainNameAndPathFromUrl("http://code.google.com/", domain_name_, - resource_path_); - EXPECT_STREQ("code.google.com", domain_name_.c_str()); - EXPECT_STREQ("", resource_path_.c_str()); +TEST_F(HttpSocketTest, ParseUrlTest) { + std::string scheme; + bool secure_connect; + std::string domain_name; + int port; + std::string path; + ParseUrlTests* test = NULL; - socket_.GetDomainNameAndPathFromUrl("http://code.google.com", domain_name_, - resource_path_); - EXPECT_STREQ("code.google.com", domain_name_.c_str()); - EXPECT_STREQ("", resource_path_.c_str()); - - socket_.GetDomainNameAndPathFromUrl( - "code.google.com/p/googletest/wiki/Primer", domain_name_, resource_path_); - EXPECT_STREQ("code.google.com", domain_name_.c_str()); - EXPECT_STREQ("p/googletest/wiki/Primer", resource_path_.c_str()); - - socket_.GetDomainNameAndPathFromUrl("code.google.com", domain_name_, - resource_path_); - EXPECT_STREQ("code.google.com", domain_name_.c_str()); - EXPECT_STREQ("", resource_path_.c_str()); - - socket_.GetDomainNameAndPathFromUrl("code.google.com/", domain_name_, - resource_path_); - EXPECT_STREQ("code.google.com", domain_name_.c_str()); - EXPECT_STREQ("", resource_path_.c_str()); - - socket_.GetDomainNameAndPathFromUrl("", domain_name_, resource_path_); - EXPECT_TRUE(domain_name_.empty()); - EXPECT_TRUE(resource_path_.empty()); - - // Test with arbitrary numeric URL - socket_.GetDomainNameAndPathFromUrl("http://10.11.12.13:8888/drm", - domain_name_, resource_path_); - EXPECT_STREQ("10.11.12.13", domain_name_.c_str()); - EXPECT_STREQ("drm", resource_path_.c_str()); - - socket_.GetDomainNameAndPathFromUrl("http://10.11.12.13:8888", domain_name_, - resource_path_); - EXPECT_STREQ("10.11.12.13", domain_name_.c_str()); - EXPECT_TRUE(resource_path_.empty()); + for (test = &parse_url_tests[0]; test->url != NULL; ++test) { + bool ok = HttpSocket::ParseUrl(test->url, &scheme, &secure_connect, + &domain_name, &port, &path); + EXPECT_TRUE(ok); + if (ok) { + EXPECT_EQ(test->scheme, scheme); + EXPECT_EQ(test->secure_connect, secure_connect); + EXPECT_EQ(test->domain_name, domain_name); + EXPECT_EQ(test->port, port); + EXPECT_EQ(test->path, path); + } + } } TEST_F(HttpSocketTest, ConnectTest) { - const bool kUseSecureConnection = true; - - if (gTestServer.find("https") != std::string::npos) { - EXPECT_TRUE(Connect(gTestServer, kUseSecureConnection)); - socket_.CloseSocket(); - - // https connection allows insecure connection through port 80 as well - EXPECT_TRUE(Connect(gTestServer, !kUseSecureConnection)); - socket_.CloseSocket(); - } else { - EXPECT_TRUE(Connect(gTestServer, !kUseSecureConnection)); - socket_.CloseSocket(); - - // Test for the case that non-https connection must not use port 443 - EXPECT_FALSE(Connect(gTestServer, kUseSecureConnection)); - socket_.CloseSocket(); - } - - EXPECT_FALSE(Connect("ww.g.c", kUseSecureConnection)); - socket_.CloseSocket(); - - EXPECT_FALSE(Connect("ww.g.c", !kUseSecureConnection)); - socket_.CloseSocket(); + EXPECT_TRUE(Connect(kHttpsTestServer)); + EXPECT_TRUE(Connect(kHttpTestServer)); + EXPECT_FALSE(Connect("ww.g.c")); + EXPECT_FALSE(Connect("http://ww.g.c")); + EXPECT_FALSE(Connect("https://ww.g.c")); } TEST_F(HttpSocketTest, RoundTripTest) { - int secure_connection = - (gTestServer.find("https") != std::string::npos) ? true : false; - ASSERT_TRUE(Connect(gTestServer, secure_connection)); + ASSERT_TRUE(Connect(gTestServer)); EXPECT_TRUE(PostRequest(gTestData)); - GetResponse(); - socket_.CloseSocket(); + + std::string response; + EXPECT_TRUE(GetResponse(&response)); + LOGD("response: %s", response.c_str()); } } // namespace wvcdm diff --git a/core/test/url_request.cpp b/core/test/url_request.cpp index 1da99c1b..e15fb357 100644 --- a/core/test/url_request.cpp +++ b/core/test/url_request.cpp @@ -10,52 +10,16 @@ #include "string_conversions.h" namespace { -const int kMaxReadAttempts = 4; -const int kSingleReadAttempt = 1; -} // namespace -namespace wvcdm { - -UrlRequest::UrlRequest(const std::string& url, const std::string& port, - bool secure_connection, bool chunk_transfer_mode) - : chunk_transfer_mode_(chunk_transfer_mode), - is_connected_(false), - port_("80"), - request_(""), - server_url_(url) { - if (!port.empty()) { - port_.assign(port); - } - if (socket_.Connect((server_url_).c_str(), port_, true, secure_connection)) { - is_connected_ = true; - } else { - LOGE("failed to connect to %s, port=%s", socket_.domain_name().c_str(), - port.c_str()); - } -} - -UrlRequest::~UrlRequest() { socket_.CloseSocket(); } - -void UrlRequest::AppendChunkToUpload(const std::string& data) { - // format of chunk: - // size of chunk in hex\r\n - // data\r\n - // . . . - // 0\r\n - - // buffer to store length of chunk - memset(buffer_, 0, kHttpBufferSize); - snprintf(buffer_, kHttpBufferSize, "%zx\r\n", data.size()); - request_.append(buffer_); // appends size of chunk - LOGD("...\r\n%s", request_.c_str()); - request_.append(data); - request_.append("\r\n"); // marks end of data -} +const int kReadBufferSize = 1024; +const int kConnectTimeoutMs = 5000; +const int kWriteTimeoutMs = 3000; +const int kReadTimeoutMs = 3000; // Concatenate all chunks into one blob and returns the response with // header information. -void UrlRequest::ConcatenateChunkedResponse(const std::string http_response, - std::string* modified_response) { +void ConcatenateChunkedResponse(const std::string http_response, + std::string* modified_response) { if (http_response.empty()) return; modified_response->clear(); @@ -103,33 +67,52 @@ void UrlRequest::ConcatenateChunkedResponse(const std::string http_response, } } -int UrlRequest::GetResponse(std::string* message) { - message->clear(); +} // namespace + +namespace wvcdm { + +UrlRequest::UrlRequest(const std::string& url) + : is_connected_(false), + socket_(url) { + if (socket_.Connect(kConnectTimeoutMs)) { + is_connected_ = true; + } else { + LOGE("failed to connect to %s, port=%d", socket_.domain_name().c_str(), + socket_.port()); + } +} + +UrlRequest::~UrlRequest() {} + +bool UrlRequest::GetResponse(std::string* message) { std::string response; - const int kTimeoutInMs = 3000; - int bytes = 0; - for (int attempts = kMaxReadAttempts; attempts > 0; --attempts) { - memset(buffer_, 0, kHttpBufferSize); - bytes = socket_.Read(buffer_, kHttpBufferSize, kTimeoutInMs); + + // Keep reading until end of stream (0 bytes read) or timeout. Partial + // buffers worth of data can and do happen, especially with OpenSSL in + // non-blocking mode. + while (true) { + char read_buffer[kReadBufferSize]; + int bytes = socket_.Read(read_buffer, sizeof(read_buffer), kReadTimeoutMs); if (bytes > 0) { - response.append(buffer_, bytes); - if (bytes < static_cast(kHttpBufferSize)) { - attempts = kSingleReadAttempt; - } + response.append(read_buffer, bytes); + } else if (bytes < 0) { + LOGE("read error, errno = %d", errno); + return false; } else { - if (bytes < 0) LOGE("read error = ", errno); - // bytes == 0 indicates nothing to read + // end of stream. + break; } } ConcatenateChunkedResponse(response, message); LOGD("HTTP response: (%d): %s", message->size(), b2a_hex(*message).c_str()); - return message->size(); + return true; } +// static int UrlRequest::GetStatusCode(const std::string& response) { - const std::string kHttpVersion("HTTP/1.1"); + const std::string kHttpVersion("HTTP/1.1 "); int status_code = -1; size_t pos = response.find(kHttpVersion); @@ -140,79 +123,48 @@ int UrlRequest::GetStatusCode(const std::string& response) { return status_code; } -bool UrlRequest::PostRequestChunk(const std::string& data) { - request_.assign("POST /"); - request_.append(socket_.resource_path()); - request_.append(" HTTP/1.1\r\n"); - request_.append("Host: "); - request_.append(socket_.domain_name()); - request_.append("\r\nConnection: Keep-Alive\r\n"); - request_.append("Transfer-Encoding: chunked\r\n"); - request_.append("User-Agent: Widevine CDM v1.0\r\n"); - request_.append("Accept-Encoding: gzip,deflate\r\n"); - request_.append("Accept-Language: en-us,fr\r\n"); - request_.append("Accept-Charset: iso-8859-1,*,utf-8\r\n"); - request_.append("\r\n"); // empty line to terminate header +bool UrlRequest::PostRequestWithPath(const std::string& path, + const std::string& data) { + std::string request; - // calls AppendChunkToUpload repeatedly for multiple chunks - AppendChunkToUpload(data); + request.append("POST "); + request.append(path); + request.append(" HTTP/1.1\r\n"); - // terminates last chunk with 0\r\n, then ends header with an empty line - request_.append("0\r\n\r\n"); + request.append("Host: "); + request.append(socket_.domain_name()); + request.append("\r\n"); - socket_.Write(request_.c_str(), request_.size()); - return true; + request.append("Connection: close\r\n"); + request.append("User-Agent: Widevine CDM v1.0\r\n"); + + // buffer to store length of data as a string + char data_size_buffer[32] = {0}; + snprintf(data_size_buffer, sizeof(data_size_buffer), "%zd", data.size()); + + request.append("Content-Length: "); + request.append(data_size_buffer); // appends size of data + request.append("\r\n"); + + request.append("\r\n"); // empty line to terminate headers + + request.append(data); + + int ret = socket_.Write(request.c_str(), request.size(), kWriteTimeoutMs); + LOGD("HTTP request: (%d): %s", request.size(), b2a_hex(request).c_str()); + return ret != -1; } bool UrlRequest::PostRequest(const std::string& data) { - if (chunk_transfer_mode_) { - return PostRequestChunk(data); - } - request_.assign("POST /"); - request_.append(socket_.resource_path()); - request_.append(" HTTP/1.1\r\n"); - request_.append("Host: "); - request_.append(socket_.domain_name()); - request_.append("\r\nConnection: Keep-Alive\r\n"); - request_.append("User-Agent: Widevine CDM v1.0\r\n"); - request_.append("Accept-Encoding: gzip,deflate\r\n"); - request_.append("Accept-Language: en-us,fr\r\n"); - request_.append("Accept-Charset: iso-8859-1,*,utf-8\r\n"); - std::ostringstream ss; - ss << data.size(); - request_.append("Content-Length: "); - request_.append(ss.str()); - request_.append("\r\n\r\n"); - request_.append(data); - - // terminates with \r\n, then ends with an empty line - request_.append("\r\n\r\n"); - - socket_.Write(request_.c_str(), request_.size()); - LOGD("HTTP request: (%d): %s", request_.size(), request_.c_str()); - LOGD("HTTP request: (%d): %s", request_.size(), b2a_hex(request_).c_str()); - return true; + return PostRequestWithPath(socket_.resource_path(), data); } bool UrlRequest::PostCertRequestInQueryString(const std::string& data) { - request_.assign("POST /"); - request_.append(socket_.resource_path()); - request_.append("&signedRequest="); - request_.append(data); - 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: 0"); - request_.append("\r\n"); // empty line to terminate header - request_.append("\r\n"); // terminates the request + std::string path = socket_.resource_path(); + path.append("&signedRequest="); + path.append(data); - socket_.Write(request_.c_str(), request_.size()); - LOGD("HTTP request: (%d): %s", request_.size(), request_.c_str()); - LOGD("HTTP request: (%d): %s", request_.size(), b2a_hex(request_).c_str()); - return true; + return PostRequestWithPath(path, ""); } } // namespace wvcdm diff --git a/core/test/url_request.h b/core/test/url_request.h index 43358f16..498bb624 100644 --- a/core/test/url_request.h +++ b/core/test/url_request.h @@ -13,29 +13,22 @@ namespace wvcdm { // Only POST request method is implemented. class UrlRequest { public: - UrlRequest(const std::string& url, const std::string& port, - bool secure_connect, bool chunk_transfer_mode); + explicit UrlRequest(const std::string& url); ~UrlRequest(); - void AppendChunkToUpload(const std::string& data); - void ConcatenateChunkedResponse(const std::string http_response, - std::string* modified_response); - int GetResponse(std::string* message); - int GetStatusCode(const std::string& response); bool is_connected() const { return is_connected_; } + bool PostRequest(const std::string& data); - bool PostRequestChunk(const std::string& data); bool PostCertRequestInQueryString(const std::string& data); + bool GetResponse(std::string* message); + static int GetStatusCode(const std::string& response); + private: - static const unsigned int kHttpBufferSize = 4096; - char buffer_[kHttpBufferSize]; - bool chunk_transfer_mode_; + bool PostRequestWithPath(const std::string& path, const std::string& data); + bool is_connected_; - std::string port_; - std::string request_; HttpSocket socket_; - std::string server_url_; CORE_DISALLOW_COPY_AND_ASSIGN(UrlRequest); }; diff --git a/linux/src/log.cpp b/linux/src/log.cpp index b941ca2f..a4db602e 100644 --- a/linux/src/log.cpp +++ b/linux/src/log.cpp @@ -12,7 +12,7 @@ namespace wvcdm { -static LogPriority g_cutoff = LOG_WARN; +LogPriority g_cutoff = LOG_WARN; void InitLogging(int argc, const char* const* argv) { for (int i = 1; i < argc; i++) { diff --git a/oemcrypto/include/oemcrypto_logging.h b/oemcrypto/include/oemcrypto_logging.h new file mode 100644 index 00000000..93af8fb9 --- /dev/null +++ b/oemcrypto/include/oemcrypto_logging.h @@ -0,0 +1,65 @@ +// Copyright 2014 Google Inc. All Rights Reserved. + +#ifndef WVOEC_OEMCRYPTO_LOGGING_H_ +#define WVOEC_OEMCRYPTO_LOGGING_H_ + +#include "OEMCryptoCENC.h" + +#include "log.h" +#include +#include + +namespace wvoec_mock { + +// The constants below represent integers with a single "on" bit that +// represents categories of logging This allows users to specify with +// more precision what they want to log. LogCategoryEnabled(category) +// is used to see if the category passed in the parameters is to +// be logged based on the current settings. Categories can be combines +// using the | (or) bitwise operator. For example +// LogCategoryEnabled(category1 | category2) will return true if +// category1 and/or category2 are set to logging. + +const int kLoggingTraceOEMCryptoCalls = 0x01; +const int kLoggingDumpContentKeys = 0x02; +const int kLoggingDumpKeyControlBlocks = 0x04; +const int kLoggingDumpDerivedKeys = 0x08; +const int kLoggingTraceNonce = 0x10; +const int kLoggingTraceDecryption = 0x20; +const int kLoggingTraceUsageTable = 0x40; +const int kLoggingDumpTraceAll = 0xFF; + +void SetLoggingSettings(int level, int categories); + +// set level of logging +void SetLoggingLevel(int level); + +void TurnOffLoggingForAllCategories(); + +// Returns true if the category passed is set to logging. +// Returns false otherwise. The category constant declared +// above are passed. +bool LogCategoryEnabled(int category); + +// Turn on logging for the categories passed. +void AddLoggingForCategories(int categories); + +// Turn off logging for the categories passed. +void RemoveLoggingForCategories(int categories); + +void dump_hex_helper(std::string& buffer, std::string name, + const uint8_t* vector, size_t length); + +void dump_hex(std::string name, const uint8_t* vector, size_t length); + +void dump_array_part_helper(std::string& buffer, std::string array, + size_t index, std::string name, + const uint8_t* vector, size_t length); + +void dump_array_part(std::string array, size_t index, + std::string name, const uint8_t* vector, size_t length); + +} // namespace wvoec_mock + +#endif + diff --git a/oemcrypto/test/oemcrypto_test.cpp b/oemcrypto/test/oemcrypto_test.cpp index 37bf221c..6d9e0252 100644 --- a/oemcrypto/test/oemcrypto_test.cpp +++ b/oemcrypto/test/oemcrypto_test.cpp @@ -1837,7 +1837,7 @@ class DISABLED_TestKeybox : public OEMCryptoClientTest { } }; -TEST_F(DISABLED_TestKeybox, CheckSystemID) { +TEST_F(OEMCryptoClientTest, DISABLED_CheckSystemID) { OEMCryptoResult sts; uint8_t key_data[256]; size_t key_data_len = sizeof(key_data); @@ -1884,6 +1884,7 @@ TEST_F(DISABLED_TestKeybox, BadCRCKeybox) { InstallKeybox(keybox, false); sts = OEMCrypto_IsKeyboxValid(); ASSERT_EQ(OEMCrypto_ERROR_BAD_CRC, sts); + InstallKeybox(kDefaultKeybox, true); } TEST_F(DISABLED_TestKeybox, BadMagicKeybox) { @@ -1893,6 +1894,7 @@ TEST_F(DISABLED_TestKeybox, BadMagicKeybox) { InstallKeybox(keybox, false); sts = OEMCrypto_IsKeyboxValid(); ASSERT_EQ(OEMCrypto_ERROR_BAD_MAGIC, sts); + InstallKeybox(kDefaultKeybox, true); } TEST_F(DISABLED_TestKeybox, BadDataKeybox) { @@ -1902,6 +1904,7 @@ TEST_F(DISABLED_TestKeybox, BadDataKeybox) { InstallKeybox(keybox, false); sts = OEMCrypto_IsKeyboxValid(); ASSERT_EQ(OEMCrypto_ERROR_BAD_CRC, sts); + InstallKeybox(kDefaultKeybox, true); } TEST_F(DISABLED_TestKeybox, GenerateSignature) { @@ -2148,6 +2151,30 @@ TEST_F(DISABLED_TestKeybox, LoadKeyWithBadNonce) { ASSERT_NE(OEMCrypto_SUCCESS, sts); } +TEST_F(DISABLED_TestKeybox, LoadKeyWithRepeatNonce) { + InstallKeybox(kDefaultKeybox, true); + Session s; + s.open(); + s.GenerateDerivedKeys(); + uint32_t nonce = s.get_nonce(); + s.FillSimpleMessage(0, wvoec_mock::kControlNonceEnabled, nonce); + s.EncryptAndSign(); + s.LoadTestKeys(); + s.close(); + + s.open(); + s.GenerateDerivedKeys(); + s.FillSimpleMessage(0, wvoec_mock::kControlNonceEnabled, + nonce); // same old nonce. + s.EncryptAndSign(); + OEMCryptoResult sts = OEMCrypto_LoadKeys( + s.session_id(), s.message_ptr(), sizeof(MessageData), &s.signature()[0], + s.signature().size(), s.encrypted_license().mac_key_iv, + s.encrypted_license().mac_keys, kNumKeys, s.key_array(), NULL, 0); + + ASSERT_NE(OEMCrypto_SUCCESS, sts); +} + TEST_F(DISABLED_TestKeybox, LoadKeyWithBadVerification) { InstallKeybox(kDefaultKeybox, true); Session s; @@ -4601,6 +4628,85 @@ TEST_P(DISABLED_UsageTableTest, EmptyTable) { } } +TEST_P(DISABLED_UsageTableTest, FiftyEntries) { + if (OEMCrypto_SupportsUsageTable()) { + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); + Session s1; + s1.open(); + s1.GenerateDerivedKeys(); + std::string pst1 = "pst saved"; + s1.FillSimpleMessage( + 0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired, + s1.get_nonce(), pst1); + s1.EncryptAndSign(); + s1.LoadTestKeys(pst1, new_mac_keys_); + sleep(kShortSleep); + + cout << "Making 49\n"; + const size_t ENTRY_COUNT = 49;// API says should hold at least 50 entries. + Session sessions[ENTRY_COUNT]; + for (int i=0; istatus); + s.close(); + } + sleep(kShortSleep); + cout << "Making another 49\n"; + // If I add too many entries, it can delete the older ones first, except + // it shouldn't delete the one attached to an open session. (s1) + for (int i=0; istatus); + s.close(); + } + s1.close(); + s1.open(); // Make sure s1's entry is still in the table. + s1.GenerateReport(pst1); + EXPECT_EQ(kUnused, s1.pst_report()->status); + s1.close(); + } +} + TEST_P(DISABLED_UsageTableTest, DeleteUnusedEntry) { if (OEMCrypto_SupportsUsageTable()) { ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); @@ -4826,9 +4932,7 @@ TEST_P(DISABLED_UsageTableTest, GenericEncrypt) { wvoec_mock::kControlNonceRequired, nonce, pst); s.EncryptAndSign(); - printf("Getting ready to load keys.\n"); s.LoadTestKeys(pst, new_mac_keys_); - printf("done to load keys.\n"); OEMCryptoResult sts; unsigned int key_index = 0; uint8_t expected_encrypted[kBufferSize]; @@ -4842,7 +4946,6 @@ TEST_P(DISABLED_UsageTableTest, GenericEncrypt) { encrypted); ASSERT_EQ(OEMCrypto_SUCCESS, sts); EXPECT_EQ(0, memcmp(encrypted, expected_encrypted, kBufferSize)); - printf("doing the generate report"); s.GenerateReport(pst); EXPECT_EQ(kActive, s.pst_report()->status); EXPECT_ALMOST( @@ -5164,21 +5267,24 @@ TEST_P(DISABLED_UsageTableTest, TimingTest) { Session s1; Session s2; Session s3; - time_t loaded = time(NULL); LoadOfflineLicense(s1, pst1); + time_t loaded1 = time(NULL); LoadOfflineLicense(s2, pst2); + time_t loaded2 = time(NULL); LoadOfflineLicense(s3, pst3); + time_t loaded3 = time(NULL); sleep(kLongSleep); - time_t first_decrypt = time(NULL); s1.open(); s1.GenerateDerivedKeys(); s1.LoadTestKeys(pst1, new_mac_keys_); + time_t first_decrypt1 = time(NULL); s1.TestDecryptCTR(); s2.open(); s2.GenerateDerivedKeys(); s2.LoadTestKeys(pst2, new_mac_keys_); + time_t first_decrypt2 = time(NULL); s2.TestDecryptCTR(); sleep(kLongSleep); @@ -5196,6 +5302,7 @@ TEST_P(DISABLED_UsageTableTest, TimingTest) { OEMCrypto_Terminate(); sleep(kShortSleep); OEMCrypto_Initialize(); + InstallKeybox(kDefaultKeybox, true); // After a reboot, we should be able to reload keys, and generate reports. sleep(kLongSleep); @@ -5210,34 +5317,36 @@ TEST_P(DISABLED_UsageTableTest, TimingTest) { s2.open(); s3.open(); sleep(kLongSleep); - time_t report_generated = time(NULL); + time_t report_generated1 = time(NULL); s1.GenerateReport(pst1); + time_t report_generated2 = time(NULL); s2.GenerateReport(pst2); + time_t report_generated3 = time(NULL); s3.GenerateReport(pst3); EXPECT_EQ(kInactive, s1.pst_report()->status); EXPECT_ALMOST( - report_generated - loaded, + report_generated1 - loaded1, wvcdm::htonll64(s1.pst_report()->seconds_since_license_received)); EXPECT_ALMOST( - report_generated - first_decrypt, + report_generated1 - first_decrypt1, wvcdm::htonll64(s1.pst_report()->seconds_since_first_decrypt)); - EXPECT_ALMOST(report_generated - second_decrypt, + EXPECT_ALMOST(report_generated1 - second_decrypt, wvcdm::htonll64(s1.pst_report()->seconds_since_last_decrypt)); EXPECT_EQ(kActive, s2.pst_report()->status); EXPECT_ALMOST( - report_generated - loaded, + report_generated2 - loaded2, wvcdm::htonll64(s2.pst_report()->seconds_since_license_received)); EXPECT_ALMOST( - report_generated - first_decrypt, + report_generated2 - first_decrypt2, wvcdm::htonll64(s2.pst_report()->seconds_since_first_decrypt)); - EXPECT_ALMOST(report_generated - third_decrypt, + EXPECT_ALMOST(report_generated2 - third_decrypt, wvcdm::htonll64(s2.pst_report()->seconds_since_last_decrypt)); EXPECT_EQ(kUnused, s3.pst_report()->status); EXPECT_ALMOST( - report_generated - loaded, + report_generated3 - loaded3, wvcdm::htonll64(s3.pst_report()->seconds_since_license_received)); // We don't expect first or last decrypt for unused report. } diff --git a/platforms/global_config.gypi b/platforms/global_config.gypi index 71722de5..d83f7332 100644 --- a/platforms/global_config.gypi +++ b/platforms/global_config.gypi @@ -16,8 +16,7 @@ 'protoc_dir%': '/usr/bin', 'certificate_provision%': 'false', 'force_use_of_secure_buffers%': 'false', - 'cdm_target_name%': 'wvcdm_shared', - 'cdm_target_type%': 'shared_library', + 'disable_privacy_crypto%': 'false', }, # end variables 'target_defaults': { diff --git a/third_party/protobuf.gyp b/third_party/protobuf.gyp index efb2a0a6..2f7f0db0 100644 --- a/third_party/protobuf.gyp +++ b/third_party/protobuf.gyp @@ -5,27 +5,38 @@ # use_system_protobuf to false. { + # Some sources are used by both libprotobuf_lite and libprotobuf. + # Representing this with dependencies means that protobuf_lite must be built + # with both the target and host toolsets. Although gyp allows for this, + # this causes bugs in the generated output for both xcode and ninja. + # Therefore we include this variable in the sources of both libraries + # instead. + 'variables': { + 'protobuf_lite_sources': [ + '<(protobuf_source_dir)/src/google/protobuf/extension_set.cc', + '<(protobuf_source_dir)/src/google/protobuf/generated_message_util.cc', + '<(protobuf_source_dir)/src/google/protobuf/message_lite.cc', + '<(protobuf_source_dir)/src/google/protobuf/repeated_field.cc', + '<(protobuf_source_dir)/src/google/protobuf/wire_format_lite.cc', + + '<(protobuf_source_dir)/src/google/protobuf/io/coded_stream.cc', + '<(protobuf_source_dir)/src/google/protobuf/io/zero_copy_stream.cc', + '<(protobuf_source_dir)/src/google/protobuf/io/zero_copy_stream_impl_lite.cc', + + '<(protobuf_source_dir)/src/google/protobuf/stubs/atomicops_internals_x86_gcc.cc', + '<(protobuf_source_dir)/src/google/protobuf/stubs/atomicops_internals_x86_msvc.cc', + '<(protobuf_source_dir)/src/google/protobuf/stubs/common.cc', + '<(protobuf_source_dir)/src/google/protobuf/stubs/once.cc', + '<(protobuf_source_dir)/src/google/protobuf/stubs/stringprintf.cc', + ], + }, 'targets': [ { 'target_name': 'protobuf_lite', 'type': 'static_library', - 'toolsets': ['host', 'target'], + 'toolsets': ['target'], 'sources': [ - '<(protobuf_source_dir)/src/google/protobuf/extension_set.cc', - '<(protobuf_source_dir)/src/google/protobuf/generated_message_util.cc', - '<(protobuf_source_dir)/src/google/protobuf/message_lite.cc', - '<(protobuf_source_dir)/src/google/protobuf/repeated_field.cc', - '<(protobuf_source_dir)/src/google/protobuf/wire_format_lite.cc', - - '<(protobuf_source_dir)/src/google/protobuf/io/coded_stream.cc', - '<(protobuf_source_dir)/src/google/protobuf/io/zero_copy_stream.cc', - '<(protobuf_source_dir)/src/google/protobuf/io/zero_copy_stream_impl_lite.cc', - - '<(protobuf_source_dir)/src/google/protobuf/stubs/atomicops_internals_x86_gcc.cc', - '<(protobuf_source_dir)/src/google/protobuf/stubs/atomicops_internals_x86_msvc.cc', - '<(protobuf_source_dir)/src/google/protobuf/stubs/common.cc', - '<(protobuf_source_dir)/src/google/protobuf/stubs/once.cc', - '<(protobuf_source_dir)/src/google/protobuf/stubs/stringprintf.cc', + '<@(protobuf_lite_sources)', ], 'include_dirs': [ '<(protobuf_source_dir)', @@ -42,6 +53,8 @@ 'type': 'static_library', 'toolsets': ['host'], 'sources': [ + '<@(protobuf_lite_sources)', + '<(protobuf_source_dir)/src/google/protobuf/descriptor.cc', '<(protobuf_source_dir)/src/google/protobuf/descriptor.pb.cc', '<(protobuf_source_dir)/src/google/protobuf/descriptor_database.cc', @@ -71,9 +84,6 @@ '<(protobuf_source_dir)', '<(protobuf_source_dir)/src', ], - 'dependencies': [ - 'protobuf_lite', - ], }, { 'target_name': 'protoc',