From 11c108a8da7370ed0cedd331a6fe7b65de1c5698 Mon Sep 17 00:00:00 2001 From: "John W. Bruce" Date: Thu, 5 Sep 2024 07:02:36 +0000 Subject: [PATCH] Source release 19.3.0 --- CHANGELOG.md | 42 +- README.md | 40 +- cdm/cdm_unittests.gyp | 27 +- cdm/extract_bcc_tool.gyp | 17 +- cdm/include/cdm_version.h | 2 +- cdm/test/cdm_test.cpp | 203 +++++++++ cdm/test/cdm_test_runner.cpp | 57 ++- cdm/test/create_test_file_system.cpp | 5 +- cdm/test/perf_test.cpp | 12 +- cdm/test/perf_test.h | 6 +- cdm/test/perf_test_dynamic.cpp | 28 +- cdm/test/perf_test_xctest.mm | 2 +- cdm/test/test_host.cpp | 19 + cdm/test/test_host.h | 8 + cdm/test/test_version.cpp | 26 ++ cdm/test/test_version_xctest.mm | 28 ++ core/include/buffer_reader.h | 15 +- core/include/cdm_client_property_set.h | 8 +- core/include/cdm_engine.h | 12 +- core/include/cdm_engine_factory.h | 14 +- core/include/cdm_engine_metrics_decorator.h | 10 +- core/include/cdm_session.h | 12 +- core/include/cdm_session_map.h | 11 +- core/include/cdm_usage_table.h | 11 +- core/include/certificate_provisioning.h | 15 +- core/include/client_identification.h | 12 +- core/include/content_key_session.h | 10 +- core/include/crypto_key.h | 13 +- core/include/crypto_session.h | 17 +- core/include/crypto_wrapped_key.h | 11 +- core/include/device_files.h | 13 +- core/include/entitlement_key_session.h | 9 +- core/include/initialization_data.h | 7 +- core/include/key_session.h | 16 +- core/include/license.h | 220 +++++---- core/include/license_key_status.h | 28 +- core/include/license_protocol_conversions.h | 2 - core/include/oemcrypto_adapter.h | 4 +- core/include/okp_fallback_policy.h | 7 +- core/include/okp_info.h | 51 ++- core/include/ota_keybox_provisioner.h | 6 +- core/include/policy_engine.h | 14 +- core/include/policy_timers.h | 12 +- core/include/policy_timers_v16.h | 17 +- core/include/policy_timers_v18.h | 16 +- core/include/privacy_crypto.h | 14 +- core/include/properties.h | 11 +- core/include/service_certificate.h | 32 +- core/include/system_id_extractor.h | 15 +- core/include/wv_cdm_constants.h | 4 +- core/include/wv_cdm_event_listener.h | 17 +- core/include/wv_cdm_types.h | 41 +- core/src/cdm_engine.cpp | 2 +- core/src/cdm_session.cpp | 3 +- core/src/certificate_provisioning.cpp | 20 +- core/src/crypto_session.cpp | 31 +- core/src/license.cpp | 289 ++++++------ core/src/license_protocol.proto | 8 + core/src/okp_info.cpp | 2 +- core/src/privacy_crypto_apple.cpp | 2 +- core/src/privacy_crypto_boringssl.cpp | 7 +- core/src/wv_cdm_types.cpp | 12 +- .../cdm_engine_metrics_decorator_unittest.cpp | 2 +- core/test/config_test_env.h | 17 +- core/test/core_integration_test.cpp | 4 + core/test/create_test_file_system.h | 4 +- core/test/fake_provisioning_server.cpp | 27 +- core/test/http_socket.h | 12 +- core/test/license_holder.cpp | 6 +- core/test/license_request.h | 14 +- core/test/license_unittest.cpp | 102 ++--- core/test/message_dumper.cpp | 26 +- core/test/provisioning_holder.cpp | 2 +- core/test/reboot_test.cpp | 6 +- core/test/reboot_test.h | 2 + core/test/test_base.cpp | 9 +- core/test/url_request.cpp | 82 ++-- core/test/url_request.h | 18 +- .../ce/wv_factory_extractor.cpp | 35 +- factory_upload_tool/ce/wv_upload_tool.py | 130 +++++- .../common/src/WidevineOemcryptoInterface.cpp | 8 +- oemcrypto/include/OEMCryptoCENC.h | 48 +- oemcrypto/odk/include/core_message_features.h | 4 +- oemcrypto/odk/include/odk_structs.h | 4 +- oemcrypto/odk/src/core_message_features.cpp | 2 +- oemcrypto/odk/src/odk_timer.c | 2 +- oemcrypto/odk/test/odk_test.cpp | 4 +- oemcrypto/test/GEN_api_lock_file.c | 4 + oemcrypto/test/extract_bcc_tool.cpp | 131 +----- .../test/install_prov30_oem_cert_tool.cpp | 2 + oemcrypto/test/oec_decrypt_fallback_chain.cpp | 16 +- oemcrypto/test/oec_device_features.cpp | 18 +- oemcrypto/test/oec_device_features.h | 12 + oemcrypto/test/oec_session_util.cpp | 11 +- oemcrypto/test/oemcrypto_basic_test.cpp | 424 ++++++++++++++++-- oemcrypto/test/oemcrypto_decrypt_test.cpp | 32 -- .../test/oemcrypto_session_tests_helper.cpp | 3 + oemcrypto/test/oemcrypto_usage_table_test.cpp | 6 + oemcrypto/util/include/bcc_validator.h | 11 +- oemcrypto/util/include/cbor_validator.h | 11 +- oemcrypto/util/include/cmac.h | 10 +- .../util/include/device_info_validator.h | 12 +- oemcrypto/util/include/hmac.h | 2 +- oemcrypto/util/include/oemcrypto_drm_key.h | 6 +- oemcrypto/util/include/oemcrypto_ecc_key.h | 29 +- .../util/include/oemcrypto_key_deriver.h | 10 +- oemcrypto/util/include/oemcrypto_oem_cert.h | 13 +- oemcrypto/util/include/oemcrypto_rsa_key.h | 27 +- oemcrypto/util/include/scoped_object.h | 7 +- .../include/signed_csr_payload_validator.h | 7 +- oemcrypto/util/include/wvcrc32.h | 2 +- oemcrypto/util/src/bcc_validator.cpp | 7 +- oemcrypto/util/src/cbor_validator.cpp | 1 - oemcrypto/util/src/oemcrypto_ecc_key.cpp | 2 +- oemcrypto/util/src/oemcrypto_oem_cert.cpp | 13 +- oemcrypto/util/test/oem_cert_test.h | 2 +- .../util/test/oemcrypto_ref_test_utils.h | 2 +- platforms/example/no_oemcrypto.cpp | 11 +- util/include/string_conversions.h | 14 +- util/include/wv_class_utils.h | 112 +++++ util/src/string_conversions.cpp | 65 ++- util/test/base64_test.cpp | 111 +++++ 122 files changed, 2259 insertions(+), 1082 deletions(-) create mode 100644 cdm/test/test_version.cpp create mode 100644 cdm/test/test_version_xctest.mm mode change 100644 => 100755 factory_upload_tool/ce/wv_upload_tool.py create mode 100644 util/include/wv_class_utils.h diff --git a/CHANGELOG.md b/CHANGELOG.md index e96ba0da..190e15ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,17 +2,49 @@ [TOC] +## 19.3.0 (2024-09-04) + +This is a minor release with bug fixes and test improvements, as well as +internal code-quality cleanups that do not affect the CDM's behavior. However, +because of improvements to the BCC Factory Upload Tool, we recommend that all +partners who use this tool upgrade to version 19.3.0. + +### Features + + - Added workaround for OEMCrypto implementations with slightly corrupted build + information + - The BCC Factory Upload Tool supports new command-line options for dry runs, + batch checks, version-checking, and verbose output. + +### Tests + + - Added new tests to better validate the behavior of + `OEMCrypto_BuildInformation()` + - Verifies output length is set correctly + - Verifies content is ASCII JSON without trailing null bytes + - Verifies documented JSON fields: required fields are present, and optional + and required fields are the correct JSON types + +### Bug Fixes + + - Fixed decrypt failures on devices with low TEE memory caused by sending an + output buffer to decrypt that was much larger than necessary + - Several BCC Factory Upload Tool fixes: + - Added the missing `FileSystem::Exists()` function + - Fixed a bug causing the output to be unnecessarily padded + - Fixed an issue where fields containing JSON were not properly escaped + ## 19.2.0 (2024-06-24) This is a minor release with bug fixes and test improvements. -## Features +### Features - Supports up to OEMCrypto v19 - Added new test data for entitled licenses - Added new tests for clear lead sample decryption -## Bug Fixes +### Bug Fixes - Fixed backwards compatibility issues found in License Protocol v2.2 - Improved error logging for tests @@ -27,7 +59,7 @@ the OEMCrypto version. CE CDM v19.1.0 includes all changes from CE CDM v18.5.0. -## Features +### Features - Supports up to OEMCrypto v19.1, including new OEMCrypto tests introduced since OEMCrypto v18. @@ -47,7 +79,7 @@ CE CDM v19.1.0 includes all changes from CE CDM v18.5.0. - Improved code performance slightly by reducing copy operations. - Added additional tests to check for problems when moving usage entries. -## Bug Fixes +### Bug Fixes - Fixed a potential out-of-bounds read in the logging code for certain invalid severity level values. @@ -64,7 +96,7 @@ CE CDM v18.5.0 includes all changes from CE CDM v17.3.0 and v18.1.0. address two major bugs in the CE CDM code which could result in lost offline licenses or app crashes. See _Bug Fixes_ for 18.5.0 and 17.3.0 for details. -## Features +### Features - Supports up to OEMCrypto v18.5, including new OEMCrypto tests introduced since OEMCrypto v18.1. diff --git a/README.md b/README.md index b781057c..045cd1cf 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# Widevine CE CDM 19.2.0 +# Widevine CE CDM 19.3.0 -Released 2024-06-24 +Released 2024-09-04 ## Getting Started @@ -10,23 +10,37 @@ following to learn more about the contents of this project and how to use them: The [Widevine Developer Site][wv-devsite] documents the CDM API and describes how to integrate the CDM into a system. -## New in v19.2.0 +## New in v19.3.0 -This is a minor release with minimal changes. +This is a minor release with bug fixes and test improvements, as well as +internal code-quality cleanups that do not affect the CDM's behavior. However, +because of improvements to the BCC Factory Upload Tool, we recommend that all +partners who use this tool upgrade to version 19.3.0. -## Features +### Features - - Supports up to OEMCrypto v19 - - Added new test data for entitled licenses - - Added new tests for clear lead sample decryption + - Added workaround for OEMCrypto implementations with slightly corrupted build + information + - The BCC Factory Upload Tool supports new command-line options for dry runs, + batch checks, version-checking, and verbose output. + +### Tests + + - Added new tests to better validate the behavior of + `OEMCrypto_BuildInformation()` + - Verifies output length is set correctly + - Verifies content is ASCII JSON without trailing null bytes + - Verifies documented JSON fields: required fields are present, and optional + and required fields are the correct JSON types ### Bug Fixes - - Fixed backwards compatibility issues found in License Protocol v2.2 - - Improved error logging for tests - - Small fixes to reduce compiler warning - - Fixed URL error found for tests using different license server SDK - - Skip CAS tests on non-CAS devices + - Fixed decrypt failures on devices with low TEE memory caused by sending an + output buffer to decrypt that was much larger than necessary + - Several BCC Factory Upload Tool fixes: + - Added the missing `FileSystem::Exists()` function + - Fixed a bug causing the output to be unnecessarily padded + - Fixed an issue where fields containing JSON were not properly escaped [CHANGELOG.md](./CHANGELOG.md) lists the major changes for each past release. diff --git a/cdm/cdm_unittests.gyp b/cdm/cdm_unittests.gyp index 13c915cb..d8a752fc 100644 --- a/cdm/cdm_unittests.gyp +++ b/cdm/cdm_unittests.gyp @@ -19,6 +19,9 @@ 'device_files_target': 'cdm.gyp:device_files', # The path to the prebuilt CDM to test against. (iOS only) 'prebuilt_cdm_path%': '', + # The path to the include directory. + 'test_include_dir%': 'include', + 'expected_cdm_version%': '', 'supports_dynamic_perf_test%': 0, 'json_dir': '../third_party/nlohmann-json', 'jsmn_dir': '../third_party/jsmn', @@ -150,6 +153,21 @@ ], }], }], # condition: supports_dynamic_perf_test + ['prebuilt_cdm_path!=""', { + 'targets': [{ + 'target_name': 'widevine_check_version', + 'type': 'executable', + 'sources': [ + 'test/test_version.cpp', + ], + 'include_dirs': [ + '<(test_include_dir)', + ], + 'libraries': [ + '<(prebuilt_cdm_path)', + ], + }], + }], ['OS=="ios"', { 'targets': [{ 'toolsets' : [ 'target' ], @@ -165,6 +183,7 @@ ], 'xcode_settings': { 'INFOPLIST_FILE': 'test/info.plist', + 'PROVISIONING_PROFILE_SPECIFIER': 'Google Development', }, 'conditions': [ ['prebuilt_cdm_path!=""', { @@ -187,6 +206,7 @@ 'type': 'loadable_module', 'mac_xctest_bundle': '1', 'defines': [ + 'EXPECTED_CDM_VERSION="<(expected_cdm_version)"', 'GTEST_FILTER="<(gtest_filter)"', ], 'xcode_settings': { @@ -210,22 +230,27 @@ '../core/test/license_request.cpp', '../core/test/url_request.cpp', '../util/src/string_conversions.cpp', + '../util/src/string_format.cpp', '../util/test/test_clock.cpp', '../util/test/test_sleep.cpp', 'src/log.cpp', + 'src/logger_global.cpp', 'test/perf_test.cpp', 'test/perf_test_xctest.mm', 'test/test_host.cpp', + 'test/test_version_xctest.mm', ], 'includes': [ '../util/libssl_dependency.gypi' ], 'include_dirs': [ + '<(test_include_dir)', '../core/include', '../core/test', + '../oemcrypto/include', '../util/include', '../util/test', - 'include', ], 'dependencies': [ + '../cdm/cdm.gyp:device_files', '../third_party/googletest.gyp:gmock', '../third_party/googletest.gyp:gtest', 'dummy_app', diff --git a/cdm/extract_bcc_tool.gyp b/cdm/extract_bcc_tool.gyp index c5f61086..e86a0aca 100644 --- a/cdm/extract_bcc_tool.gyp +++ b/cdm/extract_bcc_tool.gyp @@ -15,17 +15,24 @@ 'toolsets' : [ 'target' ], 'target_name': 'extract_bcc_tool', 'sources': [ + '../factory_upload_tool/ce/log.cpp', + '../factory_upload_tool/ce/properties_ce.cpp', + '../factory_upload_tool/ce/wv_factory_extractor.cpp', + '../factory_upload_tool/common/src/WidevineOemcryptoInterface.cpp', '../oemcrypto/test/extract_bcc_tool.cpp', + '../util/src/string_conversions.cpp', ], 'include_dirs': [ - '../cdm/include', - '../core/include', - '../metrics/include', + '../factory_upload_tool/ce', + '../factory_upload_tool/common/include', '../oemcrypto/include', '../util/include', ], - 'dependencies': [ - 'cdm.gyp:widevine_ce_cdm_static', + # The liboemcrypto.so is dynamically loaded, causing control flow + # integrity check failed during indirect function call. + 'cflags/': [ + ['exclude', '^-fsanitize'], + ['exclude', '^-fno-sanitize'], ], 'msvs_settings': { 'VCLinkerTool': { diff --git a/cdm/include/cdm_version.h b/cdm/include/cdm_version.h index ec045ea4..ed280574 100644 --- a/cdm/include/cdm_version.h +++ b/cdm/include/cdm_version.h @@ -10,7 +10,7 @@ # define CDM_VERSION_MAJOR 19 #endif #ifndef CDM_VERSION_MINOR -# define CDM_VERSION_MINOR 2 +# define CDM_VERSION_MINOR 3 #endif #ifndef CDM_VERSION_PATCH # define CDM_VERSION_PATCH 0 diff --git a/cdm/test/cdm_test.cpp b/cdm/test/cdm_test.cpp index e41b4638..ca7c92b7 100644 --- a/cdm/test/cdm_test.cpp +++ b/cdm/test/cdm_test.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -2219,6 +2220,208 @@ TEST_F(CdmTest, GetMetrics) { EXPECT_NE(metrics.length(), 0u); } +// These test reverse-compatibility of offline licenses. These tests are +// disabled by default as they require multiple runs using different builds. +// First run the *Setup* test with the old build to save the files into the test +// data path. Then copy the *.dat files into the new build directory. Finally +// run the remaining tests on the new build. +// You can set GTEST_ALSO_RUN_DISABLED_TESTS variable to run these tests too. +class OfflineReverseCompatTest : public CdmTest { + public: + void SaveToFiles(const std::string& session_id) { + auto save = [](const std::string& data, const std::string& path) { + std::ofstream os(path); + os.write(data.data(), data.size()); + ASSERT_TRUE(os); + }; + ASSERT_NO_FATAL_FAILURE(save(session_id, kSessionIdFile)); + + std::string data; + ASSERT_TRUE(g_host->global_storage().SaveToString(&data)); + ASSERT_NO_FATAL_FAILURE(save(data, kGlobalStorageFile)); + + ASSERT_TRUE(g_host->per_origin_storage().SaveToString(&data)); + ASSERT_NO_FATAL_FAILURE(save(data, kPerOriginStorageFile)); + } + + void LoadFromFiles(std::string* session_id) { + auto load = [](const std::string& path, std::string* data) { + std::ifstream file(path); + ASSERT_TRUE(file); + data->assign(std::istreambuf_iterator(file), + std::istreambuf_iterator()); + }; + ASSERT_NO_FATAL_FAILURE(load(kSessionIdFile, session_id)); + + std::string data; + ASSERT_NO_FATAL_FAILURE(load(kGlobalStorageFile, &data)); + ASSERT_TRUE(g_host->global_storage().LoadFromString(data)); + + ASSERT_NO_FATAL_FAILURE(load(kPerOriginStorageFile, &data)); + ASSERT_TRUE(g_host->per_origin_storage().LoadFromString(data)); + } + + private: + static constexpr const char* kSessionIdFile = "session_id.dat"; + static constexpr const char* kGlobalStorageFile = "global.dat"; + static constexpr const char* kPerOriginStorageFile = "per_origin.dat"; +}; + +TEST_F(OfflineReverseCompatTest, DISABLED_Setup) { + constexpr const size_t kNumSessions = 20; + std::string first_session_id; + std::string session_id; + std::string response; + EnsureProvisioned(); + + ASSERT_NO_FATAL_FAILURE(CreateSessionAndFetchLicense( + Cdm::kPersistentLicense, Cdm::kCenc, &first_session_id, &response)); + ASSERT_EQ(Cdm::kSuccess, updateWithRetry(first_session_id, response)); + ASSERT_EQ(Cdm::kSuccess, cdm_->close(first_session_id)); + + for (size_t i = 0; i < kNumSessions; i++) { + ASSERT_NO_FATAL_FAILURE(CreateSessionAndFetchLicense( + Cdm::kPersistentLicense, Cdm::kCenc, &session_id, &response)); + ASSERT_EQ(Cdm::kSuccess, updateWithRetry(session_id, response)); + ASSERT_EQ(Cdm::kSuccess, cdm_->close(session_id)); + } + + ASSERT_NO_FATAL_FAILURE(SaveToFiles(first_session_id)); +} + +TEST_F(OfflineReverseCompatTest, DISABLED_LoadAndDecrypt) { + std::string session_id; + ASSERT_NO_FATAL_FAILURE(LoadFromFiles(&session_id)); + ASSERT_EQ(cdm_->getProvisioningStatus(), Cdm::kProvisioned); + + Cdm::Status status = cdm_->load(session_id); + ASSERT_EQ(Cdm::kSuccess, status); + + Cdm::Subsample subsample; + subsample.protected_bytes = kInputSize; + + // Set up sample + Cdm::Sample sample; + sample.input.iv = kIvCenc; + sample.input.iv_length = kIvCencSize; + sample.input.data = kInput; + sample.input.data_length = kInputSize; + sample.input.subsamples = &subsample; + sample.input.subsamples_length = 1; + std::vector output_buffer(sample.input.data_length); + sample.output.data = output_buffer.data(); + sample.output.data_length = static_cast(output_buffer.size()); + + // Set up batch to decrypt + Cdm::DecryptionBatch batch; + batch.samples = &sample; + batch.samples_length = 1; + batch.key_id = kKeyIdCtr.data(); + batch.key_id_length = static_cast(kKeyIdCtr.size()); + batch.pattern = kPatternNone; + batch.encryption_scheme = Cdm::kAesCtr; + + std::vector expected_output(kOutputCenc, + kOutputCenc + kOutputCencSize); + status = cdm_->decrypt(session_id, batch); + ASSERT_EQ(Cdm::kSuccess, status); + EXPECT_EQ(expected_output, output_buffer); +} + +TEST_F(OfflineReverseCompatTest, DISABLED_CreateSessionThenLoad) { + std::string session_id; + ASSERT_NO_FATAL_FAILURE(LoadFromFiles(&session_id)); + ASSERT_EQ(cdm_->getProvisioningStatus(), Cdm::kProvisioned); + + // This will cause us to re-provision. + EnsureProvisioned(); + + // Create a new streaming license to use the new CDM/OEMCrypto. + std::string new_session_id; + std::string response; + ASSERT_NO_FATAL_FAILURE(CreateSessionAndFetchLicense( + Cdm::kTemporary, Cdm::kCenc, &new_session_id, &response)); + EXPECT_CALL(*this, onKeyStatusesChange(new_session_id, true)); + Cdm::Status status = updateWithRetry(new_session_id, response); + ASSERT_EQ(Cdm::kSuccess, status); + Mock::VerifyAndClear(this); + + // Loading the offline license should still succeed. + status = cdm_->load(session_id); + ASSERT_EQ(Cdm::kSuccess, status); +} + +TEST_F(OfflineReverseCompatTest, DISABLED_LoadThenCreateSession) { + std::string session_id; + ASSERT_NO_FATAL_FAILURE(LoadFromFiles(&session_id)); + ASSERT_EQ(cdm_->getProvisioningStatus(), Cdm::kProvisioned); + + // Loading the offline license should not break new sessions. + Cdm::Status status = cdm_->load(session_id); + ASSERT_EQ(Cdm::kSuccess, status); + ASSERT_EQ(cdm_->close(session_id), Cdm::kSuccess); + + // This will cause us to re-provision. + EnsureProvisioned(); + + std::string new_session_id; + std::string response; + ASSERT_NO_FATAL_FAILURE(CreateSessionAndFetchLicense( + Cdm::kTemporary, Cdm::kCenc, &new_session_id, &response)); + EXPECT_CALL(*this, onKeyStatusesChange(new_session_id, true)); + status = updateWithRetry(new_session_id, response); + ASSERT_EQ(Cdm::kSuccess, status); + Mock::VerifyAndClear(this); +} + +TEST_F(OfflineReverseCompatTest, DISABLED_LoadThenCreateOfflineSession) { + std::string session_id; + ASSERT_NO_FATAL_FAILURE(LoadFromFiles(&session_id)); + ASSERT_EQ(cdm_->getProvisioningStatus(), Cdm::kProvisioned); + + // Loading the offline license should not break new sessions. + Cdm::Status status = cdm_->load(session_id); + ASSERT_EQ(Cdm::kSuccess, status); + ASSERT_EQ(cdm_->close(session_id), Cdm::kSuccess); + + // This will cause us to re-provision. + EnsureProvisioned(); + + std::string new_session_id; + std::string response; + ASSERT_NO_FATAL_FAILURE(CreateSessionAndFetchLicense( + Cdm::kPersistentLicense, Cdm::kCenc, &new_session_id, &response)); + EXPECT_CALL(*this, onKeyStatusesChange(new_session_id, true)); + status = updateWithRetry(new_session_id, response); + ASSERT_EQ(Cdm::kSuccess, status); + Mock::VerifyAndClear(this); +} + +TEST_F(OfflineReverseCompatTest, DISABLED_NewCdmInstance) { + std::string session_id; + ASSERT_NO_FATAL_FAILURE(LoadFromFiles(&session_id)); + ASSERT_EQ(cdm_->getProvisioningStatus(), Cdm::kProvisioned); + + // This will cause us to re-provision. + EnsureProvisioned(); + + std::string new_session_id; + std::string response; + ASSERT_NO_FATAL_FAILURE(CreateSessionAndFetchLicense( + Cdm::kTemporary, Cdm::kCenc, &new_session_id, &response)); + EXPECT_CALL(*this, onKeyStatusesChange(new_session_id, true)); + Cdm::Status status = updateWithRetry(new_session_id, response); + ASSERT_EQ(Cdm::kSuccess, status); + Mock::VerifyAndClear(this); + + // Loading should still work with an entirely new CDM instance. + ASSERT_NO_FATAL_FAILURE(RecreateCdm(true /* privacy_mode */)); + EnsureProvisioned(); + status = cdm_->load(session_id); + ASSERT_EQ(Cdm::kSuccess, status); +} + + TEST_P(CdmTestWithDecryptParam, DecryptToClearBuffer) { EnsureProvisioned(); DecryptParam param = GetParam(); diff --git a/cdm/test/cdm_test_runner.cpp b/cdm/test/cdm_test_runner.cpp index 02fcc07a..a951dceb 100644 --- a/cdm/test/cdm_test_runner.cpp +++ b/cdm/test/cdm_test_runner.cpp @@ -7,11 +7,14 @@ #include #include #include +#include +#include #include #include #include "cdm.h" #include "log.h" +#include "oec_device_features.h" #include "stderr_logger.h" #include "test_base.h" #include "test_host.h" @@ -22,13 +25,56 @@ std::string g_sandbox_id; namespace widevine { namespace { constexpr char kSandboxIdParam[] = "--sandbox_id="; +constexpr char kCertPathParam[] = "--cert_path="; +constexpr char kCertKeyPathParam[] = "--cert_key_path="; // Following the pattern established by help text in test_base.cpp constexpr char kExtraHelpText[] = " --sandbox_id=\n" " Specifies the Sandbox ID that should be sent to OEMCrypto via\n" " OEMCrypto_SetSandbox(). On most platforms, since Sandbox IDs are not\n" - " in use, this parameter should be omitted.\n"; + " in use, this parameter should be omitted.\n" + " --cert_path=\n" + " Path to a preloaded DRM certificate. This may speed up some tests\n" + " by skipping the provisioning step. On most platforms, this parameter\n" + " parameter should be omitted.\n" + " --cert_key_path=\n" + " Path to a key in preloaded DRM certificate. This should only be used\n" + " if the device has a baked in cert.\n"; + +bool ReadFile(const std::string& path, std::string* output) { + output->clear(); + std::ifstream fs(path, std::ios::in | std::ios::binary); + if (!fs) { + LOGE("Failed to open %s: %s", path.c_str(), strerror(errno)); + return false; + } + std::stringstream buffer; + buffer << fs.rdbuf(); + *output = buffer.str(); + return true; +} + +// Reads a file from the command line arguments. +// The file path is expected to be the first argument after the flag. +// For example, if the flag is "--cert_path=" and the command line is +// cdm_test_runner --cert_path=/path/to/cert +// then the file at /path/to/cert will be read. +// The flag will be removed from the command line arguments. +bool ReadFileFromArg(const char* path_flag, std::vector& args, + std::string* data) { + auto path_iter = std::find_if(std::begin(args) + 1, std::end(args), + [path_flag](const std::string& elem) -> bool { + return elem.find(path_flag) == 0; + }); + if (path_iter != std::end(args)) { + const std::string path = path_iter->substr(strlen(path_flag)); + args.erase(path_iter); + return ReadFile(path, data); + } + return false; +} + } // namespace int Main(Cdm::IStorage* storage, Cdm::IClock* clock, Cdm::ITimer* timer, @@ -50,6 +96,15 @@ int Main(Cdm::IStorage* storage, Cdm::IClock* clock, Cdm::ITimer* timer, (void)status; // status is now used when assertions are turned off. assert(status == Cdm::kSuccess); + std::string data; + if (ReadFileFromArg(kCertPathParam, args, &data)) { + g_host->set_baked_in_cert(data); + } + if (ReadFileFromArg(kCertKeyPathParam, args, &data)) { + wvoec::global_features.set_rsa_test_key( + std::vector(data.begin(), data.end())); + } + std::vector new_argv(args.size()); std::transform( std::begin(args), std::end(args), std::begin(new_argv), diff --git a/cdm/test/create_test_file_system.cpp b/cdm/test/create_test_file_system.cpp index 0b069cba..f11c0f8b 100644 --- a/cdm/test/create_test_file_system.cpp +++ b/cdm/test/create_test_file_system.cpp @@ -6,6 +6,7 @@ #include "test_host.h" -wvutil::FileSystem* CreateTestFileSystem() { - return new wvutil::FileSystem("", &g_host->per_origin_storage()); +std::unique_ptr CreateTestFileSystem() { + return std::make_unique("", + &g_host->per_origin_storage()); } diff --git a/cdm/test/perf_test.cpp b/cdm/test/perf_test.cpp index a9554370..ba1dcfa1 100644 --- a/cdm/test/perf_test.cpp +++ b/cdm/test/perf_test.cpp @@ -169,7 +169,7 @@ bool SendPost(const std::string& message, std::string* response) { std::unique_ptr CreateCdm(EventListener* event_listener) { std::unique_ptr ret( - create_func(event_listener, &g_host->per_origin_storage(), true)); + create_func(event_listener, &g_host->per_origin_storage(), true, false)); if (ret) { EXPECT_SUCCESS(ret->setServiceCertificate( Cdm::kProvisioningService, @@ -182,8 +182,7 @@ std::unique_ptr CreateCdm(EventListener* event_listener) { class GlobalEnv : public testing::Environment { public: - GlobalEnv(InitFuncType init_func, const std::string& cert) - : init_func_(init_func), cert_(cert) {} + GlobalEnv(InitFuncType init_func) : init_func_(init_func) {} void SetUp() override { // Manually set the logger because `TestHost` makes logging calls before @@ -191,7 +190,6 @@ class GlobalEnv : public testing::Environment { g_logger = &g_stderr_logger; g_host = new TestHost; - if (!cert_.empty()) g_host->per_origin_storage().write("cert.bin", cert_); Cdm::LogLevel log_level = Cdm::kErrors; if (const char* verbose = getenv("VERBOSE_OUTPUT")) { @@ -203,7 +201,6 @@ class GlobalEnv : public testing::Environment { private: const InitFuncType init_func_; - const std::string cert_; }; } // namespace @@ -331,8 +328,7 @@ std::string PrintDecryptParam(const testing::TestParamInfo& info) { INSTANTIATE_TEST_SUITE_P(Decrypt, DecryptPerfTest, testing::Bool(), PrintDecryptParam); -int PerfTestMain(InitFuncType init_func, CreateFuncType create, - const std::string& cert) { +int PerfTestMain(InitFuncType init_func, CreateFuncType create) { #ifdef _DEBUG // Don't use #error since we build all targets and we don't want to fail the // debug build (and we can't have configuration-specific targets). @@ -340,7 +336,7 @@ int PerfTestMain(InitFuncType init_func, CreateFuncType create, return 1; #else create_func = create; - testing::AddGlobalTestEnvironment(new GlobalEnv(init_func, cert)); + testing::AddGlobalTestEnvironment(new GlobalEnv(init_func)); return RUN_ALL_TESTS(); #endif diff --git a/cdm/test/perf_test.h b/cdm/test/perf_test.h index b8fe9b96..fa6482f9 100644 --- a/cdm/test/perf_test.h +++ b/cdm/test/perf_test.h @@ -14,10 +14,10 @@ namespace widevine { using InitFuncType = Cdm::Status (*)(Cdm::SecureOutputType, Cdm::IStorage*, Cdm::IClock*, Cdm::ITimer*, Cdm::ILogger*, Cdm::LogLevel); -using CreateFuncType = Cdm* (*)(Cdm::IEventListener*, Cdm::IStorage*, bool); +using CreateFuncType = + Cdm* (*)(Cdm::IEventListener*, Cdm::IStorage*, bool, bool); -int PerfTestMain(InitFuncType init_func, CreateFuncType create_func, - const std::string& cert); +int PerfTestMain(InitFuncType init_func, CreateFuncType create_func); } // namespace widevine diff --git a/cdm/test/perf_test_dynamic.cpp b/cdm/test/perf_test_dynamic.cpp index 9adc5d39..73b5eb44 100644 --- a/cdm/test/perf_test_dynamic.cpp +++ b/cdm/test/perf_test_dynamic.cpp @@ -24,24 +24,6 @@ constexpr char kInitName[] = constexpr char kCreateName[] = "_ZN8widevine3Cdm6createEPNS0_14IEventListenerEPNS0_8IStorageEbb"; -bool ReadFile(const std::string& path, std::string* output) { - constexpr size_t kReadSize = 8 * 1024; - std::ifstream fs(path, std::ios::in | std::ios::binary); - if (!fs) return false; - while (true) { - const size_t offset = output->size(); - output->resize(output->size() + kReadSize); - fs.read(&output->at(offset), kReadSize); - if (fs.eof()) { - output->resize(offset + fs.gcount()); - return true; - } else if (!fs) { - fprintf(stderr, "Error reading from cert file\n"); - return false; - } - } -} - std::tuple LoadCdm(const char* path) { // Note we will leak the library object; but this is just for tests and will // be unloaded when we exit anyway. @@ -70,15 +52,11 @@ int64_t Clock::GetCurrentTime() { return g_host->now() / 1000; } int main(int argc, char** argv) { testing::InitGoogleTest(&argc, argv); - if (argc != 3) { - fprintf(stderr, "Usage: %s \n", argv[0]); + if (argc != 2) { + fprintf(stderr, "Usage: %s \n", argv[0]); return 1; } - std::string cert; - if (!widevine::ReadFile(argv[2], &cert)) - return 1; - auto funcs = widevine::LoadCdm(argv[1]); - return widevine::PerfTestMain(std::get<0>(funcs), std::get<1>(funcs), cert); + return widevine::PerfTestMain(std::get<0>(funcs), std::get<1>(funcs)); } diff --git a/cdm/test/perf_test_xctest.mm b/cdm/test/perf_test_xctest.mm index d2b8527f..128737cb 100644 --- a/cdm/test/perf_test_xctest.mm +++ b/cdm/test/perf_test_xctest.mm @@ -20,7 +20,7 @@ int argc = 1; testing::InitGoogleTest(&argc, argv); - XCTAssertEqual(widevine::PerfTestMain(&widevine::Cdm::initialize, &widevine::Cdm::create, ""), 0); + XCTAssertEqual(widevine::PerfTestMain(&widevine::Cdm::initialize, &widevine::Cdm::create), 0); } @end diff --git a/cdm/test/test_host.cpp b/cdm/test/test_host.cpp index 3a4826a6..7fdcf1af 100644 --- a/cdm/test/test_host.cpp +++ b/cdm/test/test_host.cpp @@ -137,6 +137,11 @@ bool TestHost::Storage::SaveToString(std::string* data) const { } bool TestHost::Storage::read(const std::string& name, std::string* data) { + if (wvutil::kLegacyCertificateFileName == name && + !g_host->baked_in_cert().empty()) { + *data = g_host->baked_in_cert(); + return true; + } StorageMap::iterator it = files_.find(name); bool ok = it != files_.end(); LOGV("read file: %s: %s", name.c_str(), ok ? "ok" : "fail"); @@ -148,12 +153,21 @@ bool TestHost::Storage::read(const std::string& name, std::string* data) { bool TestHost::Storage::write(const std::string& name, const std::string& data) { LOGV("write file: %s", name.c_str()); + if (wvutil::kLegacyCertificateFileName == name && + !g_host->baked_in_cert().empty()) { + return false; + } if (!CheckFilename(name)) return false; files_[name] = data; return true; } bool TestHost::Storage::exists(const std::string& name) { + if (wvutil::kLegacyCertificateFileName == name && + !g_host->baked_in_cert().empty()) { + LOGV("exists? %s: always", name.c_str()); + return true; + } StorageMap::iterator it = files_.find(name); bool ok = it != files_.end(); LOGV("exists? %s: %s", name.c_str(), ok ? "true" : "false"); @@ -174,6 +188,11 @@ bool TestHost::Storage::remove(const std::string& name) { } int32_t TestHost::Storage::size(const std::string& name) { + if (wvutil::kLegacyCertificateFileName == name && + !g_host->baked_in_cert().empty()) { + LOGV("size? %s: always", name.c_str()); + return static_cast(g_host->baked_in_cert().size()); + } StorageMap::iterator it = files_.find(name); bool ok = (it != files_.end()); LOGV("size? %s: %s", name.c_str(), ok ? "ok" : "fail"); diff --git a/cdm/test/test_host.h b/cdm/test/test_host.h index 29622dc3..5c6b4747 100644 --- a/cdm/test/test_host.h +++ b/cdm/test/test_host.h @@ -69,6 +69,13 @@ class TestHost : public widevine::Cdm::IClock, void setTimeout(int64_t delay_ms, IClient* client, void* context) override; void cancel(IClient* client) override; + // If this is set, then the storage will return this as a baked in cert. + // Trying to write a new cert will generate an error. + const std::string& baked_in_cert() const { return baked_in_cert_; }; + void set_baked_in_cert(const std::string& baked_in_cert) { + baked_in_cert_ = baked_in_cert; + }; + private: struct Timer { Timer(int64_t expiry_time, IClient* client, void* context) @@ -95,6 +102,7 @@ class TestHost : public widevine::Cdm::IClock, Storage global_storage_; Storage per_origin_storage_; + std::string baked_in_cert_; }; // Owned and managed by the test runner. diff --git a/cdm/test/test_version.cpp b/cdm/test/test_version.cpp new file mode 100644 index 00000000..e444022e --- /dev/null +++ b/cdm/test/test_version.cpp @@ -0,0 +1,26 @@ +// Copyright 2022 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. + +#include + +#include "cdm.h" +#include "cdm_version.h" + +int main(int argc, char** argv) { + if (argc != 2) { + fprintf(stderr, "Usage: %s \n", argv[0]); + return 1; + } + + const char* bundle_version = argv[1]; + fprintf(stderr, "Header: " CDM_VERSION "\n"); + fprintf(stderr, "Library: %s\n", widevine::Cdm::version()); + fprintf(stderr, "Bundle: %s\n", bundle_version); + if (strcmp(CDM_VERSION, widevine::Cdm::version()) || + strcmp(CDM_VERSION, bundle_version)) { + fprintf(stderr, "ERROR: Mismatched version\n"); + return 1; + } + return 0; +} diff --git a/cdm/test/test_version_xctest.mm b/cdm/test/test_version_xctest.mm new file mode 100644 index 00000000..e9bb71f6 --- /dev/null +++ b/cdm/test/test_version_xctest.mm @@ -0,0 +1,28 @@ +// Copyright 2022 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. + +#include +#import + +#include "cdm.h" +#include "cdm_version.h" + +#define STR(s) [NSString stringWithUTF8String:s] + +@interface VersionTests : XCTestCase +@end + +@implementation VersionTests + +- (void)testVersion { + fprintf(stderr, "Header: " CDM_VERSION "\n"); + fprintf(stderr, "Library: %s\n", widevine::Cdm::version()); + fprintf(stderr, "Bundle: " EXPECTED_CDM_VERSION "\n"); + + XCTAssertEqual(STR(CDM_VERSION), STR(widevine::Cdm::version())); + XCTAssertEqual(STR(CDM_VERSION), STR(EXPECTED_CDM_VERSION)); +} + +@end + diff --git a/core/include/buffer_reader.h b/core/include/buffer_reader.h index 4730610a..937dbbce 100644 --- a/core/include/buffer_reader.h +++ b/core/include/buffer_reader.h @@ -1,19 +1,17 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary // source code may only be used and distributed under the Widevine License // Agreement. - #ifndef WVCDM_CORE_BUFFER_READER_H_ #define WVCDM_CORE_BUFFER_READER_H_ -#include +#include #include #include -#include "disallow_copy_and_assign.h" +#include "wv_class_utils.h" namespace wvcdm { - // Annotate a function indicating the caller must examine the return value. // Use like: // int foo() WARN_UNUSED_RESULT; @@ -26,6 +24,9 @@ namespace wvcdm { class BufferReader { public: + BufferReader() = delete; + WVCDM_DISALLOW_COPY_AND_MOVE(BufferReader); + BufferReader(const uint8_t* buf, size_t size) : buf_(buf), size_(buf != nullptr ? size : 0), pos_(0) {} @@ -64,10 +65,6 @@ class BufferReader { template bool Read(T* t) WARN_UNUSED_RESULT; - - CORE_DISALLOW_COPY_AND_ASSIGN(BufferReader); -}; - +}; // class BufferReader } // namespace wvcdm - #endif // WVCDM_CORE_BUFFER_READER_H_ diff --git a/core/include/cdm_client_property_set.h b/core/include/cdm_client_property_set.h index 4c3071db..c0c1fe04 100644 --- a/core/include/cdm_client_property_set.h +++ b/core/include/cdm_client_property_set.h @@ -1,14 +1,12 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary // source code may only be used and distributed under the Widevine License // Agreement. - #ifndef WVCDM_CORE_CDM_CLIENT_PROPERTY_SET_H_ #define WVCDM_CORE_CDM_CLIENT_PROPERTY_SET_H_ -#include +#include #include -#include namespace wvcdm { @@ -25,8 +23,6 @@ class CdmClientPropertySet { virtual void set_session_sharing_id(uint32_t id) = 0; virtual const std::string& app_id() const = 0; virtual bool use_atsc_mode() const = 0; -}; - +}; // class CdmClientPropertySet } // namespace wvcdm - #endif // WVCDM_CORE_CDM_CLIENT_PROPERTY_SET_H_ diff --git a/core/include/cdm_engine.h b/core/include/cdm_engine.h index 90a74b8c..61282c13 100644 --- a/core/include/cdm_engine.h +++ b/core/include/cdm_engine.h @@ -1,7 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary // source code may only be used and distributed under the Widevine License // Agreement. - #ifndef WVCDM_CORE_CDM_ENGINE_H_ #define WVCDM_CORE_CDM_ENGINE_H_ @@ -14,7 +13,6 @@ #include "certificate_provisioning.h" #include "clock.h" #include "crypto_session.h" -#include "disallow_copy_and_assign.h" #include "file_store.h" #include "initialization_data.h" #include "metrics_collections.h" @@ -22,6 +20,7 @@ #include "service_certificate.h" #include "wv_cdm_constants.h" #include "wv_cdm_types.h" +#include "wv_class_utils.h" namespace wvcdm { class CdmClientPropertySet; @@ -38,6 +37,9 @@ using CdmReleaseKeySetMap = class CdmEngine { public: + CdmEngine() = delete; + WVCDM_DISALLOW_COPY_AND_MOVE(CdmEngine); + virtual ~CdmEngine(); // Session related methods @@ -495,10 +497,6 @@ class CdmEngine { // To prevent race conditions around the engine's OKP state, this mutex // should be locked before the use of any of the |okp_*| variables. std::mutex okp_mutex_; - - CORE_DISALLOW_COPY_AND_ASSIGN(CdmEngine); -}; - +}; // class CdmEngine } // namespace wvcdm - #endif // WVCDM_CORE_CDM_ENGINE_H_ diff --git a/core/include/cdm_engine_factory.h b/core/include/cdm_engine_factory.h index 7b37c2a4..dd905354 100644 --- a/core/include/cdm_engine_factory.h +++ b/core/include/cdm_engine_factory.h @@ -1,28 +1,24 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary // source code may only be used and distributed under the Widevine License // Agreement. - #ifndef WVCDM_CORE_CDM_ENGINE_FACTORY_H_ #define WVCDM_CORE_CDM_ENGINE_FACTORY_H_ -#include - #include "cdm_engine.h" #include "file_store.h" +#include "wv_class_utils.h" namespace wvcdm { // This factory is used to create an instance of the CdmEngine. class CdmEngineFactory { public: + CdmEngineFactory() = delete; + WVCDM_DISALLOW_COPY_AND_MOVE(CdmEngineFactory); + // Creates a new instance of a CdmEngine. Caller retains ownership of the // |files_system| which cannot be null. static CdmEngine* CreateCdmEngine(wvutil::FileSystem* file_system); - - private: - CORE_DISALLOW_COPY_AND_ASSIGN(CdmEngineFactory); -}; - +}; // class CdmEngineFactory } // namespace wvcdm - #endif // WVCDM_CORE_CDM_ENGINE_FACTORY_H_ diff --git a/core/include/cdm_engine_metrics_decorator.h b/core/include/cdm_engine_metrics_decorator.h index e1719254..56a05696 100644 --- a/core/include/cdm_engine_metrics_decorator.h +++ b/core/include/cdm_engine_metrics_decorator.h @@ -1,4 +1,3 @@ - // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary // source code may only be used and distributed under the Widevine License // Agreement. @@ -14,7 +13,6 @@ #include "certificate_provisioning.h" #include "clock.h" #include "crypto_session.h" -#include "disallow_copy_and_assign.h" #include "file_store.h" #include "initialization_data.h" #include "log.h" @@ -24,9 +22,9 @@ #include "service_certificate.h" #include "wv_cdm_constants.h" #include "wv_cdm_types.h" +#include "wv_class_utils.h" namespace wvcdm { - class CdmClientPropertySet; class CdmEngineFactory; class CdmSession; @@ -43,6 +41,9 @@ class WvCdmEventListener; template class CdmEngineMetricsImpl : public T { public: + CdmEngineMetricsImpl() = delete; + WVCDM_DISALLOW_COPY_AND_MOVE(CdmEngineMetricsImpl); + // This constructor initializes the instance and takes ownership of |metrics|. // |file_system| and |metrics| must not be null. // |metrics| is used within the base class constructor. So, it must be @@ -269,7 +270,6 @@ class CdmEngineMetricsImpl : public T { private: std::shared_ptr metrics_; wvutil::Clock clock_; -}; - +}; // class CdmEngineMetricsImpl } // namespace wvcdm #endif // WVCDM_CORE_CDM_ENGINE_METRICS_DECORATOR_H_ diff --git a/core/include/cdm_session.h b/core/include/cdm_session.h index 2557d0d3..dc85922a 100644 --- a/core/include/cdm_session.h +++ b/core/include/cdm_session.h @@ -1,7 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary // source code may only be used and distributed under the Widevine License // Agreement. - #ifndef WVCDM_CORE_CDM_SESSION_H_ #define WVCDM_CORE_CDM_SESSION_H_ @@ -12,7 +11,6 @@ #include "crypto_session.h" #include "device_files.h" -#include "disallow_copy_and_assign.h" #include "file_store.h" #include "initialization_data.h" #include "license.h" @@ -21,6 +19,7 @@ #include "policy_engine.h" #include "timer_metric.h" #include "wv_cdm_types.h" +#include "wv_class_utils.h" namespace wvcdm { @@ -32,6 +31,9 @@ class SystemIdExtractor; class CdmSession { public: + CdmSession() = delete; + WVCDM_DISALLOW_COPY_AND_MOVE(CdmSession); + // Creates a new instance of the CdmSession with the given |file_system| // and |metrics| parameters. Both parameters are owned by the caller and // must remain in scope throughout the scope of the new instance. |metrics| @@ -345,10 +347,6 @@ class CdmSession { bool mock_crypto_session_in_use_ = false; bool mock_license_parser_in_use_ = false; bool mock_policy_engine_in_use_ = false; - - CORE_DISALLOW_COPY_AND_ASSIGN(CdmSession); -}; - +}; // class CdmSession } // namespace wvcdm - #endif // WVCDM_CORE_CDM_SESSION_H_ diff --git a/core/include/cdm_session_map.h b/core/include/cdm_session_map.h index 61897ec8..644e4f9f 100644 --- a/core/include/cdm_session_map.h +++ b/core/include/cdm_session_map.h @@ -1,7 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary // source code may only be used and distributed under the Widevine License // Agreement. - #ifndef WVCDM_CORE_CDM_SESSION_MAP_H_ #define WVCDM_CORE_CDM_SESSION_MAP_H_ @@ -10,8 +9,8 @@ #include #include "cdm_session.h" -#include "disallow_copy_and_assign.h" #include "wv_cdm_types.h" +#include "wv_class_utils.h" namespace wvcdm { @@ -23,6 +22,8 @@ using CdmSessionList = std::list >; class CdmSessionMap { public: CdmSessionMap() {} + WVCDM_DISALLOW_COPY_AND_MOVE(CdmSessionMap); + virtual ~CdmSessionMap(); // Use |Terminate| rather than relying on the destructor to release @@ -50,10 +51,6 @@ class CdmSessionMap { std::shared_ptr* session); CdmIdToSessionMap sessions_; - - CORE_DISALLOW_COPY_AND_ASSIGN(CdmSessionMap); -}; - +}; // class CdmSessionMap } // namespace wvcdm - #endif // WVCDM_CORE_CDM_SESSION_MAP_H_ diff --git a/core/include/cdm_usage_table.h b/core/include/cdm_usage_table.h index 07cc3a3f..b7bd478a 100644 --- a/core/include/cdm_usage_table.h +++ b/core/include/cdm_usage_table.h @@ -1,7 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary // source code may only be used and distributed under the Widevine License // Agreement. - #ifndef WVCDM_CORE_CDM_USAGE_TABLE_H_ #define WVCDM_CORE_CDM_USAGE_TABLE_H_ @@ -13,17 +12,16 @@ #include "clock.h" #include "crypto_session.h" #include "device_files.h" -#include "disallow_copy_and_assign.h" #include "file_store.h" #include "metrics_collections.h" #include "wv_cdm_types.h" +#include "wv_class_utils.h" #if defined(UNIT_TEST) # include #endif namespace wvcdm { - // Offline licenses/secure stops may be securely tracked using usage // tables (OEMCrypto v9-12) or usage table headers+usage entries // (OEMCrypto v13+). This class assists with the latter, synchronizing @@ -51,6 +49,7 @@ namespace wvcdm { class CdmUsageTable { public: CdmUsageTable(); + WVCDM_DISALLOW_COPY_AND_MOVE(CdmUsageTable); virtual ~CdmUsageTable() {} // |crypto_session| is used to create or load a usage master table @@ -362,10 +361,6 @@ class CdmUsageTable { // Test related data members std::unique_ptr test_crypto_session_; - - CORE_DISALLOW_COPY_AND_ASSIGN(CdmUsageTable); -}; - +}; // class CdmUsageTable } // namespace wvcdm - #endif // WVCDM_CORE_CDM_USAGE_TABLE_H_ diff --git a/core/include/certificate_provisioning.h b/core/include/certificate_provisioning.h index 52d35fd6..4906304c 100644 --- a/core/include/certificate_provisioning.h +++ b/core/include/certificate_provisioning.h @@ -1,7 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary // source code may only be used and distributed under the Widevine License // Agreement. - #ifndef WVCDM_CORE_CERTIFICATE_PROVISIONING_H_ #define WVCDM_CORE_CERTIFICATE_PROVISIONING_H_ @@ -9,25 +8,27 @@ #include #include "crypto_session.h" -#include "disallow_copy_and_assign.h" #include "license_protocol.pb.h" #include "metrics_collections.h" #include "oemcrypto_adapter.h" #include "service_certificate.h" #include "wv_cdm_types.h" +#include "wv_class_utils.h" namespace wvutil { class FileSystem; -} +} // namespace wvutil namespace wvcdm { - class CdmClientPropertySet; class CdmSession; class ServiceCertificate; class CertificateProvisioning { public: + CertificateProvisioning() = delete; + WVCDM_DISALLOW_COPY_AND_MOVE(CertificateProvisioning); + CertificateProvisioning(metrics::CryptoMetrics* metrics) : crypto_session_(CryptoSession::MakeCryptoSession(metrics)), cert_type_(kCertificateWidevine), @@ -134,10 +135,6 @@ class CertificateProvisioning { CryptoWrappedKey::Type provisioning_40_key_type_; // Store the last provisioning request message std::string provisioning_request_message_; - - CORE_DISALLOW_COPY_AND_ASSIGN(CertificateProvisioning); -}; - +}; // class CertificateProvisioning } // namespace wvcdm - #endif // WVCDM_CORE_CERTIFICATE_PROVISIONING_H_ diff --git a/core/include/client_identification.h b/core/include/client_identification.h index 35c106b7..588e45a4 100644 --- a/core/include/client_identification.h +++ b/core/include/client_identification.h @@ -1,24 +1,24 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary // source code may only be used and distributed under the Widevine License // Agreement. - #ifndef WVCDM_CORE_CLIENT_IDENTIFICATION_H_ #define WVCDM_CORE_CLIENT_IDENTIFICATION_H_ #include -// ClientIdentification fills in the ClientIdentification portion -// of the License or Provisioning request messages. -#include "disallow_copy_and_assign.h" #include "license_protocol.pb.h" #include "wv_cdm_types.h" +#include "wv_class_utils.h" namespace wvcdm { class CryptoSession; +// ClientIdentification fills in the ClientIdentification portion +// of the License or Provisioning request messages. class ClientIdentification { public: ClientIdentification() {} + WVCDM_DISALLOW_COPY_AND_MOVE(ClientIdentification); virtual ~ClientIdentification() {} // Call this method when used with provisioning requests. |client_token| may @@ -60,8 +60,6 @@ class ClientIdentification { std::string client_token_; std::string device_id_; CryptoSession* crypto_session_ = nullptr; - - CORE_DISALLOW_COPY_AND_ASSIGN(ClientIdentification); -}; +}; // class ClientIdentification } // namespace wvcdm #endif // WVCDM_CORE_CLIENT_IDENTIFICATION_H_ diff --git a/core/include/content_key_session.h b/core/include/content_key_session.h index 833cb5ce..4469d3f1 100644 --- a/core/include/content_key_session.h +++ b/core/include/content_key_session.h @@ -1,7 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary // source code may only be used and distributed under the Widevine License // Agreement. - #ifndef WVCDM_CORE_CONTENT_KEY_SESSION_H_ #define WVCDM_CORE_CONTENT_KEY_SESSION_H_ @@ -9,10 +8,15 @@ #include "metrics_collections.h" #include "timer_metric.h" +#include "wv_class_utils.h" + namespace wvcdm { class ContentKeySession : public KeySession { public: + ContentKeySession() = delete; + WVCDM_DISALLOW_COPY_AND_MOVE(ContentKeySession); + ContentKeySession(RequestedSecurityLevel security_level, CryptoSessionId oec_session_id, metrics::CryptoMetrics* metrics) @@ -87,8 +91,6 @@ class ContentKeySession : public KeySession { CdmCipherMode cipher_mode_; std::vector key_handle_; -}; - +}; // class ContentKeySession } // namespace wvcdm - #endif // WVCDM_CORE_CONTENT_KEY_SESSION_H_ diff --git a/core/include/crypto_key.h b/core/include/crypto_key.h index f0bfcfb9..57989b10 100644 --- a/core/include/crypto_key.h +++ b/core/include/crypto_key.h @@ -1,18 +1,21 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary // source code may only be used and distributed under the Widevine License // Agreement. - #ifndef WVCDM_CORE_CRYPTO_KEY_H_ #define WVCDM_CORE_CRYPTO_KEY_H_ +#include + #include "wv_cdm_types.h" +#include "wv_class_utils.h" namespace wvcdm { class CryptoKey { public: - CryptoKey(){}; - ~CryptoKey(){}; + CryptoKey() = default; + WVCDM_DEFAULT_COPY_AND_MOVE(CryptoKey); + ~CryptoKey() {} const std::string& key_id() const { return key_id_; } const std::string& key_data() const { return key_data_; } @@ -50,8 +53,6 @@ class CryptoKey { std::string track_label_; std::string entitlement_key_id_; CdmCipherMode cipher_mode_; -}; - +}; // class CryptoKey } // namespace wvcdm - #endif // WVCDM_CORE_CRYPTO_KEY_H_ diff --git a/core/include/crypto_session.h b/core/include/crypto_session.h index e4c39277..e4f94b10 100644 --- a/core/include/crypto_session.h +++ b/core/include/crypto_session.h @@ -1,7 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary // source code may only be used and distributed under the Widevine License // Agreement. - #ifndef WVCDM_CORE_CRYPTO_SESSION_H_ #define WVCDM_CORE_CRYPTO_SESSION_H_ @@ -14,13 +13,13 @@ #include "OEMCryptoCENC.h" #include "crypto_wrapped_key.h" -#include "disallow_copy_and_assign.h" #include "key_session.h" #include "metrics_collections.h" #include "oemcrypto_adapter.h" #include "rw_lock.h" #include "timer_metric.h" #include "wv_cdm_types.h" +#include "wv_class_utils.h" namespace wvcdm { class CryptoKey; @@ -71,6 +70,8 @@ class CryptoSession { static const char* HdcpCapabilityToString(HdcpCapability hdcp_level); + CryptoSession() = delete; + WVCDM_DISALLOW_COPY_AND_MOVE(CryptoSession); virtual ~CryptoSession(); // This method will try to terminate OEMCrypto if |session_size_| is 0. @@ -584,24 +585,18 @@ class CryptoSession { // be created for the system if OTA keybox provisioning is both // required and supported by L1. static std::unique_ptr okp_fallback_policy_l1_; - - CORE_DISALLOW_COPY_AND_ASSIGN(CryptoSession); }; // class CryptoSession class CryptoSessionFactory { public: + WVCDM_DISALLOW_COPY_AND_MOVE(CryptoSessionFactory); virtual ~CryptoSessionFactory() {} virtual CryptoSession* MakeCryptoSession( metrics::CryptoMetrics* crypto_metrics); protected: friend class CryptoSession; - CryptoSessionFactory() {} - - private: - CORE_DISALLOW_COPY_AND_ASSIGN(CryptoSessionFactory); -}; - + CryptoSessionFactory() = default; +}; // class CryptoSessionFactory } // namespace wvcdm - #endif // WVCDM_CORE_CRYPTO_SESSION_H_ diff --git a/core/include/crypto_wrapped_key.h b/core/include/crypto_wrapped_key.h index bf947bb9..750796f4 100644 --- a/core/include/crypto_wrapped_key.h +++ b/core/include/crypto_wrapped_key.h @@ -6,8 +6,9 @@ #include -namespace wvcdm { +#include "wv_class_utils.h" +namespace wvcdm { // Represents OEMCrypto's wrapped private DRM key. As of v16, it is // possible for OEMCrypto to support ECC-based DRM certificates. The // format of the wrapped key is vendor specific; however, the API @@ -15,7 +16,8 @@ namespace wvcdm { class CryptoWrappedKey { public: enum Type : int32_t { kUninitialized = 0, kRsa = 1, kEcc = 2 }; - CryptoWrappedKey() {} + CryptoWrappedKey() = default; + WVCDM_DEFAULT_COPY_AND_MOVE(CryptoWrappedKey); CryptoWrappedKey(Type type, const std::string& key) : type_(type), key_(key) {} @@ -35,9 +37,10 @@ class CryptoWrappedKey { bool IsValid() const { return type_ != kUninitialized && !key_.empty(); } // Equality operator is for testing only. Real keys may have // different meta data but the same logical key. - bool operator==(const CryptoWrappedKey& other) const { + bool IsEqualTo(const CryptoWrappedKey& other) const { return type_ == other.type_ && key_ == other.key_; } + WVCDM_DEFINE_EQ_OPERATORS(CryptoWrappedKey); private: // DRM key type of the wrapped key. For wrapped keys which the type @@ -46,7 +49,5 @@ class CryptoWrappedKey { // Vendor-specific wrapped DRM key. std::string key_; }; // class CryptoWrappedKey - } // namespace wvcdm - #endif // WVCDM_CORE_CRYPTO_WRAPPED_KEY_H_ diff --git a/core/include/device_files.h b/core/include/device_files.h index e2125bea..f02c6389 100644 --- a/core/include/device_files.h +++ b/core/include/device_files.h @@ -1,7 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary // source code may only be used and distributed under the Widevine License // Agreement. -// #ifndef WVCDM_CORE_DEVICE_FILES_H_ #define WVCDM_CORE_DEVICE_FILES_H_ @@ -12,10 +11,10 @@ #include "crypto_wrapped_key.h" #include "device_files.pb.h" -#include "disallow_copy_and_assign.h" #include "okp_info.h" #include "platform.h" #include "wv_cdm_types.h" +#include "wv_class_utils.h" #if defined(UNIT_TEST) # include @@ -23,7 +22,7 @@ namespace wvutil { class FileSystem; -} +} // namespace wvutil namespace wvcdm { @@ -124,6 +123,8 @@ class DeviceFiles { CryptoWrappedKey wrapped_private_key; }; + DeviceFiles() = delete; + WVCDM_DISALLOW_COPY_AND_MOVE(DeviceFiles); DeviceFiles(wvutil::FileSystem*); virtual ~DeviceFiles(); @@ -387,10 +388,6 @@ class DeviceFiles { wvutil::FileSystem* file_system_; CdmSecurityLevel security_level_; bool initialized_; - - CORE_DISALLOW_COPY_AND_ASSIGN(DeviceFiles); -}; - +}; // class DeviceFiles } // namespace wvcdm - #endif // WVCDM_CORE_DEVICE_FILES_H_ diff --git a/core/include/entitlement_key_session.h b/core/include/entitlement_key_session.h index aecd8990..779c84cf 100644 --- a/core/include/entitlement_key_session.h +++ b/core/include/entitlement_key_session.h @@ -1,22 +1,25 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary // source code may only be used and distributed under the Widevine License // Agreement. - #ifndef WVCDM_CORE_ENTITLEMENT_KEY_SESSION_H_ #define WVCDM_CORE_ENTITLEMENT_KEY_SESSION_H_ #include #include +#include #include "OEMCryptoCENC.h" #include "content_key_session.h" #include "crypto_key.h" #include "metrics_collections.h" +#include "wv_class_utils.h" namespace wvcdm { class EntitlementKeySession : public ContentKeySession { public: + EntitlementKeySession() = delete; + WVCDM_DISALLOW_COPY_AND_MOVE(EntitlementKeySession); EntitlementKeySession(RequestedSecurityLevel security_level, CryptoSessionId oec_session_id, metrics::CryptoMetrics* metrics); @@ -49,8 +52,6 @@ class EntitlementKeySession : public ContentKeySession { // Find the current entitled content key id for the given entitlement key id. std::map current_loaded_content_keys_; EntitledKeySessionId key_session_id_; -}; - +}; // class EntitlementKeySession } // namespace wvcdm - #endif // WVCDM_CORE_ENTITLEMENT_KEY_SESSION_H_ diff --git a/core/include/initialization_data.h b/core/include/initialization_data.h index 02c216cc..e743a3b9 100644 --- a/core/include/initialization_data.h +++ b/core/include/initialization_data.h @@ -1,7 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary // source code may only be used and distributed under the Widevine License // Agreement. - #ifndef CORE_INCLUDE_INITIALIZATION_DATA_H_ #define CORE_INCLUDE_INITIALIZATION_DATA_H_ @@ -9,6 +8,7 @@ #include "license_protocol.pb.h" #include "wv_cdm_types.h" +#include "wv_class_utils.h" namespace wvcdm { @@ -16,6 +16,7 @@ class WvCdmEngineTest; class InitializationData { public: + WVCDM_DEFAULT_COPY_AND_MOVE(InitializationData); InitializationData(const std::string& type = std::string(), const CdmInitData& data = CdmInitData(), const std::string& oec_version = std::string()); @@ -94,8 +95,6 @@ class InitializationData { std::vector hls_iv_; CdmHlsMethod hls_method_; -}; - +}; // class InitializationData } // namespace wvcdm - #endif // CORE_INCLUDE_INITIALIZATION_DATA_H_ diff --git a/core/include/key_session.h b/core/include/key_session.h index 81197f19..6e12d624 100644 --- a/core/include/key_session.h +++ b/core/include/key_session.h @@ -1,26 +1,26 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary // source code may only be used and distributed under the Widevine License // Agreement. - #ifndef WVCDM_CORE_KEY_SESSION_H_ #define WVCDM_CORE_KEY_SESSION_H_ +#include #include #include "OEMCryptoCENC.h" #include "metrics_collections.h" #include "wv_cdm_types.h" +#include "wv_class_utils.h" namespace wvcdm { - class CryptoKey; class KeySession { - protected: - KeySession(metrics::CryptoMetrics* metrics) : metrics_(metrics) {} - public: typedef enum { kDefault, kEntitlement } KeySessionType; + + KeySession() = delete; + WVCDM_DISALLOW_COPY_AND_MOVE(KeySession); virtual ~KeySession() {} virtual KeySessionType Type() = 0; virtual OEMCryptoResult LoadKeys(const std::string& message, @@ -54,9 +54,9 @@ class KeySession { const std::string& signature) = 0; protected: + KeySession(metrics::CryptoMetrics* metrics) : metrics_(metrics) {} + metrics::CryptoMetrics* metrics_; -}; - +}; // class KeySession } // namespace wvcdm - #endif // WVCDM_CORE_KEY_SESSION_H_ diff --git a/core/include/license.h b/core/include/license.h index 5525ab6f..f3a5b0c0 100644 --- a/core/include/license.h +++ b/core/include/license.h @@ -1,68 +1,66 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary // source code may only be used and distributed under the Widevine License // Agreement. - #ifndef WVCDM_CORE_LICENSE_H_ #define WVCDM_CORE_LICENSE_H_ #include #include -#include "disallow_copy_and_assign.h" #include "initialization_data.h" #include "license_protocol.pb.h" #include "service_certificate.h" #include "wv_cdm_types.h" - -namespace video_widevine { -class SignedMessage; -class LicenseRequest; -class VersionInfo; -} // namespace video_widevine +#include "wv_class_utils.h" namespace wvutil { class Clock; -} +} // namespace wvutil namespace wvcdm { -class CryptoSession; -class PolicyEngine; class CdmSession; class CryptoKey; - -using ::google::protobuf::RepeatedPtrField; -using video_widevine::License_KeyContainer; -using video_widevine::VersionInfo; -using video_widevine::WidevinePsshData_EntitledKey; +class CryptoSession; +class InitializationData; +class PolicyEngine; class CdmLicense { public: - CdmLicense(const CdmSessionId& session_id); + using PsshEntitledKey = video_widevine::WidevinePsshData::EntitledKey; + + CdmLicense() = delete; + WVCDM_DISALLOW_COPY_AND_MOVE(CdmLicense); + explicit CdmLicense(const CdmSessionId& session_id); virtual ~CdmLicense(); virtual bool Init(bool use_privacy_mode, const std::string& signed_service_certificate, CryptoSession* session, PolicyEngine* policy_engine); + // == Accessors == + + virtual bool is_offline() const { return is_offline_; } + + virtual std::string provider_session_token() { + return provider_session_token_; + } + + virtual bool HasInitData() { return static_cast(stored_init_data_); } + + virtual const video_widevine::VersionInfo& GetServiceVersion() { + return latest_service_version_; + } + + virtual bool IsKeyLoaded(const KeyId& key_id); + + // == Service Certificate API == + // Override the currently-installed service certificate with a new service // certificate. virtual CdmResponseType SetServiceCertificate( const std::string& signed_service_certificate); - virtual CdmResponseType PrepareKeyRequest( - const InitializationData& init_data, const std::string& client_token, - CdmLicenseType license_type, const CdmAppParameterMap& app_parameters, - CdmKeyMessage* signed_request, std::string* server_url); - virtual CdmResponseType PrepareKeyUpdateRequest( - bool is_renewal, const CdmAppParameterMap& app_parameters, - CdmSession* cdm_session, CdmKeyMessage* signed_request, - std::string* server_url); - virtual CdmResponseType HandleKeyResponse( - bool is_restore, const CdmKeyResponse& license_response); - virtual CdmResponseType HandleKeyUpdateResponse( - bool is_renewal, bool is_restore, const CdmKeyResponse& license_response); - virtual CdmResponseType HandleEmbeddedKeyData( - const InitializationData& init_data); + // == License Restoring API == virtual CdmResponseType RestoreOfflineLicense( const std::string& client_token, const CdmKeyMessage& license_request, @@ -73,23 +71,36 @@ class CdmLicense { virtual CdmResponseType RestoreLicenseForRelease( const std::string& client_token, const CdmKeyMessage& license_request, const CdmKeyResponse& license_response); - virtual bool HasInitData() { return static_cast(stored_init_data_); } - virtual bool IsKeyLoaded(const KeyId& key_id); - virtual std::string provider_session_token() { - return provider_session_token_; - } + // == Request/Response API == - virtual bool is_offline() const { return is_offline_; } + virtual CdmResponseType PrepareKeyRequest( + const InitializationData& init_data, const std::string& client_token, + CdmLicenseType license_type, const CdmAppParameterMap& app_parameters, + CdmKeyMessage* signed_request, std::string* server_url); - virtual const VersionInfo& GetServiceVersion() { - return latest_service_version_; - } + virtual CdmResponseType PrepareKeyUpdateRequest( + bool is_renewal, const CdmAppParameterMap& app_parameters, + CdmSession* cdm_session, CdmKeyMessage* signed_request, + std::string* server_url); + + virtual CdmResponseType HandleKeyResponse( + bool is_restore, const CdmKeyResponse& license_response); + + virtual CdmResponseType HandleKeyUpdateResponse( + bool is_renewal, bool is_restore, const CdmKeyResponse& license_response); + + virtual CdmResponseType HandleEmbeddedKeyData( + const InitializationData& init_data); + + // == Utilities == static bool ExtractProviderSessionToken( const CdmKeyResponse& license_response, std::string* provider_session_token); + // == Test Accessors == + // Testing only. Caller retains ownership of pointers. void set_crypto_session(CryptoSession* crypto_session) { crypto_session_ = crypto_session; @@ -100,9 +111,42 @@ class CdmLicense { } private: + // Test Constructor. + // CdmLicense takes ownership of the clock. + CdmLicense(const CdmSessionId& session_id, wvutil::Clock* clock); + + // == Internal Request/Response API == + + // Prepare to reload a key update message. Some special code is needed to work + // around b/166010609. + // TODO(b/166007195): Remove this. + CdmResponseType PrepareKeyUpdateReload(CdmSession* cdm_session); + CdmResponseType HandleKeyErrorResponse( const video_widevine::SignedMessage& signed_message); + CdmResponseType HandleContentKeyResponse( + bool is_restore, const std::string& session_key, const std::string& msg, + const std::string& core_message, const std::string& signature, + const std::vector& license_keys, + const video_widevine::License& license); + + // Loads the entitlement keys in |license_keys| into + // the crypto session. + // + // In addition, it also extracts content keys from + // |request_entitled_keys_| and loads them for use. + CdmResponseType HandleEntitlementKeyResponse( + bool is_restore, const std::string& session_key, const std::string& msg, + const std::string& core_message, const std::string& signature, + const std::vector& license_keys, + const video_widevine::License& license); + + CdmResponseType HandleNewEntitledKeys( + const std::vector& packaged_entitled_keys); + + // == Internal Utilities == + CdmResponseType PrepareClientId( const CdmAppParameterMap& app_parameters, const std::string& provider_client_token, @@ -113,82 +157,66 @@ class CdmLicense { const std::string& request_id, video_widevine::LicenseRequest* license_request); - CdmResponseType HandleContentKeyResponse( - bool is_restore, const std::string& session_key, const std::string& msg, - const std::string& core_message, const std::string& signature, - const std::vector& key_array, - const video_widevine::License& license); - - // HandleEntitlementKeyResponse loads the entitlement keys in |key_array| into - // the crypto session. In addition, it also extracts content keys from - // |wrapped_keys_| and loads them for use. - CdmResponseType HandleEntitlementKeyResponse( - bool is_restore, const std::string& session_key, const std::string& msg, - const std::string& core_message, const std::string& signature, - const std::vector& key_array, - const video_widevine::License& license); - - // Prepare to reload a key update message. Some special code is needed to work - // around b/166010609. - // TODO(b/166007195): Remove this. - CdmResponseType PrepareKeyUpdateReload(CdmSession* cdm_session); - - CdmResponseType HandleNewEntitledKeys( - const std::vector& wrapped_keys); - template bool SetTypeAndId(CdmLicenseType license_type, const std::string& request_id, T* content_id); - CryptoSession* crypto_session_ = nullptr; - PolicyEngine* policy_engine_ = nullptr; - std::string server_url_; - std::string client_token_; + // == Creation-time Variables == + const CdmSessionId session_id_; - std::unique_ptr stored_init_data_; - bool initialized_; - std::set loaded_keys_; - std::string provider_session_token_; - video_widevine::ProtocolVersion protocol_version_; - bool renew_with_client_id_; - bool is_offline_; - - // Associated with ClientIdentification encryption - bool use_privacy_mode_; - ServiceCertificate service_certificate_; - - // Used for certificate based licensing - CdmKeyMessage key_request_; - std::unique_ptr clock_; - // For testing - // CdmLicense takes ownership of the clock. - CdmLicense(const CdmSessionId& session_id, wvutil::Clock* clock); + // == Initialization-time Variables == + + bool initialized_ = false; + CryptoSession* crypto_session_ = nullptr; + PolicyEngine* policy_engine_ = nullptr; + + // Associated with ClientIdentification encryption + bool use_privacy_mode_ = false; + ServiceCertificate service_certificate_; + + video_widevine::ProtocolVersion protocol_version_ = + video_widevine::VERSION_2_2; + + // == License Request/Response variables == + + std::string client_token_; + std::unique_ptr stored_init_data_; + + // The nonce used in the original license request. + uint32_t license_nonce_ = 0; + + CdmKeyMessage license_request_; // For entitlement key licensing. This holds the keys from the init_data. // These keys are extracted from the PSSH when we generate a license request. // It is used to load content keys after we have received a license and // entitlement keys. It is also used in updating the key status info. - std::vector wrapped_keys_; - - CdmLicenseKeyType license_key_type_; - RepeatedPtrField entitlement_keys_; + std::vector request_entitled_keys_; std::string provider_client_token_; + std::string provider_session_token_; + + bool renew_with_client_id_ = false; + + std::string renewal_server_url_; + // This is the latest version info extracted from the SignedMessage in // HandleKeyResponse - VersionInfo latest_service_version_; + video_widevine::VersionInfo latest_service_version_; - // The nonce used in the original license request. - uint32_t license_nonce_; + // == License Life-Time Variables == + + CdmLicenseKeyType license_key_type_ = kLicenseKeyTypeContent; + bool is_offline_ = false; + + std::set content_key_ids_; + std::set entitlement_key_ids_; #if defined(UNIT_TEST) friend class CdmLicenseTestPeer; #endif - - CORE_DISALLOW_COPY_AND_ASSIGN(CdmLicense); -}; +}; // class CdmLicense } // namespace wvcdm - #endif // WVCDM_CORE_LICENSE_H_ diff --git a/core/include/license_key_status.h b/core/include/license_key_status.h index 6bb6e0a5..e992d7f6 100644 --- a/core/include/license_key_status.h +++ b/core/include/license_key_status.h @@ -1,16 +1,16 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary // source code may only be used and distributed under the Widevine License // Agreement. - #ifndef WVCDM_CORE_LICENSE_KEY_STATUS_H_ #define WVCDM_CORE_LICENSE_KEY_STATUS_H_ #include +#include #include "crypto_session.h" -#include "disallow_copy_and_assign.h" #include "license_protocol.pb.h" #include "wv_cdm_types.h" +#include "wv_class_utils.h" namespace wvcdm { @@ -21,7 +21,10 @@ using video_widevine::WidevinePsshData_EntitledKey; // Holds all content and operator session keys for a session. class LicenseKeys { public: - LicenseKeys(CdmSecurityLevel security_level) + LicenseKeys() = delete; + WVCDM_DISALLOW_COPY_AND_MOVE(LicenseKeys); + + explicit LicenseKeys(CdmSecurityLevel security_level) : security_level_(security_level) {} virtual ~LicenseKeys() { Clear(); } @@ -83,7 +86,6 @@ class LicenseKeys { void Clear(); - bool is_initialized_; // |key_statuses_| can hold either content key statuses, or entitlement key // statuses. std::map key_statuses_; @@ -93,15 +95,17 @@ class LicenseKeys { std::map content_keyid_to_entitlement_key_id_; CdmSecurityLevel security_level_; - - CORE_DISALLOW_COPY_AND_ASSIGN(LicenseKeys); -}; +}; // class LicenseKeys // Holds the current license status of a key. class LicenseKeyStatus { + public: friend class LicenseKeys; - public: + LicenseKeyStatus() = delete; + WVCDM_DISALLOW_COPY_AND_MOVE(LicenseKeyStatus); + virtual ~LicenseKeyStatus() {} + // Returns true if the key is a content key (not an operator session key) virtual bool IsContentKey() { return is_content_key_; } @@ -145,8 +149,6 @@ class LicenseKeyStatus { LicenseKeyStatus(const KeyContainer& key, CdmSecurityLevel level); - virtual ~LicenseKeyStatus() {} - private: void ParseContentKey(const KeyContainer& key, CdmSecurityLevel level); void ParseOperatorSessionKey(const KeyContainer& key); @@ -164,10 +166,6 @@ class LicenseKeyStatus { CryptoSession::HdcpCapability last_reported_device_hdcp_level_ = HDCP_NONE; CryptoSession::HdcpCapability last_reported_license_hdcp_level_ = HDCP_NONE; ConstraintList constraints_; - - CORE_DISALLOW_COPY_AND_ASSIGN(LicenseKeyStatus); -}; - +}; // class LicenseKeyStatus } // namespace wvcdm - #endif // WVCDM_CORE_LICENSE_KEY_STATUS_H_ diff --git a/core/include/license_protocol_conversions.h b/core/include/license_protocol_conversions.h index 5805b202..bd68f04b 100644 --- a/core/include/license_protocol_conversions.h +++ b/core/include/license_protocol_conversions.h @@ -4,8 +4,6 @@ #ifndef WVCDM_CORE_LICENSE_PROTOCOL_CONVERSIONS_H_ #define WVCDM_CORE_LICENSE_PROTOCOL_CONVERSIONS_H_ -#include - #include "OEMCryptoCENC.h" #include "license_protocol.pb.h" diff --git a/core/include/oemcrypto_adapter.h b/core/include/oemcrypto_adapter.h index a38aa6f7..aec88230 100644 --- a/core/include/oemcrypto_adapter.h +++ b/core/include/oemcrypto_adapter.h @@ -1,10 +1,11 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary // source code may only be used and distributed under the Widevine License // Agreement. -// #ifndef WVCDM_CORE_OEMCRYPTO_ADAPTER_H_ #define WVCDM_CORE_OEMCRYPTO_ADAPTER_H_ +#include + #include "OEMCryptoCENC.h" #include "wv_cdm_types.h" @@ -97,5 +98,4 @@ OEMCryptoResult OEMCrypto_Generic_Verify( OEMCryptoResult OEMCrypto_GetBCCType(RequestedSecurityLevel level, OEMCrypto_BCCType* bcc_type); } // namespace wvcdm - #endif // WVCDM_CORE_OEMCRYPTO_ADAPTER_H_ diff --git a/core/include/okp_fallback_policy.h b/core/include/okp_fallback_policy.h index 37a79de8..a594dedc 100644 --- a/core/include/okp_fallback_policy.h +++ b/core/include/okp_fallback_policy.h @@ -3,15 +3,16 @@ // Agreement. #ifndef WVCDM_CORE_OKP_FALLBACK_POLICY_H_ #define WVCDM_CORE_OKP_FALLBACK_POLICY_H_ + #include #include #include #include "clock.h" -#include "disallow_copy_and_assign.h" #include "file_store.h" #include "okp_info.h" +#include "wv_class_utils.h" namespace wvcdm { class DeviceFiles; @@ -36,6 +37,8 @@ static constexpr int64_t kMaxInitialBackoffDuration = // build, there should only be at most one SystemFallbackPolicy instance. class SystemFallbackPolicy { public: + WVCDM_DISALLOW_COPY_AND_MOVE(SystemFallbackPolicy); + // Creates a new instance of SystemFallbackPolicy. If there exists // OKP information for the device in storage, it will be loaded and // the system policy will resume from its previous state. If no @@ -115,8 +118,6 @@ class SystemFallbackPolicy { // All public methods must lock to protect from simultaneous // engine access. mutable std::mutex mutex_; - - CORE_DISALLOW_COPY_AND_ASSIGN(SystemFallbackPolicy); }; // class SystemFallbackPolicy } // namespace okp } // namespace wvcdm diff --git a/core/include/okp_info.h b/core/include/okp_info.h index add43ea9..9adfee14 100644 --- a/core/include/okp_info.h +++ b/core/include/okp_info.h @@ -6,6 +6,8 @@ #include +#include "wv_class_utils.h" + namespace wvcdm { // OTA Keybox Provisioning (OKP) namespace okp { @@ -23,38 +25,43 @@ const char* SystemStateToString(SystemState state); // Container for all the device information related to OKP. class SystemFallbackInfo { public: - SystemState state() const { return state_; } - void SetState(SystemState state) { state_ = state; } + constexpr SystemFallbackInfo() = default; + WVCDM_CONSTEXPR_DEFAULT_COPY_AND_MOVE(SystemFallbackInfo); - bool HasFirstCheckedTime() const { return first_checked_time_ != 0; } - int64_t first_checked_time() const { return first_checked_time_; } - void SetFirstCheckedTime(int64_t time) { + constexpr SystemState state() const { return state_; } + constexpr void SetState(SystemState state) { state_ = state; } + + constexpr bool HasFirstCheckedTime() const { + return first_checked_time_ != 0; + } + constexpr int64_t first_checked_time() const { return first_checked_time_; } + constexpr void SetFirstCheckedTime(int64_t time) { first_checked_time_ = (time > 0 ? time : 0); } - bool HasBackoffStartTime() const { return backoff_start_time_ > 0; } - int64_t backoff_start_time() const { return backoff_start_time_; } - void SetBackoffStartTime(int64_t time) { + constexpr bool HasBackoffStartTime() const { return backoff_start_time_ > 0; } + constexpr int64_t backoff_start_time() const { return backoff_start_time_; } + constexpr void SetBackoffStartTime(int64_t time) { backoff_start_time_ = (time > 0 ? time : 0); } - void ClearBackoffStartTime() { backoff_start_time_ = 0; } + constexpr void ClearBackoffStartTime() { backoff_start_time_ = 0; } - bool HasBackoffDuration() const { return backoff_duration_ > 0; } - int64_t backoff_duration() const { return backoff_duration_; } - void SetBackoffDuration(int64_t duration) { + constexpr bool HasBackoffDuration() const { return backoff_duration_ > 0; } + constexpr int64_t backoff_duration() const { return backoff_duration_; } + constexpr void SetBackoffDuration(int64_t duration) { backoff_duration_ = (duration > 0 ? duration : 0); } - void DoubleBackoffDuration() { backoff_duration_ *= 2; } - void ClearBackoffDuration() { backoff_duration_ = 0; } + constexpr void DoubleBackoffDuration() { backoff_duration_ *= 2; } + constexpr void ClearBackoffDuration() { backoff_duration_ = 0; } - bool HasProvisioningTime() const { return provisioning_time_ != 0; } - int64_t provisioning_time() const { return provisioning_time_; } - void SetProvisioningTime(int64_t time) { + constexpr bool HasProvisioningTime() const { return provisioning_time_ != 0; } + constexpr int64_t provisioning_time() const { return provisioning_time_; } + constexpr void SetProvisioningTime(int64_t time) { provisioning_time_ = (time > 0 ? time : 0); } - void ClearProvisioningTime() { provisioning_time_ = 0; } + constexpr void ClearProvisioningTime() { provisioning_time_ = 0; } - void Clear() { + constexpr void Clear() { state_ = SystemState::kUnknown; first_checked_time_ = 0; backoff_start_time_ = 0; @@ -62,10 +69,8 @@ class SystemFallbackInfo { provisioning_time_ = 0; } - bool operator==(const SystemFallbackInfo& other) const; - bool operator!=(const SystemFallbackInfo& other) const { - return !(*this == other); - } + bool IsEqualTo(const SystemFallbackInfo& other) const; + WVCDM_DEFINE_EQ_OPERATORS(SystemFallbackInfo); private: SystemState state_ = SystemState::kUnknown; diff --git a/core/include/ota_keybox_provisioner.h b/core/include/ota_keybox_provisioner.h index 0bb41dd5..a624a4db 100644 --- a/core/include/ota_keybox_provisioner.h +++ b/core/include/ota_keybox_provisioner.h @@ -8,9 +8,9 @@ #include #include "client_identification.h" -#include "disallow_copy_and_assign.h" #include "metrics_collections.h" #include "wv_cdm_types.h" +#include "wv_class_utils.h" namespace wvcdm { class CryptoSession; @@ -32,6 +32,8 @@ class OtaKeyboxProvisioner { std::unique_ptr&& crypto_session, okp::SystemFallbackPolicy* fallback_policy); + OtaKeyboxProvisioner() = delete; + WVCDM_DISALLOW_COPY_AND_MOVE(OtaKeyboxProvisioner); ~OtaKeyboxProvisioner(); // Returns true if the underlying SystemFallbackPolicy is @@ -78,8 +80,6 @@ class OtaKeyboxProvisioner { // These flags are for debugging purposes. bool request_generated_ = false; bool response_received_ = false; - - CORE_DISALLOW_COPY_AND_ASSIGN(OtaKeyboxProvisioner); }; // class OtaKeyboxProvisioner } // namespace wvcdm #endif // WVCDM_CORE_OTA_KEYBOX_PROVISIONER_H_ diff --git a/core/include/policy_engine.h b/core/include/policy_engine.h index 506b7e91..d8b87e60 100644 --- a/core/include/policy_engine.h +++ b/core/include/policy_engine.h @@ -1,7 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary // source code may only be used and distributed under the Widevine License // Agreement. - #ifndef WVCDM_CORE_POLICY_ENGINE_H_ #define WVCDM_CORE_POLICY_ENGINE_H_ @@ -10,14 +9,14 @@ #include #include "clock.h" -#include "disallow_copy_and_assign.h" #include "license_key_status.h" #include "license_protocol.pb.h" #include "wv_cdm_types.h" +#include "wv_class_utils.h" namespace wvutil { class Clock; -} +} // namespace wvutil namespace wvcdm { @@ -32,6 +31,9 @@ class WvCdmEventListener; // or no(false) you may not decrypt this data anymore." class PolicyEngine { public: + PolicyEngine() = delete; + WVCDM_DISALLOW_COPY_AND_MOVE(PolicyEngine); + PolicyEngine(CdmSessionId session_id, WvCdmEventListener* event_listener, CryptoSession* crypto_session); virtual ~PolicyEngine(); @@ -204,10 +206,6 @@ class PolicyEngine { std::unique_ptr policy_timers_; std::unique_ptr clock_; - - CORE_DISALLOW_COPY_AND_ASSIGN(PolicyEngine); -}; - +}; // class PolicyEngine } // namespace wvcdm - #endif // WVCDM_CORE_POLICY_ENGINE_H_ diff --git a/core/include/policy_timers.h b/core/include/policy_timers.h index b5bc0f83..65df56b7 100644 --- a/core/include/policy_timers.h +++ b/core/include/policy_timers.h @@ -8,22 +8,22 @@ #include #include -#include "disallow_copy_and_assign.h" #include "license_protocol.pb.h" #include "wv_cdm_constants.h" #include "wv_cdm_types.h" +#include "wv_class_utils.h" namespace wvcdm { - // This is driven by the Policy Engine and maintains timer related // information from the policy such as duration windows and renewals. // Timer expiration behavior has changed with the introduction of core // messages in OEMCrypto v16. Handling of behavior that differs between // a OEMCrypto v16 license with core messages and one without is left to // a class that derives from this interface. - class PolicyTimers { public: + WVCDM_DISALLOW_COPY_AND_MOVE(PolicyTimers); + virtual ~PolicyTimers() {} // SetLicense is used in handling the initial license response. @@ -143,10 +143,6 @@ class PolicyTimers { int64_t current_time, bool ignore_soft_enforce_playback_duration); bool HasRentalOrPlaybackDurationExpired(int64_t current_time); virtual int64_t GetRentalDurationRemaining(int64_t current_time); - - CORE_DISALLOW_COPY_AND_ASSIGN(PolicyTimers); -}; - +}; // class PolicyTimers } // namespace wvcdm - #endif // WVCDM_CORE_POLICY_TIMERS_H_ diff --git a/core/include/policy_timers_v16.h b/core/include/policy_timers_v16.h index 15adc6c7..f35ad444 100644 --- a/core/include/policy_timers_v16.h +++ b/core/include/policy_timers_v16.h @@ -1,17 +1,17 @@ // Copyright 2020 Google LLC. All Rights Reserved. This file and proprietary // source code may only be used and distributed under the Widevine License // Agreement. - #ifndef WVCDM_CORE_POLICY_TIMERS_V16_H_ #define WVCDM_CORE_POLICY_TIMERS_V16_H_ -#include "disallow_copy_and_assign.h" +#include + #include "license_protocol.pb.h" #include "policy_timers.h" #include "wv_cdm_types.h" +#include "wv_class_utils.h" namespace wvcdm { - // OEMCrypto v16 and core messages introduced changes to how duration values // and clocks should be evaluated. This class provides backward compatibility // for licenses that do not include a core message. Durations are handled @@ -21,10 +21,10 @@ namespace wvcdm { // * OEMCrypto has not been upgraded to v16 // * Licenses were persisted before the device was upgraded to v16 // * License service does not yet support core messages - class PolicyTimersV16 : public PolicyTimers { public: - PolicyTimersV16() {} + PolicyTimersV16() = default; + WVCDM_DISALLOW_COPY_AND_MOVE(PolicyTimersV16); ~PolicyTimersV16() override {} @@ -45,11 +45,6 @@ class PolicyTimersV16 : public PolicyTimers { // Renewal related methods bool HasRenewalDelayExpired(int64_t current_time) override; - - private: - CORE_DISALLOW_COPY_AND_ASSIGN(PolicyTimersV16); -}; - +}; // class PolicyTimersV16 } // namespace wvcdm - #endif // WVCDM_CORE_POLICY_TIMERS_V16_H_ diff --git a/core/include/policy_timers_v18.h b/core/include/policy_timers_v18.h index e8048522..6ae66bdd 100644 --- a/core/include/policy_timers_v18.h +++ b/core/include/policy_timers_v18.h @@ -1,27 +1,27 @@ // Copyright 2022 Google LLC. All Rights Reserved. This file and proprietary // source code may only be used and distributed under the Widevine License // Agreement. - #ifndef WVCDM_CORE_POLICY_TIMERS_V18_H_ #define WVCDM_CORE_POLICY_TIMERS_V18_H_ -#include "disallow_copy_and_assign.h" +#include + #include "license_protocol.pb.h" #include "policy_timers.h" #include "wv_cdm_types.h" +#include "wv_class_utils.h" namespace wvcdm { - // OEMCrypto v18 includes support for renewing licenses on load by using // |initial_renewal_delay_base| and TimerDelayBase. // // Backward compatibility may be needed if // * OEMCrypto has not been upgraded to v18 // * Licenses were persisted before the device was upgraded to v18 - class PolicyTimersV18 : public PolicyTimers { public: - PolicyTimersV18() {} + PolicyTimersV18() = default; + WVCDM_DISALLOW_COPY_AND_MOVE(PolicyTimersV18); ~PolicyTimersV18() override {} @@ -48,10 +48,6 @@ class PolicyTimersV18 : public PolicyTimers { bool license_renewal_ = false; bool renew_on_first_decrypt_ = false; bool can_renew_ = false; - - CORE_DISALLOW_COPY_AND_ASSIGN(PolicyTimersV18); -}; - +}; // class PolicyTimersV18 } // namespace wvcdm - #endif // WVCDM_CORE_POLICY_TIMERS_V18_H_ diff --git a/core/include/privacy_crypto.h b/core/include/privacy_crypto.h index 2b11db0b..352d8105 100644 --- a/core/include/privacy_crypto.h +++ b/core/include/privacy_crypto.h @@ -26,14 +26,15 @@ #include -#include "disallow_copy_and_assign.h" #include "wv_cdm_types.h" +#include "wv_class_utils.h" namespace wvcdm { class AesCbcKey { public: AesCbcKey(); + WVCDM_DISALLOW_COPY_AND_MOVE(AesCbcKey); ~AesCbcKey(); bool Init(const std::string& key); @@ -44,13 +45,12 @@ class AesCbcKey { private: std::string key_; - - CORE_DISALLOW_COPY_AND_ASSIGN(AesCbcKey); -}; +}; // class AesCbcKey class RsaPublicKey { public: RsaPublicKey(); + WVCDM_DISALLOW_COPY_AND_MOVE(RsaPublicKey); ~RsaPublicKey(); // Initializes an RsaPublicKey object using a DER encoded PKCS#1 RSAPublicKey @@ -67,9 +67,7 @@ class RsaPublicKey { private: std::string serialized_key_; - - CORE_DISALLOW_COPY_AND_ASSIGN(RsaPublicKey); -}; +}; // class RsaPublicKey /** * Extracts an integer value from the extensions in a certificate. @@ -88,7 +86,5 @@ std::string Md5Hash(const std::string& data); std::string Sha1Hash(const std::string& data); std::string Sha256Hash(const std::string& data); std::string Sha512Hash(const std::string& data); - } // namespace wvcdm - #endif // WVCDM_CORE_PRIVACY_CRYPTO_H_ diff --git a/core/include/properties.h b/core/include/properties.h index f411f4d3..bda142da 100644 --- a/core/include/properties.h +++ b/core/include/properties.h @@ -11,8 +11,8 @@ #include #include "cdm_client_property_set.h" -#include "disallow_copy_and_assign.h" #include "wv_cdm_types.h" +#include "wv_class_utils.h" #if defined(UNIT_TEST) #include @@ -30,6 +30,9 @@ using CdmClientPropertySetMap = std::map; // Setter methods are provided but their only planned use is for testing. class Properties { public: + Properties() = delete; + WVCDM_DISALLOW_COPY_AND_MOVE(Properties); + static void Init() { std::unique_lock lock(init_mutex_); @@ -156,10 +159,6 @@ class Properties { static bool allow_restore_of_offline_licenses_with_release_; static bool delay_oem_crypto_termination_; static std::unique_ptr session_property_set_; - - CORE_DISALLOW_COPY_AND_ASSIGN(Properties); -}; - +}; // class Properties } // namespace wvcdm - #endif // WVCDM_CORE_PROPERTIES_H_ diff --git a/core/include/service_certificate.h b/core/include/service_certificate.h index ced4f4c1..d9313822 100644 --- a/core/include/service_certificate.h +++ b/core/include/service_certificate.h @@ -1,10 +1,21 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary // source code may only be used and distributed under the Widevine License // Agreement. -// #ifndef WVCDM_CORE_SERVICE_CERTIFICATE_H_ #define WVCDM_CORE_SERVICE_CERTIFICATE_H_ +#include +#include + +#include "license_protocol.pb.h" +#include "privacy_crypto.h" +#include "wv_cdm_types.h" +#include "wv_class_utils.h" + +namespace wvcdm { + +class CryptoSession; + // Service Certificates are used to encrypt the ClientIdentification message // that is part of Device Provisioning, License, Renewal, and Release requests. // It also supplies a provider_id setting used in device provisioning. @@ -12,21 +23,10 @@ // is not supplied and privacy mode is enabled, the CDM will send a Service // Certificate Request to the target server to get one. Once the Service // Certificate is established for the session, it should not change. - -#include - -#include "disallow_copy_and_assign.h" -#include "license_protocol.pb.h" -#include "privacy_crypto.h" -#include "wv_cdm_types.h" - -namespace wvcdm { - -class CryptoSession; - class ServiceCertificate { public: ServiceCertificate() : has_certificate_(false) {} + WVCDM_DISALLOW_COPY_AND_MOVE(ServiceCertificate); virtual ~ServiceCertificate() {} // Set up a new service certificate. @@ -79,10 +79,6 @@ class ServiceCertificate { // Public key. std::unique_ptr public_key_; - - CORE_DISALLOW_COPY_AND_ASSIGN(ServiceCertificate); -}; - +}; // class ServiceCertificate } // namespace wvcdm - #endif // WVCDM_CORE_SERVICE_CERTIFICATE_H_ diff --git a/core/include/system_id_extractor.h b/core/include/system_id_extractor.h index 55019385..1319ad21 100644 --- a/core/include/system_id_extractor.h +++ b/core/include/system_id_extractor.h @@ -4,9 +4,10 @@ #ifndef WVCDM_CORE_SYSTEM_ID_EXTRACTOR_H_ #define WVCDM_CORE_SYSTEM_ID_EXTRACTOR_H_ -#include +#include #include "wv_cdm_types.h" +#include "wv_class_utils.h" namespace wvutil { class FileSystem; @@ -21,6 +22,8 @@ class DeviceFiles; // different place. class SystemIdExtractor { public: + SystemIdExtractor() = delete; + WVCDM_DISALLOW_COPY_AND_MOVE(SystemIdExtractor); // The constructor should be provided all the parameters necessary // to find the system ID. Although certain provisioning methods // may not use all parameters, this class must behave in a way which @@ -31,7 +34,7 @@ class SystemIdExtractor { // |security_level| // - Requested security level, uses the |crypto_session| handle // to convert to a concrete security level. - // |crypto_sesssion| + // |crypto_session| // - Handle into the OEMCrypto platform. If handle is open, // then the session's real security level should match // |security_level|. @@ -41,12 +44,6 @@ class SystemIdExtractor { CryptoSession* crypto_session, wvutil::FileSystem* fs); virtual ~SystemIdExtractor() {} - // Disallow copy and move. - SystemIdExtractor(const SystemIdExtractor&) = delete; - SystemIdExtractor(SystemIdExtractor&&) = delete; - SystemIdExtractor& operator=(const SystemIdExtractor&) = delete; - SystemIdExtractor& operator=(SystemIdExtractor&&) = delete; - // Extracts the system ID from the appropriate source. virtual bool ExtractSystemId(uint32_t* system_id); @@ -102,6 +99,6 @@ class SystemIdExtractor { // Test only handle to DeviceFiles. When not null, |fs_| will be // ignored. DeviceFiles* test_device_files_ = nullptr; -}; +}; // class SystemIdExtractor } // namespace wvcdm #endif // WVCDM_CORE_SYSTEM_ID_EXTRACTOR_H_ diff --git a/core/include/wv_cdm_constants.h b/core/include/wv_cdm_constants.h index c35236aa..7bd5cb6a 100644 --- a/core/include/wv_cdm_constants.h +++ b/core/include/wv_cdm_constants.h @@ -1,10 +1,11 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary // source code may only be used and distributed under the Widevine License // Agreement. - #ifndef WVCDM_CORE_WV_CDM_CONSTANTS_H_ #define WVCDM_CORE_WV_CDM_CONSTANTS_H_ +#include + #include #include @@ -187,5 +188,4 @@ constexpr uint32_t HDCP_UNSPECIFIED_VIDEO_RESOLUTION = 0; constexpr int64_t HDCP_DEVICE_CHECK_INTERVAL = 10; constexpr char EMPTY_APP_PACKAGE_NAME[] = ""; } // namespace wvcdm - #endif // WVCDM_CORE_WV_CDM_CONSTANTS_H_ diff --git a/core/include/wv_cdm_event_listener.h b/core/include/wv_cdm_event_listener.h index 1c030ff6..c7fe874f 100644 --- a/core/include/wv_cdm_event_listener.h +++ b/core/include/wv_cdm_event_listener.h @@ -1,19 +1,21 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary // source code may only be used and distributed under the Widevine License // Agreement. - #ifndef WVCDM_CORE_WV_CDM_EVENT_LISTENER_H_ #define WVCDM_CORE_WV_CDM_EVENT_LISTENER_H_ -#include "disallow_copy_and_assign.h" +#include + #include "wv_cdm_types.h" +#include "wv_class_utils.h" namespace wvcdm { - // Listener for events from the Content Decryption Module. class WvCdmEventListener { public: - WvCdmEventListener() {} + WvCdmEventListener() = default; + WVCDM_DISALLOW_COPY_AND_MOVE(WvCdmEventListener); + virtual ~WvCdmEventListener() {} virtual void OnSessionRenewalNeeded(const CdmSessionId& session_id) = 0; @@ -24,11 +26,6 @@ class WvCdmEventListener { // license. virtual void OnExpirationUpdate(const CdmSessionId& session_id, int64_t new_expiry_time_seconds) = 0; - - private: - CORE_DISALLOW_COPY_AND_ASSIGN(WvCdmEventListener); -}; - +}; // class WvCdmEventListener } // namespace wvcdm - #endif // WVCDM_CORE_WV_CDM_EVENT_LISTENER_H_ diff --git a/core/include/wv_cdm_types.h b/core/include/wv_cdm_types.h index 5a5a12db..69cd21dc 100644 --- a/core/include/wv_cdm_types.h +++ b/core/include/wv_cdm_types.h @@ -1,11 +1,10 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary // source code may only be used and distributed under the Widevine License // Agreement. - #ifndef WVCDM_CORE_WV_CDM_TYPES_H_ #define WVCDM_CORE_WV_CDM_TYPES_H_ -#include +#include #include #include @@ -13,9 +12,9 @@ #include #include "OEMCryptoCENC.h" +#include "wv_class_utils.h" namespace wvcdm { - using CdmKeySystem = std::string; using CdmInitData = std::string; using CdmKeyMessage = std::string; @@ -467,11 +466,12 @@ enum CdmResponseEnum : int32_t { // Don't forget to add new values to // * core/src/wv_cdm_types.cpp // * android/include/mapErrors-inl.h -}; +}; // enum CdmResponseEnum class CdmResponseType { public: - constexpr CdmResponseType() {} + constexpr CdmResponseType() = default; + WVCDM_CONSTEXPR_DEFAULT_COPY_AND_MOVE(CdmResponseType); constexpr explicit CdmResponseType(CdmResponseEnum code) : code_(code) {} constexpr CdmResponseType(CdmResponseEnum code, OEMCryptoResult oemc_result, const char* crypto_session_method) @@ -504,12 +504,12 @@ class CdmResponseType { constexpr int ToInt() const { return static_cast(code_); } constexpr explicit operator int() const { return ToInt(); }; - bool operator==(CdmResponseEnum other) const { return code_ == other; } - bool operator!=(CdmResponseEnum other) const { return code_ != other; } - bool operator==(const CdmResponseType& other) const; - bool operator!=(const CdmResponseType& other) const { - return !(*this == other); + constexpr bool IsEqualTo(const CdmResponseEnum& other) const { + return code_ == other; } + WVCDM_DEFINE_CONSTEXPR_EQ_OPERATORS(CdmResponseEnum); + bool IsEqualTo(const CdmResponseType& other) const; + WVCDM_DEFINE_EQ_OPERATORS(CdmResponseType); std::string ToString() const; @@ -522,13 +522,15 @@ class CdmResponseType { bool has_oemc_result_ = false; OEMCryptoResult oemc_result_ = OEMCrypto_SUCCESS; const char* crypto_session_method_ = nullptr; -}; +}; // class CdmResponseType -inline bool operator==(const CdmResponseEnum lhs, const CdmResponseType& rhs) { +constexpr inline bool operator==(const CdmResponseEnum lhs, + const CdmResponseType& rhs) { return lhs == rhs.code(); } -inline bool operator!=(const CdmResponseEnum lhs, const CdmResponseType& rhs) { +constexpr inline bool operator!=(const CdmResponseEnum lhs, + const CdmResponseType& rhs) { return lhs != rhs.code(); } @@ -636,7 +638,7 @@ struct CdmUsageEntryInfo { std::string usage_info_file_name; int64_t last_use_time; int64_t offline_license_expiry_time; // Only for offline licenses. - bool operator==(const CdmUsageEntryInfo& other) const { + bool IsEqualTo(const CdmUsageEntryInfo& other) const { if (this == &other) { return true; } @@ -654,6 +656,7 @@ struct CdmUsageEntryInfo { // else storage_type == kStorageTypeUnknown return true; } + WVCDM_DEFINE_EQ_OPERATORS(CdmUsageEntryInfo); void Clear() { storage_type = kStorageTypeUnknown; @@ -662,7 +665,7 @@ struct CdmUsageEntryInfo { last_use_time = 0; offline_license_expiry_time = 0; } -}; +}; // struct CdmUsageEntryInfo enum CdmKeySecurityLevel : int32_t { kKeySecurityLevelUnset, @@ -696,6 +699,7 @@ enum CdmProductionReadiness : int32_t { class CdmKeyAllowedUsage { public: CdmKeyAllowedUsage() { Clear(); } + WVCDM_DEFAULT_COPY_AND_MOVE(CdmKeyAllowedUsage); bool Valid() const { return valid_; } void SetValid() { valid_ = true; } @@ -711,7 +715,7 @@ class CdmKeyAllowedUsage { valid_ = false; } - bool Equals(const CdmKeyAllowedUsage& other) { + bool IsEqualTo(const CdmKeyAllowedUsage& other) const { return valid_ && other.Valid() && decrypt_to_clear_buffer == other.decrypt_to_clear_buffer && decrypt_to_secure_buffer == other.decrypt_to_secure_buffer && @@ -721,6 +725,7 @@ class CdmKeyAllowedUsage { generic_verify == other.generic_verify && key_security_level_ == other.key_security_level_; } + WVCDM_DEFINE_EQ_OPERATORS(CdmKeyAllowedUsage); bool decrypt_to_clear_buffer; bool decrypt_to_secure_buffer; @@ -732,7 +737,7 @@ class CdmKeyAllowedUsage { private: bool valid_; -}; +}; // class CdmKeyAllowedUsage // For schemes that do not use pattern encryption (cenc), encrypt and skip // must be set to 0. For those that do (cbcs), it is recommended that @@ -935,6 +940,7 @@ const char* RequestedSecurityLevelToString( RequestedSecurityLevel security_level); const char* CdmWatermarkingSupportToString(CdmWatermarkingSupport support); const char* CdmProductionReadinessToString(CdmProductionReadiness readiness); +const char* CdmCipherModeToString(CdmCipherMode cipher_mode); // Converts a generic, unknown enum value to a string representation // containing its numeric value. // The pointer returned from this function is thread_local. @@ -953,5 +959,4 @@ const char* BoolToString(bool value); // Logging utilities for OEMCrypto types. const char* OemCryptoResultToString(OEMCryptoResult result); } // namespace wvcdm - #endif // WVCDM_CORE_WV_CDM_TYPES_H_ diff --git a/core/src/cdm_engine.cpp b/core/src/cdm_engine.cpp index d0e26d61..7d059de4 100644 --- a/core/src/cdm_engine.cpp +++ b/core/src/cdm_engine.cpp @@ -1031,7 +1031,7 @@ CdmResponseType CdmEngine::QueryKeyAllowedUsage(const std::string& key_id, if (sts == NO_ERROR) { if (found) { // Found another key. If usage settings do not match, fail. - if (!key_usage->Equals(found_in_this_session)) { + if (*key_usage != found_in_this_session) { key_usage->Clear(); return CdmResponseType(KEY_CONFLICT_1); } diff --git a/core/src/cdm_session.cpp b/core/src/cdm_session.cpp index febb5ab9..73f826eb 100644 --- a/core/src/cdm_session.cpp +++ b/core/src/cdm_session.cpp @@ -574,7 +574,8 @@ CdmResponseType CdmSession::AddKeyInternal(const CdmKeyResponse& key_response) { license_parser_->HandleKeyResponse(/* is restore */ false, key_response); // Update the license sdk and service versions. - const VersionInfo& version_info = license_parser_->GetServiceVersion(); + const video_widevine::VersionInfo& version_info = + license_parser_->GetServiceVersion(); metrics_->license_sdk_version_.Record(version_info.license_sdk_version()); metrics_->license_service_version_.Record( version_info.license_service_version()); diff --git a/core/src/certificate_provisioning.cpp b/core/src/certificate_provisioning.cpp index b40c3825..bb0faedc 100644 --- a/core/src/certificate_provisioning.cpp +++ b/core/src/certificate_provisioning.cpp @@ -14,6 +14,7 @@ #include "properties.h" #include "service_certificate.h" #include "string_conversions.h" +#include "system_id_extractor.h" #include "wv_cdm_constants.h" namespace wvcdm { @@ -371,8 +372,8 @@ CdmResponseType CertificateProvisioning::GetProvisioning40RequestInternal( // First-stage provisioning always uses the WV production service cert for // encryption. ServiceCertificate wv_service_cert; - status = wv_service_cert.Init( - wvutil::a2bs_hex(kCpProductionServiceCertificate)); + status = + wv_service_cert.Init(wvutil::a2bs_hex(kCpProductionServiceCertificate)); if (status != NO_ERROR) return status; // Since |stored_oem_cert| is empty, the client identification token will be @@ -614,6 +615,20 @@ CdmResponseType CertificateProvisioning::HandleProvisioning40Response( LOGE("Failed to store provisioning 4 OEM certificate"); return CdmResponseType(PROVISIONING_4_FAILED_TO_STORE_OEM_CERTIFICATE); } + if (provisioning_response.has_device_state() && + provisioning_response.device_state() == + ProvisioningResponse::DEVICE_STATE_TEST_ONLY) { + uint32_t system_id = 0; + if (SystemIdExtractor::ExtractSystemIdFromOemCert(device_certificate, + &system_id)) { + LOGW( + "Device has a TEST_ONLY system id: %u. Production content may not " + "be played", + system_id); + } else { + LOGW("Failed to extract system id from OEM certificate"); + } + } } else { // The response is assumed to be an DRM cert. DeviceFiles per_origin_file_handle(file_system); @@ -628,7 +643,6 @@ CdmResponseType CertificateProvisioning::HandleProvisioning40Response( return CdmResponseType(PROVISIONING_4_FAILED_TO_STORE_DRM_CERTIFICATE); } } - return CdmResponseType(NO_ERROR); } diff --git a/core/src/crypto_session.cpp b/core/src/crypto_session.cpp index f0dcce4a..a8ad13ce 100644 --- a/core/src/crypto_session.cpp +++ b/core/src/crypto_session.cpp @@ -151,7 +151,6 @@ void AdvanceDestBuffer(OEMCrypto_DestBufferDesc* dest_buffer, size_t bytes) { switch (dest_buffer->type) { case OEMCrypto_BufferType_Clear: dest_buffer->buffer.clear.clear_buffer += bytes; - dest_buffer->buffer.clear.clear_buffer_length -= bytes; return; case OEMCrypto_BufferType_Secure: @@ -2535,6 +2534,17 @@ bool CryptoSession::GetBuildInformation(RequestedSecurityLevel security_level, return false; } info->resize(info_length); + // Some OEMCrypto implementations may include trailing null + // bytes in the output. Trim them here. + while (!info->empty() && info->back() == '\0') { + info->pop_back(); + } + if (info->empty()) { + LOGE("BuildInformation() returned corrupted data: length = %zu", + info_length); + return false; + } + return true; } @@ -3253,6 +3263,11 @@ OEMCryptoResult CryptoSession::DecryptSample( } fake_sample.buffers.input_data_length = length; + if (fake_sample.buffers.output_descriptor.type == + OEMCrypto_BufferType_Clear) { + fake_sample.buffers.output_descriptor.buffer.clear + .clear_buffer_length = length; + } fake_sample.subsamples = &clear_subsample; fake_sample.subsamples_length = 1; @@ -3280,6 +3295,11 @@ OEMCryptoResult CryptoSession::DecryptSample( } fake_sample.buffers.input_data_length = length; + if (fake_sample.buffers.output_descriptor.type == + OEMCrypto_BufferType_Clear) { + fake_sample.buffers.output_descriptor.buffer.clear + .clear_buffer_length = length; + } fake_sample.subsamples = &encrypted_subsample; fake_sample.subsamples_length = 1; @@ -3372,6 +3392,10 @@ OEMCryptoResult CryptoSession::LegacyCopyBufferInChunks( // Calculate the size of the next chunk. const size_t chunk_size = std::min(remaining_input_data, max_chunk_size); + if (output_descriptor.type == OEMCrypto_BufferType_Clear) { + output_descriptor.buffer.clear.clear_buffer_length = chunk_size; + } + // Re-add "last subsample" flag if this is the last subsample. if (chunk_size == remaining_input_data) { subsample_flags |= OEMCrypto_LastSubsample; @@ -3419,6 +3443,11 @@ OEMCryptoResult CryptoSession::LegacyDecryptInChunks( // Calculate the size of the next chunk. const size_t chunk_size = std::min(remaining_input_data, max_chunk_size); fake_sample.buffers.input_data_length = chunk_size; + if (fake_sample.buffers.output_descriptor.type == + OEMCrypto_BufferType_Clear) { + fake_sample.buffers.output_descriptor.buffer.clear.clear_buffer_length = + chunk_size; + } if (is_protected) { fake_subsample.num_bytes_encrypted = chunk_size; } else { diff --git a/core/src/license.cpp b/core/src/license.cpp index 8e0feaa7..42a9da8e 100644 --- a/core/src/license.cpp +++ b/core/src/license.cpp @@ -38,17 +38,14 @@ constexpr size_t kLicenseMacKeySize = wvcdm::MAC_KEY_SIZE * 2; namespace wvcdm { // Protobuf generated classes. -using video_widevine::EncryptedClientIdentification; +using ContentIdentification = + video_widevine::LicenseRequest::ContentIdentification; using video_widevine::HashAlgorithmProto; using video_widevine::License; -using video_widevine::License_KeyContainer; using video_widevine::LicenseError; using video_widevine::LicenseIdentification; using video_widevine::LicenseRequest; -using video_widevine::LicenseRequest_ContentIdentification; -using video_widevine::LicenseRequest_ContentIdentification_ExistingLicense; -using video_widevine::LicenseRequest_ContentIdentification_WebmKeyId; -using video_widevine::LicenseRequest_ContentIdentification_WidevinePsshData; +using KeyContainer = video_widevine::License::KeyContainer; using video_widevine::SignedMessage; namespace { @@ -58,7 +55,7 @@ std::vector ExtractEntitlementKeys(const License& license) { for (int i = 0; i < license.key_size(); ++i) { CryptoKey key; switch (license.key(i).type()) { - case License_KeyContainer::ENTITLEMENT: { + case KeyContainer::ENTITLEMENT: { // We always take the first ENTITLEMENT_KEY_SIZE bytes and ignore the // rest in order to ignore any padding. // @@ -121,8 +118,8 @@ std::vector ExtractContentKeys( for (int i = 0; i < license.key_size(); ++i) { CryptoKey key; switch (license.key(i).type()) { - case License_KeyContainer::CONTENT: - case License_KeyContainer::OPERATOR_SESSION: { + case KeyContainer::CONTENT: + case KeyContainer::OPERATOR_SESSION: { key.set_key_id(license.key(i).id()); // KeyContainers have a fixed 16 bytes of padding in License Protocol // 2.1. Note that OPERATION_SESSION keys may be CONTENT_KEY_SIZE or @@ -174,7 +171,7 @@ std::vector ExtractContentKeys( key_array.push_back(key); break; } - case License_KeyContainer::KEY_CONTROL: + case KeyContainer::KEY_CONTROL: if (license.key(i).has_key_control()) { key.set_key_control(license.key(i).key_control().key_control_block()); if (license.key(i).key_control().has_iv()) { @@ -195,24 +192,10 @@ std::vector ExtractContentKeys( } // namespace CdmLicense::CdmLicense(const CdmSessionId& session_id) - : session_id_(session_id), - initialized_(false), - protocol_version_(video_widevine::VERSION_2_2), - renew_with_client_id_(false), - is_offline_(false), - use_privacy_mode_(false), - clock_(new wvutil::Clock()), - license_key_type_(kLicenseKeyTypeContent) {} + : session_id_(session_id), clock_(new wvutil::Clock()) {} CdmLicense::CdmLicense(const CdmSessionId& session_id, wvutil::Clock* clock) - : session_id_(session_id), - initialized_(false), - protocol_version_(video_widevine::VERSION_2_2), - renew_with_client_id_(false), - is_offline_(false), - use_privacy_mode_(false), - clock_(clock), - license_key_type_(kLicenseKeyTypeContent) { + : session_id_(session_id), clock_(clock) { if (!clock_) { LOGW("Input |clock| is null, using default"); clock_.reset(new wvutil::Clock()); @@ -246,7 +229,7 @@ bool CdmLicense::Init(bool use_privacy_mode, return false; } - uint32_t api_version; + uint32_t api_version = 16; if (!session->GetApiVersion(&api_version)) { api_version = 16; } @@ -281,7 +264,9 @@ CdmResponseType CdmLicense::PrepareKeyRequest( return PrepareKeyRequest(restored_init_data, client_token, license_type, app_parameters, signed_request, server_url); } - wrapped_keys_ = init_data.ExtractWrappedKeys(); + // Store PSSH provided entitled keys so they can be loaded when + // entitled license is received. + request_entitled_keys_ = init_data.ExtractWrappedKeys(); if (!init_data.is_supported()) { LOGE("Unsupported init data type: type = %s", init_data.type().c_str()); return CdmResponseType(INVALID_PARAMETERS_LIC_3); @@ -298,17 +283,17 @@ CdmResponseType CdmLicense::PrepareKeyRequest( LOGE("Output parameter |server_url| not provided"); return CdmResponseType(INVALID_PARAMETERS_LIC_7); } + signed_request->clear(); + server_url->clear(); // Never assign for license requests. // If privacy mode and no service certificate, depending on platform - // configuration, request service certificate or declare error + // configuration, request service certificate or declare error. if (use_privacy_mode_ && !service_certificate_.has_certificate()) { if (!Properties::allow_service_certificate_requests()) { LOGE("Privacy mode failure: No service certificate"); return CdmResponseType(PRIVACY_MODE_ERROR_1); } - stored_init_data_.reset(new InitializationData(init_data)); - if (!ServiceCertificate::GetRequest(signed_request)) { LOGE("Failed to prepare service certificated request"); return CdmResponseType( @@ -322,7 +307,9 @@ CdmResponseType CdmLicense::PrepareKeyRequest( LicenseRequest license_request; CdmResponseType status; - status = PrepareClientId(app_parameters, kEmptyString, &license_request); + status = PrepareClientId(app_parameters, + /* provider_client_token = */ kEmptyString, + &license_request); if (NO_ERROR != status) return status; status = @@ -330,7 +317,6 @@ CdmResponseType CdmLicense::PrepareKeyRequest( if (NO_ERROR != status) return status; license_request.set_type(LicenseRequest::NEW); - license_request.set_request_time(clock_->GetCurrentTime()); // Get/set the nonce. This value will be reflected in the Key Control Block @@ -353,20 +339,19 @@ CdmResponseType CdmLicense::PrepareKeyRequest( std::string serialized_license_req; license_request.SerializeToString(&serialized_license_req); - key_request_ = serialized_license_req; + license_request_ = serialized_license_req; // Derive signing and encryption keys and construct core message and // signature. std::string core_message; std::string license_request_signature; - bool should_specify_algorithm; + bool should_specify_algorithm = false; OEMCrypto_SignatureHashAlgorithm oec_algorithm = OEMCrypto_SHA1; status = crypto_session_->PrepareAndSignLicenseRequest( serialized_license_req, &core_message, &license_request_signature, should_specify_algorithm, oec_algorithm); if (status != NO_ERROR) { - signed_request->clear(); return status; } @@ -393,11 +378,10 @@ CdmResponseType CdmLicense::PrepareKeyRequest( signed_message.SerializeToString(signed_request); - *server_url = server_url_; return CdmResponseType(KEY_MESSAGE); } -// TODO(b/166007195): Remove this. +// TODO(b/166007195): Remove this when v16 support is dropped. CdmResponseType CdmLicense::PrepareKeyUpdateReload(CdmSession* cdm_session) { uint32_t api_version = 0; if (!crypto_session_->GetApiVersion(&api_version)) { @@ -452,6 +436,7 @@ CdmResponseType CdmLicense::PrepareKeyUpdateRequest( license_request.set_type(LicenseRequest::RELEASE); license_request.set_request_time(clock_->GetCurrentTime()); + license_request.set_protocol_version(protocol_version_); if (renew_with_client_id_) { CdmResponseType status = PrepareClientId( @@ -459,29 +444,30 @@ CdmResponseType CdmLicense::PrepareKeyUpdateRequest( if (NO_ERROR != status) return status; } - LicenseRequest_ContentIdentification_ExistingLicense* current_license = + ContentIdentification::ExistingLicense* current_license = license_request.mutable_content_id()->mutable_existing_license(); - LicenseIdentification license_id = policy_engine_->license_id(); + const LicenseIdentification& license_id = policy_engine_->license_id(); current_license->mutable_license_id()->CopyFrom(license_id); - int64_t seconds_since_started, seconds_since_last_played; + int64_t seconds_since_started = 0; + int64_t seconds_since_last_played = 0; CryptoSession::UsageDurationStatus usage_duration_status = CryptoSession::kUsageDurationsInvalid; if (!provider_session_token_.empty()) { if (!is_renewal) { - CdmResponseType status = + const CdmResponseType status = crypto_session_->DeactivateUsageInformation(provider_session_token_); if (NO_ERROR != status) return status; } // TODO(rfrias): Refactor to avoid needing to call CdmSession - if (cdm_session && cdm_session->SupportsUsageTable()) { + if (cdm_session != nullptr && cdm_session->SupportsUsageTable()) { const CdmResponseType status = cdm_session->UpdateUsageEntryInformation(); if (NO_ERROR != status) return status; } std::string usage_report; - CdmResponseType status = crypto_session_->GenerateUsageReport( + const CdmResponseType status = crypto_session_->GenerateUsageReport( provider_session_token_, &usage_report, &usage_duration_status, &seconds_since_started, &seconds_since_last_played); if (!is_renewal) { @@ -503,20 +489,18 @@ CdmResponseType CdmLicense::PrepareKeyUpdateRequest( current_license->set_seconds_since_last_played(seconds_since_last_played); } - license_request.set_protocol_version(protocol_version_); - // License request is complete. Serialize it. std::string serialized_license_req; license_request.SerializeToString(&serialized_license_req); // Construct signature and core message. std::string core_message; - std::string license_request_signature; + std::string signature; const CdmResponseType status = crypto_session_->PrepareAndSignRenewalRequest( - serialized_license_req, &core_message, &license_request_signature); + serialized_license_req, &core_message, &signature); if (status != NO_ERROR) return status; - if (license_request_signature.empty()) { + if (signature.empty()) { LOGE("License request signature is empty"); return CdmResponseType(EMPTY_LICENSE_RENEWAL); } @@ -524,12 +508,12 @@ CdmResponseType CdmLicense::PrepareKeyUpdateRequest( // Put serialize license request and signature together SignedMessage signed_message; signed_message.set_type(SignedMessage::LICENSE_REQUEST); - signed_message.set_signature(license_request_signature); + signed_message.set_signature(signature); signed_message.set_msg(serialized_license_req); signed_message.set_oemcrypto_core_message(core_message); signed_message.SerializeToString(signed_request); - *server_url = server_url_; + *server_url = renewal_server_url_; return CdmResponseType(KEY_MESSAGE); } @@ -566,8 +550,9 @@ CdmResponseType CdmLicense::HandleKeyResponse( return (status == NO_ERROR) ? CdmResponseType(NEED_KEY) : status; } - if (signed_response.type() == SignedMessage::ERROR_RESPONSE) + if (signed_response.type() == SignedMessage::ERROR_RESPONSE) { return HandleKeyErrorResponse(signed_response); + } if (signed_response.type() != SignedMessage::LICENSE) { LOGE("Unrecognized signed message type: type = %d", @@ -599,7 +584,7 @@ CdmResponseType CdmLicense::HandleKeyResponse( std::string mac_key_iv; std::string mac_keys; for (int i = 0; i < license.key_size(); ++i) { - if (license.key(i).type() == License_KeyContainer::SIGNING) { + if (license.key(i).type() == KeyContainer::SIGNING) { mac_key_iv.assign(license.key(i).iv()); // Strip off PKCS#5 padding @@ -626,33 +611,35 @@ CdmResponseType CdmLicense::HandleKeyResponse( // overall type of the license, we check for the existence of either // type of keys. If both are present, we default to entitlement keys. CdmLicenseKeyType key_type = kLicenseKeyTypeEntitlement; - std::vector key_array = ExtractEntitlementKeys(license); - if (key_array.empty()) { - key_array = ExtractContentKeys(license, protocol_version_); + std::vector license_keys = ExtractEntitlementKeys(license); + if (license_keys.empty()) { + license_keys = ExtractContentKeys(license, protocol_version_); key_type = kLicenseKeyTypeContent; } - if (key_array.empty()) { + if (license_keys.empty()) { LOGE("No content keys"); return CdmResponseType(NO_CONTENT_KEY); } license_key_type_ = key_type; - if (license.has_provider_client_token()) + if (license.has_provider_client_token()) { provider_client_token_ = license.provider_client_token(); + } if (license.id().type() == video_widevine::OFFLINE && - license.policy().can_persist()) + license.policy().can_persist()) { is_offline_ = true; + } - if (license.id().has_provider_session_token()) + if (license.id().has_provider_session_token()) { provider_session_token_ = license.id().provider_session_token(); + } - LOGV("provider_session_token = %s", provider_session_token_.empty() - ? "N/A" - : provider_session_token_.c_str()); + LOGV("provider_session_token = %s", + wvutil::SafeByteIdToString(provider_session_token_).c_str()); if (license.policy().has_renewal_server_url()) { - server_url_ = license.policy().renewal_server_url(); + renewal_server_url_ = license.policy().renewal_server_url(); } if (license.policy().has_always_include_client_id()) { @@ -660,7 +647,7 @@ CdmResponseType CdmLicense::HandleKeyResponse( } // If the field is not set, it will default to false. - CdmResponseType status = + const CdmResponseType status = crypto_session_->UseSecondaryKey(signed_response.using_secondary_key()); if (status != NO_ERROR) return status; @@ -668,11 +655,11 @@ CdmResponseType CdmLicense::HandleKeyResponse( if (kLicenseKeyTypeEntitlement == key_type) { resp = HandleEntitlementKeyResponse( is_restore, signed_response.session_key(), signed_message, core_message, - signature, key_array, license); + signature, license_keys, license); } else if (kLicenseKeyTypeContent == key_type) { resp = HandleContentKeyResponse(is_restore, signed_response.session_key(), signed_message, core_message, signature, - key_array, license); + license_keys, license); } return resp; } @@ -743,7 +730,7 @@ CdmResponseType CdmLicense::HandleKeyUpdateResponse( if (license.policy().has_renewal_server_url() && license.policy().renewal_server_url().size() > 0) { - server_url_ = license.policy().renewal_server_url(); + renewal_server_url_ = license.policy().renewal_server_url(); } // If the field is not set, it will default to false. @@ -797,42 +784,46 @@ CdmResponseType CdmLicense::RestoreOfflineLicense( return CdmResponseType(INVALID_LICENSE_REQUEST_TYPE_1); } - key_request_ = signed_request.msg(); + license_request_ = signed_request.msg(); LicenseRequest original_license_request; - if (!original_license_request.ParseFromString(key_request_)) { + if (!original_license_request.ParseFromString(license_request_)) { LOGW("Could not parse original request."); } else { license_nonce_ = original_license_request.key_control_nonce(); protocol_version_ = original_license_request.protocol_version(); } - CdmResponseType sts = HandleKeyResponse(true, license_response); + CdmResponseType status = + HandleKeyResponse(/* is_restore = */ true, license_response); - if (sts != KEY_ADDED) return sts; + if (status != KEY_ADDED) return status; if (!license_renewal_response.empty()) { - sts = PrepareKeyUpdateReload(cdm_session); - if (sts != KEY_MESSAGE && sts != NO_ERROR) return sts; - sts = HandleKeyUpdateResponse(true, true, license_renewal_response); - if (sts != KEY_ADDED) return sts; + status = PrepareKeyUpdateReload(cdm_session); + if (status != KEY_MESSAGE && status != NO_ERROR) return status; + status = HandleKeyUpdateResponse(/* is_renewal = */ true, + /* is_restore = */ true, + license_renewal_response); + if (status != KEY_ADDED) return status; } if (!provider_session_token_.empty()) { if (cdm_session && cdm_session->SupportsUsageTable()) { - const CdmResponseType status = cdm_session->UpdateUsageEntryInformation(); - if (NO_ERROR != status) return sts; + status = cdm_session->UpdateUsageEntryInformation(); + if (NO_ERROR != status) return status; } std::string usage_report; CryptoSession::UsageDurationStatus usage_duration_status = CryptoSession::kUsageDurationsInvalid; - int64_t seconds_since_started, seconds_since_last_played; - sts = crypto_session_->GenerateUsageReport( + int64_t seconds_since_started = 0; + int64_t seconds_since_last_played = 0; + status = crypto_session_->GenerateUsageReport( provider_session_token_, &usage_report, &usage_duration_status, &seconds_since_started, &seconds_since_last_played); - if (NO_ERROR == sts) { + if (NO_ERROR == status) { switch (usage_duration_status) { case CryptoSession::kUsageDurationPlaybackNotBegun: playback_start_time = 0; @@ -885,7 +876,7 @@ CdmResponseType CdmLicense::RestoreLicenseForRelease( return CdmResponseType(INVALID_LICENSE_REQUEST_TYPE_2); } - key_request_ = signed_request.msg(); + license_request_ = signed_request.msg(); SignedMessage signed_response; if (!signed_response.ParseFromString(license_response)) { @@ -916,14 +907,17 @@ CdmResponseType CdmLicense::RestoreLicenseForRelease( return CdmResponseType(LICENSE_RESPONSE_PARSE_ERROR_5); } - if (license.has_provider_client_token()) + if (license.has_provider_client_token()) { provider_client_token_ = license.provider_client_token(); + } - if (license.id().has_provider_session_token()) + if (license.id().has_provider_session_token()) { provider_session_token_ = license.id().provider_session_token(); + } - if (license.policy().has_always_include_client_id()) + if (license.policy().has_always_include_client_id()) { renew_with_client_id_ = license.policy().always_include_client_id(); + } if (!signed_response.has_session_key()) { LOGE("No session keys present"); @@ -931,12 +925,14 @@ CdmResponseType CdmLicense::RestoreLicenseForRelease( } if (!license.id().has_provider_session_token()) { - CdmResponseType result = HandleKeyResponse(false, license_response); + const CdmResponseType result = + HandleKeyResponse(/* is_restore = */ false, license_response); return result == KEY_ADDED ? CdmResponseType(NO_ERROR) : result; } - if (license.policy().has_renewal_server_url()) - server_url_ = license.policy().renewal_server_url(); + if (license.policy().has_renewal_server_url()) { + renewal_server_url_ = license.policy().renewal_server_url(); + } // If the policy engine already has keys, they will now expire. // If the policy engine does not already have keys, this will not add any. @@ -945,7 +941,7 @@ CdmResponseType CdmLicense::RestoreLicenseForRelease( } bool CdmLicense::IsKeyLoaded(const KeyId& key_id) { - return loaded_keys_.find(key_id) != loaded_keys_.end(); + return content_key_ids_.find(key_id) != content_key_ids_.end(); } bool CdmLicense::ExtractProviderSessionToken( @@ -1009,19 +1005,19 @@ CdmResponseType CdmLicense::HandleKeyErrorResponse( CdmResponseType CdmLicense::PrepareClientId( const CdmAppParameterMap& app_parameters, const std::string& provider_client_token, LicenseRequest* license_request) { - wvcdm::ClientIdentification id; + wvcdm::ClientIdentification id_builder; if (client_token_.empty()) { LOGE("Client token not set when preparing client ID"); return CdmResponseType(CLIENT_TOKEN_NOT_SET); } CdmResponseType status = - id.InitForLicenseRequest(client_token_, crypto_session_); + id_builder.InitForLicenseRequest(client_token_, crypto_session_); if (status != NO_ERROR) return status; video_widevine::ClientIdentification* client_id = license_request->mutable_client_id(); - status = id.Prepare(app_parameters, provider_client_token, client_id); + status = id_builder.Prepare(app_parameters, provider_client_token, client_id); if (status != NO_ERROR) return status; if (Properties::UsePrivacyMode(session_id_)) { @@ -1029,10 +1025,9 @@ CdmResponseType CdmLicense::PrepareClientId( LOGE("Service certificate not staged"); return CdmResponseType(PRIVACY_MODE_ERROR_3); } - EncryptedClientIdentification* encrypted_client_id = - license_request->mutable_encrypted_client_id(); - status = service_certificate_.EncryptClientId(crypto_session_, client_id, - encrypted_client_id); + status = service_certificate_.EncryptClientId( + crypto_session_, client_id, + license_request->mutable_encrypted_client_id()); if (status != NO_ERROR) { LOGE("Failed to encrypt client ID: status = %s", status.ToString().c_str()); @@ -1054,11 +1049,10 @@ CdmResponseType CdmLicense::PrepareContentId( const InitializationData& init_data, CdmLicenseType license_type, const std::string& request_id, LicenseRequest* license_request) { // Content Identification may be a cenc_id, a webm_id or a license_id - LicenseRequest_ContentIdentification* content_id = - license_request->mutable_content_id(); + ContentIdentification* content_id = license_request->mutable_content_id(); if (init_data.is_cenc() || init_data.is_hls()) { - LicenseRequest_ContentIdentification_WidevinePsshData* widevine_pssh_data = + ContentIdentification::WidevinePsshData* widevine_pssh_data = content_id->mutable_widevine_pssh_data(); if (!init_data.IsEmpty()) { @@ -1072,7 +1066,7 @@ CdmResponseType CdmLicense::PrepareContentId( return CdmResponseType(PREPARE_CENC_CONTENT_ID_FAILED); } } else if (init_data.is_webm()) { - LicenseRequest_ContentIdentification_WebmKeyId* webm_key_id = + ContentIdentification::WebmKeyId* webm_key_id = content_id->mutable_webm_key_id(); if (!init_data.IsEmpty()) { @@ -1095,21 +1089,21 @@ CdmResponseType CdmLicense::PrepareContentId( CdmResponseType CdmLicense::HandleContentKeyResponse( bool is_restore, const std::string& session_key, const std::string& msg, const std::string& core_message, const std::string& signature, - const std::vector& key_array, + const std::vector& license_keys, const video_widevine::License& license) { - if (key_array.empty()) { + if (license_keys.empty()) { LOGE("No content keys provided"); return CdmResponseType(NO_CONTENT_KEY); } const CdmResponseType resp = crypto_session_->LoadLicense( protocol_version_ <= video_widevine::VERSION_2_1 - ? key_request_ - : Sha512Hash(key_request_), + ? license_request_ + : Sha512Hash(license_request_), session_key, msg, core_message, signature, kLicenseKeyTypeContent); if (KEY_ADDED == resp) { - loaded_keys_.clear(); - for (const CryptoKey& key : key_array) { - loaded_keys_.insert(key.key_id()); + content_key_ids_.clear(); + for (const CryptoKey& key : license_keys) { + content_key_ids_.insert(key.key_id()); } policy_engine_->SetLicense(license, is_restore); } @@ -1119,67 +1113,72 @@ CdmResponseType CdmLicense::HandleContentKeyResponse( CdmResponseType CdmLicense::HandleEntitlementKeyResponse( bool is_restore, const std::string& session_key, const std::string& msg, const std::string& core_message, const std::string& signature, - const std::vector& key_array, + const std::vector& license_keys, const video_widevine::License& license) { - if (key_array.empty()) { + if (license_keys.empty()) { LOGE("No entitlement keys provided"); return CdmResponseType(NO_CONTENT_KEY); } const CdmResponseType resp = crypto_session_->LoadLicense( protocol_version_ <= video_widevine::VERSION_2_1 - ? key_request_ - : Sha512Hash(key_request_), + ? license_request_ + : Sha512Hash(license_request_), session_key, msg, core_message, signature, kLicenseKeyTypeEntitlement); if (KEY_ADDED != resp) { return resp; } - // Save the entitlement keys for future use to handle key changes. - entitlement_keys_.CopyFrom(license.key()); + // Save the entitlement keys for future use to handle key changes, + // and for call to HandleNewEntitledKeys(). + for (const auto& key_container : license.key()) { + if (key_container.type() != KeyContainer::ENTITLEMENT) continue; + entitlement_key_ids_.insert(key_container.id()); + } policy_engine_->SetLicense(license, is_restore); - return HandleNewEntitledKeys(wrapped_keys_); + return HandleNewEntitledKeys(request_entitled_keys_); } CdmResponseType CdmLicense::HandleNewEntitledKeys( - const std::vector& wrapped_keys) { - std::vector entitled_key_array; - entitled_key_array.reserve(wrapped_keys.size()); - for (const auto& kc : entitlement_keys_) { - if (kc.type() != video_widevine::License::KeyContainer::ENTITLEMENT) { - continue; - } - for (const auto& wk : wrapped_keys) { - if (wk.entitlement_key_id() != kc.id()) continue; - // Strip PKCS#5 padding from entitled content keys. - std::string content_key = wk.key(); - if (content_key.size() < CONTENT_KEY_SIZE) { - LOGE( - "Entitled content key too small: " - "expected = %zu, actual = %zu (bytes)", - CONTENT_KEY_SIZE, content_key.size()); - return CdmResponseType(KEY_SIZE_ERROR_2); - } else if (content_key.size() > CONTENT_KEY_SIZE) { - content_key.resize(CONTENT_KEY_SIZE); - } + const std::vector& packaged_entitled_keys) { + std::vector entitled_keys; + entitled_keys.reserve(packaged_entitled_keys.size()); + // Only transfer entitled keys which there are a known entitlement + // key from the license. + for (const auto& packaged_entitled_key : packaged_entitled_keys) { + const auto it = + entitlement_key_ids_.find(packaged_entitled_key.entitlement_key_id()); + if (it == entitlement_key_ids_.end()) continue; - CryptoKey this_entry; - this_entry.set_key_id(wk.key_id()); - this_entry.set_key_data_iv(wk.iv()); - this_entry.set_entitlement_key_id(wk.entitlement_key_id()); - this_entry.set_key_data(content_key); - // Add a new entry to the key array to load oemcrypto. - entitled_key_array.push_back(std::move(this_entry)); + // Verify key data length. + const std::string& entitled_key_data = packaged_entitled_key.key(); + if (entitled_key_data.size() < CONTENT_KEY_SIZE) { + LOGE( + "Entitled content key too small: " + "expected = %zu, actual = %zu (bytes)", + CONTENT_KEY_SIZE, entitled_key_data.size()); + return CdmResponseType(KEY_SIZE_ERROR_2); } + CryptoKey entitled_key; + entitled_key.set_key_id(packaged_entitled_key.key_id()); + entitled_key.set_key_data_iv(packaged_entitled_key.iv()); + entitled_key.set_entitlement_key_id( + packaged_entitled_key.entitlement_key_id()); + // Strip PKCS#5 padding from entitled content keys. + entitled_key.set_key_data(entitled_key_data.substr(0, CONTENT_KEY_SIZE)); + entitled_keys.push_back(std::move(entitled_key)); } const CdmResponseType resp = - crypto_session_->LoadEntitledContentKeys(entitled_key_array); + crypto_session_->LoadEntitledContentKeys(entitled_keys); if (resp != KEY_ADDED) return resp; - for (const auto& wk : wrapped_keys) { - loaded_keys_.insert(wk.key_id()); + + // Loaded entitled keys can be accessed like regular content keys + // by the license. + for (const auto& entitled_key : entitled_keys) { + content_key_ids_.insert(entitled_key.key_id()); } - policy_engine_->SetEntitledLicenseKeys(wrapped_keys); + policy_engine_->SetEntitledLicenseKeys(packaged_entitled_keys); return CdmResponseType(KEY_ADDED); } diff --git a/core/src/license_protocol.proto b/core/src/license_protocol.proto index 7e814b54..748b5860 100644 --- a/core/src/license_protocol.proto +++ b/core/src/license_protocol.proto @@ -944,6 +944,12 @@ message ProvisioningResponse { // Android Attestation OTA keybox provisioning. optional bytes ota_response = 1; } + // The state of the device (system id). + enum DeviceState { + DEVICE_STATE_UNSPECIFIED = 0; + // The device is in the test only state. + DEVICE_STATE_TEST_ONLY = 1; + } // AES-128 encrypted device private RSA key. PKCS#1 ASN.1 DER-encoded. // Required. For X.509 certificates, the private RSA key may also include @@ -971,6 +977,8 @@ message ProvisioningResponse { // The Android Attestation OTA response. Only populated if the request // was an Android Attestation OTA request. optional AndroidAttestationOtaKeyboxResponse android_ota_keybox_response = 8; + // The state of the device (system id) + optional DeviceState device_state = 9; } // Protocol-specific context data used to hold the state of the server in diff --git a/core/src/okp_info.cpp b/core/src/okp_info.cpp index 30edfb4e..b7e11b5a 100644 --- a/core/src/okp_info.cpp +++ b/core/src/okp_info.cpp @@ -19,7 +19,7 @@ const char* SystemStateToString(SystemState state) { } } -bool SystemFallbackInfo::operator==(const SystemFallbackInfo& other) const { +bool SystemFallbackInfo::IsEqualTo(const SystemFallbackInfo& other) const { if (this == &other) return true; if (state_ != other.state_) return false; if (first_checked_time_ != other.first_checked_time_) return false; diff --git a/core/src/privacy_crypto_apple.cpp b/core/src/privacy_crypto_apple.cpp index 3eab7e7a..e1ca4ab3 100644 --- a/core/src/privacy_crypto_apple.cpp +++ b/core/src/privacy_crypto_apple.cpp @@ -397,7 +397,7 @@ std::string Md5Hash(const std::string& data) { } std::string Sha1Hash(const std::string& data) { - std::string hash(CC_SHA_DIGEST_LENGTH, '\0'); + std::string hash(CC_SHA1_DIGEST_LENGTH, '\0'); CC_SHA1(data.data(), data.size(), reinterpret_cast(&hash[0])); return hash; } diff --git a/core/src/privacy_crypto_boringssl.cpp b/core/src/privacy_crypto_boringssl.cpp index 64d79e37..b8738b55 100644 --- a/core/src/privacy_crypto_boringssl.cpp +++ b/core/src/privacy_crypto_boringssl.cpp @@ -6,7 +6,6 @@ // Definition of classes representing RSA public keys used // for signature verification and encryption and decryption. // - #include "privacy_crypto.h" #include @@ -56,6 +55,8 @@ void FreeKey(RSA* key) { template class boringssl_ptr { public: + WVCDM_DISALLOW_COPY_AND_MOVE(boringssl_ptr); + explicit boringssl_ptr(T* p = nullptr) : ptr_(p) {} ~boringssl_ptr() { if (ptr_) func(ptr_); @@ -67,9 +68,7 @@ class boringssl_ptr { private: T* ptr_; - CORE_DISALLOW_COPY_AND_ASSIGN(boringssl_ptr); -}; - +}; // class boringssl_ptr } // namespace namespace wvcdm { diff --git a/core/src/wv_cdm_types.cpp b/core/src/wv_cdm_types.cpp index a084c55c..44a7af20 100644 --- a/core/src/wv_cdm_types.cpp +++ b/core/src/wv_cdm_types.cpp @@ -31,7 +31,7 @@ const char* CdmResponseType::GetCodeString() const { return CdmResponseEnumToString(code_); } -bool CdmResponseType::operator==(const CdmResponseType& other) const { +bool CdmResponseType::IsEqualTo(const CdmResponseType& other) const { if (code_ != other.code_) return false; if (!HasOemcResult() || !other.HasOemcResult()) return !HasOemcResult() && !other.HasOemcResult(); @@ -175,6 +175,16 @@ const char* CdmProductionReadinessToString(CdmProductionReadiness readiness) { return UnknownValueRep(readiness); } +const char* CdmCipherModeToString(CdmCipherMode cipher_mode) { + switch (cipher_mode) { + case kCipherModeCtr: + return "CTR"; + case kCipherModeCbc: + return "CBC"; + } + return UnknownValueRep(cipher_mode); +} + const char* CdmResponseEnumToString(CdmResponseEnum cdm_response_enum) { switch (cdm_response_enum) { case NO_ERROR: diff --git a/core/test/cdm_engine_metrics_decorator_unittest.cpp b/core/test/cdm_engine_metrics_decorator_unittest.cpp index f4fd4f38..3cacfc9e 100644 --- a/core/test/cdm_engine_metrics_decorator_unittest.cpp +++ b/core/test/cdm_engine_metrics_decorator_unittest.cpp @@ -115,7 +115,7 @@ class MockCdmEngineImpl : public CdmEngine { class WvCdmEngineMetricsImplTest : public ::testing::Test { public: void SetUp() override { - file_system_.reset(CreateTestFileSystem()); + file_system_ = CreateTestFileSystem(); std::shared_ptr engine_metrics(new EngineMetrics); test_cdm_metrics_engine_.reset( new CdmEngineMetricsImpl>( diff --git a/core/test/config_test_env.h b/core/test/config_test_env.h index 9b3906b4..de3a86b9 100644 --- a/core/test/config_test_env.h +++ b/core/test/config_test_env.h @@ -1,13 +1,13 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary // source code may only be used and distributed under the Widevine License // Agreement. - #ifndef CDM_TEST_CONFIG_TEST_ENV_H_ #define CDM_TEST_CONFIG_TEST_ENV_H_ #include -#include "disallow_copy_and_assign.h" + #include "wv_cdm_types.h" +#include "wv_class_utils.h" // Declare class ConfigTestEnv - holds the configuration settings needed // to talk to the various provisioning and license servers. @@ -45,7 +45,6 @@ enum ContentId { // Configures default test environment. class ConfigTestEnv { public: - typedef struct { ServerConfigurationId id; std::string license_server_url; @@ -63,12 +62,9 @@ class ConfigTestEnv { bool release); // Allow copy, assign, and move. Performance is not an issue in test // initialization. - ConfigTestEnv(const ConfigTestEnv&) = default; - ConfigTestEnv(ConfigTestEnv&&) = default; - ConfigTestEnv& operator=(const ConfigTestEnv&) = default; - ConfigTestEnv& operator=(ConfigTestEnv&&) = default; + WVCDM_DEFAULT_COPY_AND_MOVE(ConfigTestEnv); - ~ConfigTestEnv() {}; + ~ConfigTestEnv() {} ServerConfigurationId server_id() { return server_id_; } const std::string& client_auth() const { return client_auth_; } @@ -157,11 +153,8 @@ class ConfigTestEnv { // It dump_golden_data_ is true, message data is dumped to a file for help // in generating golden test data. bool dump_golden_data_ = false; -}; - +}; // class ConfigTestEnv // The default provisioning server URL for a default provisioning request. extern const std::string kDefaultProvisioningServerUrl; - } // namespace wvcdm - #endif // CDM_TEST_CONFIG_TEST_ENV_H_ diff --git a/core/test/core_integration_test.cpp b/core/test/core_integration_test.cpp index 62b7d312..1f716b41 100644 --- a/core/test/core_integration_test.cpp +++ b/core/test/core_integration_test.cpp @@ -5,6 +5,7 @@ #include "certificate_provisioning.h" #include "license_holder.h" #include "log.h" +#include "oec_device_features.h" #include "provisioning_holder.h" #include "test_base.h" #include "wv_cdm_types.h" @@ -135,6 +136,9 @@ class CoreIntegrationTest : public WvCdmTestBaseWithEngine { * different apps. Test using two different apps and origins. */ TEST_F(CoreIntegrationTest, ProvisioningStableSpoidTest) { + if (wvoec::global_features.provisioning_method == OEMCrypto_DrmCertificate) { + GTEST_SKIP() << "Device does not provision."; + } std::string level; ASSERT_EQ( NO_ERROR, diff --git a/core/test/create_test_file_system.h b/core/test/create_test_file_system.h index fbd6505f..60b852bf 100644 --- a/core/test/create_test_file_system.h +++ b/core/test/create_test_file_system.h @@ -5,11 +5,13 @@ #ifndef CDM_TEST_CREATE_TEST_FILE_SYSTEM_H_ #define CDM_TEST_CREATE_TEST_FILE_SYSTEM_H_ +#include + #include "file_store.h" // Create a new FileSystem object that is suitable for using to create a new // CdmEngine object. How this is implemented is platform-specific. The caller // owns the returned pointer and is responsible for deleting it. -wvutil::FileSystem* CreateTestFileSystem(); +std::unique_ptr CreateTestFileSystem(); #endif // CDM_TEST_CREATE_TEST_FILE_SYSTEM_H_ diff --git a/core/test/fake_provisioning_server.cpp b/core/test/fake_provisioning_server.cpp index defe8229..a3295dce 100644 --- a/core/test/fake_provisioning_server.cpp +++ b/core/test/fake_provisioning_server.cpp @@ -1,20 +1,9 @@ // Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary // source code may only be used and distributed under the Widevine License // Agreement. - #include "fake_provisioning_server.h" -#include "core_message_deserialize.h" -#include "core_message_serialize.h" -#include "core_message_serialize_proto.h" -#include "crypto_session.h" -#include "license_protocol.pb.h" -#include "log.h" -#include "oec_key_deriver.h" -#include "oec_test_data.h" -#include "privacy_crypto.h" -#include "service_certificate.h" -#include "string_conversions.h" +#include // TODO: refactor oec_session so that these are not needed. #include @@ -27,7 +16,18 @@ #include #include #include -#include + +#include "core_message_deserialize.h" +#include "core_message_serialize.h" +#include "core_message_serialize_proto.h" +#include "crypto_session.h" +#include "license_protocol.pb.h" +#include "log.h" +#include "oec_key_deriver.h" +#include "oec_test_data.h" +#include "privacy_crypto.h" +#include "service_certificate.h" +#include "string_conversions.h" namespace wvcdm { namespace { @@ -357,5 +357,4 @@ bool FakeProvisioningServer::MakeResponse( json_response->append(kJsonEnd); return true; } - } // namespace wvcdm diff --git a/core/test/http_socket.h b/core/test/http_socket.h index 72c5f0f9..fc71dc64 100644 --- a/core/test/http_socket.h +++ b/core/test/http_socket.h @@ -1,7 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary // source code may only be used and distributed under the Widevine License // Agreement. - #ifndef CDM_TEST_HTTP_SOCKET_H_ #define CDM_TEST_HTTP_SOCKET_H_ @@ -13,13 +12,14 @@ #include #include -#include "disallow_copy_and_assign.h" +#include "wv_class_utils.h" namespace wvcdm { - // Provides basic Linux based TCP socket interface. class HttpSocket { public: + HttpSocket() = delete; + WVCDM_DISALLOW_COPY_AND_MOVE(HttpSocket); // A scheme (http:// or https://) is required for the URL. explicit HttpSocket(const std::string& url); ~HttpSocket(); @@ -70,10 +70,6 @@ class HttpSocket { // When the socket was created. Logged on error to help debug flaky // tests. e.g. b/186031735 std::time_t create_time_; - - CORE_DISALLOW_COPY_AND_ASSIGN(HttpSocket); -}; - +}; // class HttpSocket } // namespace wvcdm - #endif // CDM_TEST_HTTP_SOCKET_H_ diff --git a/core/test/license_holder.cpp b/core/test/license_holder.cpp index 089ade12..2f8ae2c5 100644 --- a/core/test/license_holder.cpp +++ b/core/test/license_holder.cpp @@ -102,7 +102,7 @@ void LicenseHolder::GenerateAndPostRenewalRequest( void LicenseHolder::FetchRenewal() { ASSERT_NE(request_in_flight_, nullptr) << "Failed for " << content_id(); ASSERT_NO_FATAL_FAILURE( - request_in_flight_->AssertOkResponse(&request_response_)) + request_in_flight_->AssertOkResponseWithRetry(&request_response_)) << "Renewal failed for " << content_id(); } @@ -144,7 +144,7 @@ void LicenseHolder::GenerateAndPostReleaseRequest( void LicenseHolder::FetchRelease() { ASSERT_NE(request_in_flight_, nullptr) << "Failed for " << content_id(); ASSERT_NO_FATAL_FAILURE( - request_in_flight_->AssertOkResponse(&request_response_)) + request_in_flight_->AssertOkResponseWithRetry(&request_response_)) << "Renewal failed for " << content_id(); } @@ -293,7 +293,7 @@ void LicenseHolder::GetKeyResponse(const CdmKeyRequest& key_request) { std::string http_response; url_request.PostRequest(key_request.message); - ASSERT_NO_FATAL_FAILURE(url_request.AssertOkResponse(&http_response)) + ASSERT_NO_FATAL_FAILURE(url_request.AssertOkResponseWithRetry(&http_response)) << "Failed for " << content_id(); LicenseRequest license_request; license_request.GetDrmMessage(http_response, key_response_); diff --git a/core/test/license_request.h b/core/test/license_request.h index 37b42883..b2bfae24 100644 --- a/core/test/license_request.h +++ b/core/test/license_request.h @@ -1,31 +1,27 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary // source code may only be used and distributed under the Widevine License // Agreement. - #ifndef CDM_TEST_LICENSE_REQUEST_H_ #define CDM_TEST_LICENSE_REQUEST_H_ #include -#include "disallow_copy_and_assign.h" + +#include "wv_class_utils.h" namespace wvcdm { - // Parses response from a license request. // This class assumes a particular response format defined by // Google license servers. class LicenseRequest { public: - LicenseRequest() {} + LicenseRequest() = default; + WVCDM_DISALLOW_COPY_AND_MOVE(LicenseRequest); ~LicenseRequest() {} void GetDrmMessage(const std::string& response, std::string& drm_msg); private: size_t FindHeaderEndPosition(const std::string& response) const; - - CORE_DISALLOW_COPY_AND_ASSIGN(LicenseRequest); -}; - +}; // class LicenseRequest } // namespace wvcdm - #endif // CDM_TEST_LICENSE_REQUEST_H_ diff --git a/core/test/license_unittest.cpp b/core/test/license_unittest.cpp index c3613da6..b8ecf17a 100644 --- a/core/test/license_unittest.cpp +++ b/core/test/license_unittest.cpp @@ -22,10 +22,32 @@ #include "wv_cdm_constants.h" namespace wvcdm { +// Protobuf generated classes +using ClientCapabilities = + video_widevine::ClientIdentification::ClientCapabilities; +using video_widevine::ClientIdentification; +using video_widevine::License; +using KeyContainer = video_widevine::License::KeyContainer; +using video_widevine::LicenseRequest; +using ContentIdentification = LicenseRequest::ContentIdentification; +using video_widevine::SignedMessage; +using PsshEntitledKey = video_widevine::WidevinePsshData::EntitledKey; + +// gmock methods +using ::testing::_; +using ::testing::DoAll; +using ::testing::NotNull; +using ::testing::PrintToStringParamName; +using ::testing::Return; +using ::testing::ReturnRef; +using ::testing::SetArgPointee; +using ::testing::SetArgReferee; +using ::testing::UnorderedElementsAre; +using ::testing::Values; -namespace { using wvutil::a2bs_hex; +namespace { const std::string kEmptyString; const std::string kAesKey = a2bs_hex("000102030405060708090a0b0c0d0e0f"); const std::string kAesIv = a2bs_hex("000102030405060708090a0b0c0d0e0f"); @@ -156,7 +178,7 @@ class MockCryptoSession : public TestCryptoSession { OEMCrypto_SignatureHashAlgorithm&), (override)); MOCK_METHOD(CdmResponseType, LoadEntitledContentKeys, - (const std::vector& key_array), (override)); + (const std::vector&), (override)); MOCK_METHOD(bool, GetResourceRatingTier, (uint32_t*), (override)); MOCK_METHOD(bool, GetWatermarkingSupport, (CdmWatermarkingSupport*), (override)); @@ -169,37 +191,11 @@ class MockPolicyEngine : public PolicyEngine { public: MockPolicyEngine(CryptoSession* crypto) : PolicyEngine("mock_session_id", nullptr, crypto) {} - MOCK_METHOD( - void, SetEntitledLicenseKeys, - (const std::vector&), - (override)); + MOCK_METHOD(void, SetEntitledLicenseKeys, + (const std::vector&), (override)); }; - } // namespace -// Protobuf generated classes -using ClientCapabilities = - video_widevine::ClientIdentification::ClientCapabilities; -using video_widevine::ClientIdentification; -using video_widevine::License; -using video_widevine::License_KeyContainer; -using video_widevine::LicenseRequest; -using video_widevine::LicenseRequest_ContentIdentification; -using video_widevine::SignedMessage; -using video_widevine::WidevinePsshData_EntitledKey; - -// gmock methods -using ::testing::_; -using ::testing::DoAll; -using ::testing::NotNull; -using ::testing::PrintToStringParamName; -using ::testing::Return; -using ::testing::ReturnRef; -using ::testing::SetArgPointee; -using ::testing::SetArgReferee; -using ::testing::UnorderedElementsAre; -using ::testing::Values; - class CdmLicenseTestPeer : public CdmLicense { public: CdmLicenseTestPeer(const CdmSessionId& session_id, wvutil::Clock* clock) @@ -208,7 +204,10 @@ class CdmLicenseTestPeer : public CdmLicense { using CdmLicense::HandleNewEntitledKeys; void set_entitlement_keys(const License& license) { - entitlement_keys_.CopyFrom(license.key()); + for (const auto& key_container : license.key()) { + if (key_container.type() != KeyContainer::ENTITLEMENT) continue; + entitlement_key_ids_.insert(key_container.id()); + } } }; @@ -368,15 +367,13 @@ TEST_F(CdmLicenseTest, PrepareKeyRequestValidation) { // Verify Client Identification const ClientIdentification& client_id = license_request.client_id(); - EXPECT_EQ( - video_widevine::ClientIdentification_TokenType_DRM_DEVICE_CERTIFICATE, - client_id.type()); + EXPECT_EQ(ClientIdentification::DRM_DEVICE_CERTIFICATE, client_id.type()); EXPECT_TRUE(std::equal(client_id.token().begin(), client_id.token().end(), kToken.begin())); EXPECT_LT(0, client_id.client_info_size()); for (int i = 0; i < client_id.client_info_size(); ++i) { - const ::video_widevine::ClientIdentification_NameValue& name_value = + const ClientIdentification::NameValue& name_value = client_id.client_info(i); EXPECT_TRUE(!name_value.name().empty()); EXPECT_TRUE(!name_value.value().empty()); @@ -406,14 +403,13 @@ TEST_F(CdmLicenseTest, PrepareKeyRequestValidation) { ClientCapabilities::WATERMARKING_CONFIGURABLE); // Verify Content Identification - const LicenseRequest_ContentIdentification& content_id = - license_request.content_id(); + const ContentIdentification& content_id = license_request.content_id(); ASSERT_TRUE(content_id.has_widevine_pssh_data()); EXPECT_FALSE(content_id.has_webm_key_id()); EXPECT_FALSE(content_id.has_existing_license()); - const ::video_widevine::LicenseRequest_ContentIdentification_WidevinePsshData& - widevine_pssh_data = content_id.widevine_pssh_data(); + const ContentIdentification::WidevinePsshData& widevine_pssh_data = + content_id.widevine_pssh_data(); EXPECT_TRUE(std::equal(widevine_pssh_data.pssh_data(0).begin(), widevine_pssh_data.pssh_data(0).end(), kCencPssh.begin())); @@ -423,8 +419,7 @@ TEST_F(CdmLicenseTest, PrepareKeyRequestValidation) { kCryptoRequestId.begin())); // Verify other license request fields - EXPECT_EQ(::video_widevine::LicenseRequest_RequestType_NEW, - license_request.type()); + EXPECT_EQ(LicenseRequest::NEW, license_request.type()); EXPECT_EQ(kLicenseStartTime, license_request.request_time()); EXPECT_EQ(video_widevine::VERSION_2_1, license_request.protocol_version()); EXPECT_EQ(kNonce, license_request.key_control_nonce()); @@ -502,15 +497,13 @@ TEST_F(CdmLicenseTest, PrepareKeyRequestValidationV15) { // Verify Client Identification const ClientIdentification& client_id = license_request.client_id(); - EXPECT_EQ( - video_widevine::ClientIdentification_TokenType_DRM_DEVICE_CERTIFICATE, - client_id.type()); + EXPECT_EQ(ClientIdentification::DRM_DEVICE_CERTIFICATE, client_id.type()); EXPECT_TRUE(std::equal(client_id.token().begin(), client_id.token().end(), kToken.begin())); EXPECT_LT(0, client_id.client_info_size()); for (int i = 0; i < client_id.client_info_size(); ++i) { - const ::video_widevine::ClientIdentification_NameValue& name_value = + const ClientIdentification::NameValue& name_value = client_id.client_info(i); EXPECT_TRUE(!name_value.name().empty()); EXPECT_TRUE(!name_value.value().empty()); @@ -540,14 +533,13 @@ TEST_F(CdmLicenseTest, PrepareKeyRequestValidationV15) { ClientCapabilities::WATERMARKING_NOT_SUPPORTED); // Verify Content Identification - const LicenseRequest_ContentIdentification& content_id = - license_request.content_id(); + const ContentIdentification& content_id = license_request.content_id(); ASSERT_TRUE(content_id.has_widevine_pssh_data()); EXPECT_FALSE(content_id.has_webm_key_id()); EXPECT_FALSE(content_id.has_existing_license()); - const ::video_widevine::LicenseRequest_ContentIdentification_WidevinePsshData& - widevine_pssh_data = content_id.widevine_pssh_data(); + const ContentIdentification::WidevinePsshData& widevine_pssh_data = + content_id.widevine_pssh_data(); EXPECT_TRUE(std::equal(widevine_pssh_data.pssh_data(0).begin(), widevine_pssh_data.pssh_data(0).end(), kCencPssh.begin())); @@ -557,8 +549,7 @@ TEST_F(CdmLicenseTest, PrepareKeyRequestValidationV15) { kCryptoRequestId.begin())); // Verify other license request fields - EXPECT_EQ(::video_widevine::LicenseRequest_RequestType_NEW, - license_request.type()); + EXPECT_EQ(LicenseRequest::NEW, license_request.type()); EXPECT_EQ(kLicenseStartTime, license_request.request_time()); EXPECT_EQ(video_widevine::VERSION_2_1, license_request.protocol_version()); EXPECT_EQ(kNonce, license_request.key_control_nonce()); @@ -590,14 +581,13 @@ TEST_P(CdmLicenseEntitledKeyTest, LoadsEntitledKeys) { // Set up a known, fake entitlement key License entitlement_license; - License_KeyContainer* entitlement_key = entitlement_license.add_key(); - entitlement_key->set_type( - video_widevine::License_KeyContainer_KeyType_ENTITLEMENT); + KeyContainer* entitlement_key = entitlement_license.add_key(); + entitlement_key->set_type(KeyContainer::ENTITLEMENT); entitlement_key->set_id(kFakeEntitlementKeyId); // Set up a fake entitled key that matches the entitlement key - std::vector entitled_keys(1); - WidevinePsshData_EntitledKey& padded_key = entitled_keys[0]; + std::vector entitled_keys(1); + PsshEntitledKey& padded_key = entitled_keys[0]; padded_key.set_entitlement_key_id(kFakeEntitlementKeyId); padded_key.set_key_id(kFakeEntitledKeyId); padded_key.set_key(variant.key); diff --git a/core/test/message_dumper.cpp b/core/test/message_dumper.cpp index 48ab9bde..27dffc27 100644 --- a/core/test/message_dumper.cpp +++ b/core/test/message_dumper.cpp @@ -235,24 +235,30 @@ void MessageDumper::DumpProvisioningRequest( const CdmProvisioningRequest& request) { if (wvoec::global_features.derive_key_method == wvoec::DeviceFeatures::TEST_PROVISION_40) { - LOGD("Provisioning 4.0 does not have a v17 or v18 core message."); + // The ODKGoldenProvision40V19 test will have its own class for now since + // we are only testing the request. + DumpHeader(&provision_file, "Provision40"); } else { DumpHeader(&provision_file, "Provision"); - SignedProvisioningMessage signed_message; - EXPECT_TRUE(signed_message.ParseFromString(request)) - << "Request = " << wvutil::b2a_hex(request); - if (wvoec::global_features.api_version >= wvoec::kCoreMessagesAPI) { - EXPECT_TRUE(signed_message.has_oemcrypto_core_message()); - DumpHex(&provision_file, "core_request", - signed_message.oemcrypto_core_message()); - } + } + SignedProvisioningMessage signed_message; + EXPECT_TRUE(signed_message.ParseFromString(request)) + << "Request = " << wvutil::b2a_hex(request); + if (wvoec::global_features.api_version >= wvoec::kCoreMessagesAPI) { + EXPECT_TRUE(signed_message.has_oemcrypto_core_message()); + DumpHex(&provision_file, "core_request", + signed_message.oemcrypto_core_message()); } } void MessageDumper::DumpProvisioning(const CdmProvisioningResponse& response) { if (wvoec::global_features.derive_key_method == wvoec::DeviceFeatures::TEST_PROVISION_40) { - LOGD("Provisioning 4.0 does not have a core message."); + LOGD( + "Provisioning 4.0 does not have a v17, v18 or v19 core message in the " + "response."); + provision_file << " RunTest();\n"; + provision_file << "}\n\n"; } else { SignedProvisioningMessage signed_response; if (!signed_response.ParseFromString(response)) { diff --git a/core/test/provisioning_holder.cpp b/core/test/provisioning_holder.cpp index 697beb97..39074b06 100644 --- a/core/test/provisioning_holder.cpp +++ b/core/test/provisioning_holder.cpp @@ -69,7 +69,7 @@ void ProvisioningHolder::Provision(CdmCertificateType cert_type, url_request.PostCertRequestInQueryString(request); // Receive and parse response. - ASSERT_NO_FATAL_FAILURE(url_request.AssertOkResponse(&response_)) + ASSERT_NO_FATAL_FAILURE(url_request.AssertOkResponseWithRetry(&response_)) << "Failed to fetch provisioning response. " << DumpProvAttempt(request, response_, cert_type); diff --git a/core/test/reboot_test.cpp b/core/test/reboot_test.cpp index 1e4f61f2..32a06375 100644 --- a/core/test/reboot_test.cpp +++ b/core/test/reboot_test.cpp @@ -22,6 +22,7 @@ using wvutil::TestSleep; namespace wvcdm { FileSystem* RebootTest::file_system_; +std::unique_ptr RebootTest::default_file_system_; namespace { // How much fudge or round off error do we allow in license durations for reboot @@ -31,7 +32,10 @@ constexpr int64_t kFudge = 10; void RebootTest::SetUp() { WvCdmTestBase::SetUp(); - if (!file_system_) file_system_ = CreateTestFileSystem(); + if (!file_system_) { + if (!default_file_system_) default_file_system_ = CreateTestFileSystem(); + file_system_ = default_file_system_.get(); + } const ::testing::TestInfo* const test_info = ::testing::UnitTest::GetInstance()->current_test_info(); diff --git a/core/test/reboot_test.h b/core/test/reboot_test.h index 5d9b097e..507242c6 100644 --- a/core/test/reboot_test.h +++ b/core/test/reboot_test.h @@ -6,6 +6,7 @@ #define WVCDM_CORE_REBOOT_TEST_H_ #include +#include #include #include @@ -37,6 +38,7 @@ class RebootTest : public WvCdmTestBaseWithEngine { // This is used to store each test's persistent data. static wvutil::FileSystem* file_system_; + static std::unique_ptr default_file_system_; // The persistent data for the current test. std::map persistent_data_; diff --git a/core/test/test_base.cpp b/core/test/test_base.cpp index 5463c243..8ce0e01b 100644 --- a/core/test/test_base.cpp +++ b/core/test/test_base.cpp @@ -373,9 +373,12 @@ void WvCdmTestBase::InstallTestRootOfTrust() { sizeof(test_keybox))); break; case wvoec::DeviceFeatures::LOAD_TEST_RSA_KEY: - // Rare case: used by devices with baked in DRM cert. + // Rare case: used by devices with baked in production DRM cert. ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_LoadTestRSAKey()); break; + case wvoec::DeviceFeatures::PRELOADED_RSA_KEY: + // Rare case: used by devices with baked in test DRM cert. + break; case wvoec::DeviceFeatures::TEST_PROVISION_30: // Can use oem certificate to install test rsa key. break; @@ -404,6 +407,10 @@ void WvCdmTestBase::Provision() { } void WvCdmTestBase::EnsureProvisioned() { + if (wvoec::global_features.provisioning_method == OEMCrypto_DrmCertificate) { + LOGD("Device is preprovisioned."); + return; + } CdmSessionId session_id; std::unique_ptr file_system(CreateTestFileSystem()); // OpenSession will check if a DRM certificate exists, while diff --git a/core/test/url_request.cpp b/core/test/url_request.cpp index 933e2677..0c37e3b7 100644 --- a/core/test/url_request.cpp +++ b/core/test/url_request.cpp @@ -5,7 +5,9 @@ #include "url_request.h" #include +#include +#include #include #include @@ -24,11 +26,15 @@ const int kConnectTimeoutMs = 15000; const int kWriteTimeoutMs = 12000; const int kReadTimeoutMs = 12000; constexpr int kHttpOk = 200; +const std::vector kRetryCodes = {502, 504}; const std::string kGoogleHeaderUpper("X-Google"); const std::string kGoogleHeaderLower("x-google"); const std::string kCrLf("\r\n"); +constexpr unsigned kRetryCount = 3; +constexpr unsigned kRetryIntervalSeconds = 1; + // Concatenate all chunks into one blob and returns the response with // header information. void ConcatenateChunkedResponse(const std::string http_response, @@ -127,13 +133,34 @@ bool UrlRequest::GetResponse(std::string* message) { return true; } -void UrlRequest::AssertOkResponse(std::string* message) { +void UrlRequest::AssertOkResponseWithRetry(std::string* message) { ASSERT_TRUE(message); - ASSERT_TRUE(GetResponse(message)); - const int status_code = GetStatusCode(*message); - ASSERT_EQ(kHttpOk, status_code) << "HTTP response from " << socket_.url() - << ": (" << message->size() << ") :\n" - << *message; + int status_code = 0; + for (unsigned i = 0; i < kRetryCount; i++) { + *message = ""; + ASSERT_TRUE(GetResponse(message)) << "For attempt " << (i + 1); + status_code = GetStatusCode(*message); + // If we didn't get a retry status, then we're done. + if (std::find(kRetryCodes.begin(), kRetryCodes.end(), status_code) == + kRetryCodes.end()) { + ASSERT_EQ(kHttpOk, status_code) << "HTTP response from " << socket_.url() + << ": (" << message->size() << ") :\n" + << *message; + return; + } + std::cerr << "Temporary failure HTTP response from " << socket_.url() + << ": (" << message->size() << ") :\n" + << *message << "\n" + << "Attempt " << (i + 1) << "\n"; + socket_.CloseSocket(); + is_connected_ = false; + sleep(kRetryIntervalSeconds << i); + Reconnect(); + SendRequestOnce(); + } + GTEST_FAIL() << "HTTP response from " << socket_.url() << ": (" + << message->size() << ") :\n" + << *message; } // static @@ -190,36 +217,35 @@ bool UrlRequest::GetDebugHeaderFields( bool UrlRequest::PostRequestWithPath(const std::string& path, const std::string& data) { - std::string request; + request_.clear(); - request.append("POST "); - request.append(path); - request.append(" HTTP/1.1\r\n"); + request_.append("POST "); + request_.append(path); + request_.append(" HTTP/1.1\r\n"); - request.append("Host: "); - request.append(socket_.domain_name()); - request.append("\r\n"); + request_.append("Host: "); + request_.append(socket_.domain_name()); + request_.append("\r\n"); - request.append("Connection: close\r\n"); - request.append("User-Agent: Widevine CDM v1.0\r\n"); - request.append("X-Return-Encrypted-Headers: request_and_response\r\n"); + request_.append("Connection: close\r\n"); + request_.append("User-Agent: Widevine CDM v1.0\r\n"); + request_.append("X-Return-Encrypted-Headers: request_and_response\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), "%zu", data.size()); + request_.append("Content-Length: "); + request_.append(std::to_string(data.size())); + request_.append("\r\n"); - 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("\r\n"); // empty line to terminate headers - - request.append(data); + request_.append(data); + return SendRequestOnce(); +} +bool UrlRequest::SendRequestOnce() { const int ret = socket_.WriteAndLogErrors( - request.c_str(), static_cast(request.size()), kWriteTimeoutMs); - LOGV("HTTP request: (%zu): %s", request.size(), request.c_str()); - LOGV("HTTP request hex: %s", wvutil::b2a_hex(request).c_str()); + request_.c_str(), static_cast(request_.size()), kWriteTimeoutMs); + LOGV("HTTP request: (%zu): %s", request_.size(), request_.c_str()); + LOGV("HTTP request hex: %s", wvutil::b2a_hex(request_).c_str()); return ret != -1; } diff --git a/core/test/url_request.h b/core/test/url_request.h index c2d0d35e..9b3bc418 100644 --- a/core/test/url_request.h +++ b/core/test/url_request.h @@ -1,22 +1,23 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary // source code may only be used and distributed under the Widevine License // Agreement. - #ifndef CDM_TEST_URL_REQUEST_H_ #define CDM_TEST_URL_REQUEST_H_ #include #include -#include "disallow_copy_and_assign.h" #include "http_socket.h" +#include "wv_class_utils.h" namespace wvcdm { - // Provides simple HTTP request and response service. // Only POST request method is implemented. class UrlRequest { public: + UrlRequest() = delete; + WVCDM_DISALLOW_COPY_AND_MOVE(UrlRequest); + explicit UrlRequest(const std::string& url); ~UrlRequest(); @@ -29,7 +30,8 @@ class UrlRequest { bool GetResponse(std::string* message); static int GetStatusCode(const std::string& response); // Get the response, and expect the status is OK. - void AssertOkResponse(std::string* message); + // It will retry if the response code is in the 500 range. + void AssertOkResponseWithRetry(std::string* message); static bool GetDebugHeaderFields( const std::string& response, @@ -37,13 +39,11 @@ class UrlRequest { private: bool PostRequestWithPath(const std::string& path, const std::string& data); + bool SendRequestOnce(); bool is_connected_; HttpSocket socket_; - - CORE_DISALLOW_COPY_AND_ASSIGN(UrlRequest); -}; - + std::string request_; +}; // class UrlRequest } // namespace wvcdm - #endif // CDM_TEST_URL_REQUEST_H_ diff --git a/factory_upload_tool/ce/wv_factory_extractor.cpp b/factory_upload_tool/ce/wv_factory_extractor.cpp index c306cb0c..1628aba1 100644 --- a/factory_upload_tool/ce/wv_factory_extractor.cpp +++ b/factory_upload_tool/ce/wv_factory_extractor.cpp @@ -11,6 +11,39 @@ namespace widevine { namespace { +std::string EscapeJson(const std::string& input) { + std::string result; + for (std::string::const_iterator c = input.begin(); c != input.end(); ++c) { + switch (*c) { + case '\"': + result += "\\\""; + break; + case '\\': + result += "\\\\"; + break; + case '\b': + result += "\\b"; + break; + case '\f': + result += "\\f"; + break; + case '\n': + result += "\\n"; + break; + case '\r': + result += "\\r"; + break; + case '\t': + result += "\\t"; + break; + default: + result += *c; + break; + } + } + return result; +} + std::string StringMapToJson( const std::map& string_map) { std::string json = "{"; @@ -72,7 +105,7 @@ Status WidevineFactoryExtractor::GenerateUploadRequest(std::string& request) { request_map["model"] = PropertiesCE::GetClientInfo().model_name; request_map["product"] = PropertiesCE::GetClientInfo().product_name; request_map["build_info"] = PropertiesCE::GetClientInfo().build_info; - request_map["oemcrypto_build_info"] = oemcrypto_build_info; + request_map["oemcrypto_build_info"] = EscapeJson(oemcrypto_build_info); request_map["bcc"] = wvutil::Base64Encode(bcc); std::string request_json = StringMapToJson(request_map); diff --git a/factory_upload_tool/ce/wv_upload_tool.py b/factory_upload_tool/ce/wv_upload_tool.py old mode 100644 new mode 100755 index d8ae09d0..e70ce74c --- a/factory_upload_tool/ce/wv_upload_tool.py +++ b/factory_upload_tool/ce/wv_upload_tool.py @@ -12,6 +12,7 @@ input, with one JSON string per line of input. """ import argparse +from http import HTTPStatus import http.server import json import os @@ -25,6 +26,7 @@ from google.oauth2 import service_account DEFAULT_BASE = 'https://widevine.googleapis.com/v1beta1' UPLOAD_PATH = '/uniqueDeviceInfo:batchUpload' +BATCH_CHECK_PATH = '/uniqueDeviceInfo:batchCheck' TOKEN_CACHE_FILE = os.path.join( os.path.expanduser('~'), '.device_info_uploader.token' ) @@ -128,15 +130,15 @@ def parse_args(): Returns: An argparse.Namespace object populated with the arguments. """ - parser = argparse.ArgumentParser(description='Upload device info') + parser = argparse.ArgumentParser(description='Widevine BCC Batch Upload/Check Tool') + + parser.add_argument("--version", action="version", version="20240822") #yyyymmdd + parser.add_argument( '--json-csr', nargs='+', required=True, - help=( - 'list of files containing JSON output from' - ' rkp_factory_extraction_tool' - ), + help='list of files containing JSON output from factory extraction tool', ) parser.add_argument('--credentials', help='JSON credentials file') @@ -161,6 +163,30 @@ def parse_args(): action='store_true', help='exit on error and stop uploading more CSRs', ) + + parser.add_argument( + '--dryrun', + action='store_true', + help=( + 'Do not upload anything. Instead print out what actions would have' + ' been taken if the --dryrun flag had not been specified.' + ), + ) + + parser.add_argument( + '--check', + action='store_true', + required=False, + help='Perform a batch check on the CSRs.', + ) + + parser.add_argument( + '--verbose', + action='store_true', + required=False, + help='Print request and response details.', + ) + return parser.parse_args() @@ -177,6 +203,7 @@ def parse_json_csrs(filename, batches): line_count = 0 for line in open(filename): line_count = line_count + 1 + obj = {} try: obj = json.loads(line) except json.JSONDecodeError as e: @@ -191,13 +218,16 @@ def parse_json_csrs(filename, batches): 'model': obj['model'], 'product': obj['product'], 'build_info': obj['build_info'], + 'oemcrypto_build_info': obj['oemcrypto_build_info'], }) + if device_metadata not in batches: + batches[device_metadata] = [] + batches[device_metadata].append(bcc) except KeyError as e: die(f'Invalid object at {filename}:{line_count}, missing {e}') - if device_metadata not in batches: - batches[device_metadata] = [] - batches[device_metadata].append(bcc) + if line_count == 0: + die('Empty BCC file!') def format_request_body(args, device_metadata, bccs): @@ -211,6 +241,17 @@ def format_request_body(args, device_metadata, bccs): return json.dumps(request).encode('utf-8') +def format_check_request_body(args, bccs): + """Generate a formatted BatchCheck request buffer for the given build and CSRs.""" + request = { + 'parent': 'orgs/' + args.org_name, + 'request_id': uuid.uuid4().hex, + 'device_info': bccs, + } + + return json.dumps(request).encode('utf-8') + + def load_refresh_token(): if not os.path.exists(TOKEN_CACHE_FILE): return None @@ -369,16 +410,52 @@ def upload_batch(args, device_metadata, bccs): device_metadata: The build for which we're uploading CSRs bccs: a list of BCCs to be uploaded for the given build """ - print("Uploading {} bcc(s) for build '{}'".format(len(bccs), device_metadata)) + print('Uploading {} bcc(s)'.format(len(bccs))) + if args.verbose: + print("Build: '{}'".format(device_metadata)) body = format_request_body(args, device_metadata, bccs) - print(body) - print(args.endpoint + UPLOAD_PATH) - request = urllib.request.Request(args.endpoint + UPLOAD_PATH) + return batch_action_single_attempt(args, UPLOAD_PATH, body) + + +def check_batch(args, device_metadata, bccs): + """Batch check all the CSRs. + + Args: + args: The parsed command-line arguments + device_metadata: The build for which we're checking CSRs + bccs: a list of BCCs to be checked for the given build + """ + print('Checking {} bcc(s)'.format(len(bccs))) + if args.verbose: + print("Build: '{}'".format(device_metadata)) + body = format_check_request_body(args, bccs) + return batch_action_single_attempt(args, BATCH_CHECK_PATH, body) + + +def batch_action_single_attempt(args, path, body): + """Batch action (upload or check existence) for all the CSRs in chunks. + + Args: + args: The parsed command-line arguments + csrs: a list of CSRs to be uploaded/checked for the given build + path: The endpoint url for the specific action + body: The formatted request body + """ + if args.verbose: + print('Request body:') + print(body) + print('Request target:') + print(args.endpoint + path) + request = urllib.request.Request(args.endpoint + path) request.add_header('Content-Type', 'application/json') request.add_header('X-GFE-SSL', 'yes') request.add_header( 'Authorization', 'Bearer ' + authenticate_and_fetch_token(args) ) + if args.dryrun: + print('dry run: would have reached to ' + request.full_url) + return HTTPStatus.OK + try: response = urllib.request.urlopen(request, body) except urllib.error.HTTPError as e: @@ -388,20 +465,39 @@ def upload_batch(args, device_metadata, bccs): sys.exit(1) response_body = response.read().decode('utf-8') - print(response_body) + if args.verbose: + print('Response body:') + print(response_body) res = json.loads(response_body) - if 'failedDeviceInfo' in res and args.die_on_error: - sys.exit(1) - + if 'failedDeviceInfo' in res: + eprint('Failed to upload/check some device info! Response body:') + eprint(response_body) + eprint('Failed: {} bcc(s)'.format(len(res['failedDeviceInfo']))) + if args.die_on_error: + sys.exit(1) + elif 'successDeviceInfo' in res: + print('Success: {} bcc(s)'.format(len(res['successDeviceInfo']))) + else: + eprint('Failed with unexpected response:') + eprint(response_body) def main(): args = parse_args() + if args.dryrun: + print('Dry run mode enabled. Service APIs will not be called.') + batches = {} for filename in args.json_csr: parse_json_csrs(filename, batches) + if len(batches) > 1: + print('WARNING: {} different device metadata'.format(len(batches))) + for device_metadata, bccs in batches.items(): - upload_batch(args, device_metadata, bccs) + if args.check: + check_batch(args, device_metadata, bccs) + else: + upload_batch(args, device_metadata, bccs) if __name__ == '__main__': diff --git a/factory_upload_tool/common/src/WidevineOemcryptoInterface.cpp b/factory_upload_tool/common/src/WidevineOemcryptoInterface.cpp index dee299c6..b35ac733 100644 --- a/factory_upload_tool/common/src/WidevineOemcryptoInterface.cpp +++ b/factory_upload_tool/common/src/WidevineOemcryptoInterface.cpp @@ -46,6 +46,7 @@ std::unique_ptr FileSystem::Open(const std::string&, int) { return std::unique_ptr(new FileImpl()); } bool FileSystem::Exists(const std::string&) { return false; } +bool FileSystem::Exists(const std::string&, int*) { return false; } bool FileSystem::Remove(const std::string&) { return false; } ssize_t FileSystem::FileSize(const std::string&) { return false; } bool FileSystem::List(const std::string&, std::vector*) { @@ -117,6 +118,9 @@ OEMCryptoResult OEMCryptoInterface::GetBcc(std::vector& bcc) { LOGI("GetBootCertificateChain second attempt result %d", result); } + if (result == OEMCrypto_SUCCESS) { + bcc.resize(bcc_size); + } return result; } @@ -136,7 +140,9 @@ OEMCryptoResult OEMCryptoInterface::GetOEMCryptoBuildInfo( result = BuildInformation(&build_info[0], &build_info_size); LOGI("BuildInformation second attempt result %d", result); } - + if (result == OEMCrypto_SUCCESS) { + build_info.resize(build_info_size); + } return result; } diff --git a/oemcrypto/include/OEMCryptoCENC.h b/oemcrypto/include/OEMCryptoCENC.h index c710f09d..a234c35a 100644 --- a/oemcrypto/include/OEMCryptoCENC.h +++ b/oemcrypto/include/OEMCryptoCENC.h @@ -3,7 +3,7 @@ // License Agreement. /** - * @mainpage OEMCrypto API v19.2 + * @mainpage OEMCrypto API v19.3 * * OEMCrypto is the low level library implemented by the OEM to provide key and * content protection, usually in a separate secure memory or process space. The @@ -746,6 +746,7 @@ typedef enum OEMCrypto_SignatureHashAlgorithm { #define OEMCrypto_UseSecondaryKey _oecc152 #define OEMCrypto_MarkOfflineSession _oecc153 #define OEMCrypto_WrapClearPrivateKey _oecc154 +#define OEMCrypto_SetSessionUsage _oecc155 // clang-format on /// @addtogroup initcontrol @@ -1939,6 +1940,33 @@ OEMCryptoResult OEMCrypto_GetOEMKeyToken(OEMCrypto_SESSION key_session, uint8_t* key_token, size_t* key_token_length); +/** + * Sets the session's usage information and scrambling mode, allowing the + * descrambler to be set up to decode one or more streams encrypted by the + * Conditional Access System (CAS). This method is currently used exclusively by + * CAS. + * + * @param[in] session: session id. + * @param[in] intent: session usage information. A constant defined by MediaCaS. + * @param[in] mode: scrambling mode. A constant defined by MediaCaS. + * + * @retval OEMCrypto_SUCCESS on success + * @retval OEMCrypto_ERROR_INVALID_SESSION + * @retval OEMCrypto_ERROR_INVALID_CONTEXT + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED + * + * @threading + * This is a "Session Function" and may be called simultaneously with session + * functions for other sessions but not simultaneously with other functions + * for this session. It is as if the CDM holds a write lock for this session, + * and a read lock on the OEMCrypto system. + * + * @version + * This method is new in API version 19. + */ +OEMCryptoResult OEMCrypto_SetSessionUsage(OEMCrypto_SESSION session, + uint32_t intent, uint32_t mode); + /// @} /// @addtogroup decryption @@ -2236,10 +2264,20 @@ OEMCryptoResult OEMCrypto_GetKeyHandle(OEMCrypto_SESSION session, * usually be non-zero. This mode allows devices to decrypt FMP4 HLS content, * SAMPLE-AES HLS content, as well as content using the DASH 'cbcs' scheme. * - * The skip field of OEMCrypto_CENCEncryptPatternDesc may also be zero. If - * the skip field is zero, then patterns are not in use and all crypto blocks - * in the protected part of the subsample are encrypted. It is not valid for - * the encrypt field to be zero. + * The skip field of OEMCrypto_CENCEncryptPatternDesc may be zero. If the skip + * field is zero, then patterns are not in use and all crypto blocks in the + * protected part of the subsample are encrypted, except for any partial crypto + * blocks at the end. The most common pattern with a skip field of zero is + * (10,0), but all patterns with a skip field of zero are functionally the same. + * + * If the skip field of OEMCrypto_CENCEncryptPatternDesc is zero, the encrypt + * field may also be zero. This pattern sometimes appears in content, + * particularly in audio tracks. This (0,0) pattern should be treated as + * equivalent to the pattern (10,0). e.g. All complete crypto blocks should be + * decrypted. + * + * It is not valid for the encrypt field of OEMCrypto_CENCEncryptPatternDesc to + * be zero if the skip field is non-zero. * * The length of a crypto block in AES-128 is 16 bytes. In the 'cbcs' scheme, * if the protected part of a subsample has a length that is not a multiple diff --git a/oemcrypto/odk/include/core_message_features.h b/oemcrypto/odk/include/core_message_features.h index 36d9d83d..93ee31f6 100644 --- a/oemcrypto/odk/include/core_message_features.h +++ b/oemcrypto/odk/include/core_message_features.h @@ -26,9 +26,9 @@ struct CoreMessageFeatures { // This is the published version of the ODK Core Message library. The default // behavior is for the server to restrict messages to at most this version - // number. The default is 19.2. + // number. The default is 19.3. uint32_t maximum_major_version = 19; - uint32_t maximum_minor_version = 2; + uint32_t maximum_minor_version = 3; bool operator==(const CoreMessageFeatures &other) const; bool operator!=(const CoreMessageFeatures &other) const { diff --git a/oemcrypto/odk/include/odk_structs.h b/oemcrypto/odk/include/odk_structs.h index 55713f48..10efc8d4 100644 --- a/oemcrypto/odk/include/odk_structs.h +++ b/oemcrypto/odk/include/odk_structs.h @@ -16,10 +16,10 @@ extern "C" { /* The version of this library. */ #define ODK_MAJOR_VERSION 19 -#define ODK_MINOR_VERSION 2 +#define ODK_MINOR_VERSION 3 /* ODK Version string. Date changed automatically on each release. */ -#define ODK_RELEASE_DATE "ODK v19.2 2024-06-11" +#define ODK_RELEASE_DATE "ODK v19.3 2024-09-04" /* The lowest version number for an ODK message. */ #define ODK_FIRST_VERSION 16 diff --git a/oemcrypto/odk/src/core_message_features.cpp b/oemcrypto/odk/src/core_message_features.cpp index 249bfdf3..d0e6b282 100644 --- a/oemcrypto/odk/src/core_message_features.cpp +++ b/oemcrypto/odk/src/core_message_features.cpp @@ -33,7 +33,7 @@ CoreMessageFeatures CoreMessageFeatures::DefaultFeatures( features.maximum_minor_version = 4; // 18.4 break; case 19: - features.maximum_minor_version = 2; // 19.2 + features.maximum_minor_version = 3; // 19.3 break; default: features.maximum_minor_version = 0; diff --git a/oemcrypto/odk/src/odk_timer.c b/oemcrypto/odk/src/odk_timer.c index d5523ec8..822b2665 100644 --- a/oemcrypto/odk/src/odk_timer.c +++ b/oemcrypto/odk/src/odk_timer.c @@ -277,7 +277,7 @@ OEMCryptoResult ODK_InitializeSessionValues(ODK_TimerLimits* timer_limits, nonce_values->api_minor_version = 4; break; case 19: - nonce_values->api_minor_version = 2; + nonce_values->api_minor_version = 3; break; default: nonce_values->api_minor_version = 0; diff --git a/oemcrypto/odk/test/odk_test.cpp b/oemcrypto/odk/test/odk_test.cpp index 1cc2f6ca..b0cc6326 100644 --- a/oemcrypto/odk/test/odk_test.cpp +++ b/oemcrypto/odk/test/odk_test.cpp @@ -1275,7 +1275,7 @@ std::vector TestCases() { {16, ODK_MAJOR_VERSION, ODK_MINOR_VERSION, 16, 5}, {17, ODK_MAJOR_VERSION, ODK_MINOR_VERSION, 17, 2}, {18, ODK_MAJOR_VERSION, ODK_MINOR_VERSION, 18, 4}, - {19, ODK_MAJOR_VERSION, ODK_MINOR_VERSION, 19, 2}, + {19, ODK_MAJOR_VERSION, ODK_MINOR_VERSION, 19, 3}, // Here are some known good versions. Make extra sure they work. {ODK_MAJOR_VERSION, 16, 3, 16, 3}, {ODK_MAJOR_VERSION, 16, 4, 16, 4}, @@ -1288,6 +1288,7 @@ std::vector TestCases() { {ODK_MAJOR_VERSION, 19, 0, 19, 0}, {ODK_MAJOR_VERSION, 19, 1, 19, 1}, {ODK_MAJOR_VERSION, 19, 2, 19, 2}, + {ODK_MAJOR_VERSION, 19, 3, 19, 3}, {0, 16, 3, 16, 3}, {0, 16, 4, 16, 4}, {0, 16, 5, 16, 5}, @@ -1298,6 +1299,7 @@ std::vector TestCases() { {0, 19, 0, 19, 0}, {0, 19, 1, 19, 1}, {0, 19, 2, 19, 2}, + {0, 19, 3, 19, 3}, }; return test_cases; } diff --git a/oemcrypto/test/GEN_api_lock_file.c b/oemcrypto/test/GEN_api_lock_file.c index af60124c..63165bdb 100644 --- a/oemcrypto/test/GEN_api_lock_file.c +++ b/oemcrypto/test/GEN_api_lock_file.c @@ -434,3 +434,7 @@ OEMCryptoResult _oecc154(const uint8_t* clear_private_key_bytes, // OEMCrypto_MarkOfflineSession defined in v19.2 OEMCryptoResult _oecc153(OEMCrypto_SESSION session); + +// OEMCrypto_SetSessionUsage defined in v18.7 +OEMCryptoResult _oecc155(OEMCrypto_SESSION session, uint32_t intent, + uint32_t mode); diff --git a/oemcrypto/test/extract_bcc_tool.cpp b/oemcrypto/test/extract_bcc_tool.cpp index a469ea6a..ad34d5e5 100644 --- a/oemcrypto/test/extract_bcc_tool.cpp +++ b/oemcrypto/test/extract_bcc_tool.cpp @@ -12,6 +12,7 @@ #include "OEMCryptoCENC.h" #include "string_conversions.h" +#include "wv_factory_extractor.h" namespace { // Make and Model for system ID resolution. @@ -24,116 +25,8 @@ const std::string kDeviceName = "prov40 test client"; const std::string kDeviceProduct = "prov40 test"; const std::string kDeviceBuildInfo = "prov40 test build"; -// == Utils == - -std::string StringMapToJson( - const std::map& string_map) { - std::string json = "{"; - for (const auto& value_pair : string_map) { - std::string escaped_value = - std::regex_replace(value_pair.second, std::regex("\""), "\\\""); - json.append("\"" + value_pair.first + "\": " + "\"" + escaped_value + - "\","); - } - json.resize(json.size() - 1); // Remove the last comma. - json.append("}"); - return json; -} // == Primary == - -bool GetBccAndBuildInfo(std::vector* bcc, - std::string* oemcrypto_build_info) { - // Step 1: Initialize. - OEMCryptoResult result = OEMCrypto_Initialize(); - if (result != OEMCrypto_SUCCESS) { - std::cerr << "Failed to initialize: result = " << result << std::endl; - return false; - } - - // Step 2: Get BCC. - const OEMCrypto_ProvisioningMethod method = OEMCrypto_GetProvisioningMethod(); - if (method != OEMCrypto_BootCertificateChain) { - std::cerr << "ProvisioningMethod is not BCC type: method = "; - std::cerr << method << std::endl; - OEMCrypto_Terminate(); - return false; - } - - bcc->resize(0); - size_t bcc_size = 0; - std::vector additional_signature; // It should be empty. - size_t additional_signature_size = 0; - result = OEMCrypto_GetBootCertificateChain(bcc->data(), &bcc_size, - additional_signature.data(), - &additional_signature_size); - if (additional_signature_size != 0) { - std::cerr << "The additional_signature_size required by OEMCrypto is " - << additional_signature_size - << ", while it is expected to be zero." << std::endl; - OEMCrypto_Terminate(); - return false; - } - if (result == OEMCrypto_ERROR_SHORT_BUFFER) { - bcc->resize(bcc_size); - additional_signature.resize(additional_signature_size); - result = OEMCrypto_GetBootCertificateChain(bcc->data(), &bcc_size, - additional_signature.data(), - &additional_signature_size); - } - if (result != OEMCrypto_SUCCESS) { - std::cerr << "Failed to get BCC: result = " << result << std::endl; - OEMCrypto_Terminate(); - return false; - } - bcc->resize(bcc_size); - - // Step 3: Get oemcrypto build info. - oemcrypto_build_info->resize(0); - size_t oemcrypto_build_info_size = 0; - result = OEMCrypto_BuildInformation(oemcrypto_build_info->data(), - &oemcrypto_build_info_size); - if (result == OEMCrypto_ERROR_SHORT_BUFFER) { - oemcrypto_build_info->resize(oemcrypto_build_info_size); - result = OEMCrypto_BuildInformation(oemcrypto_build_info->data(), - &oemcrypto_build_info_size); - } - if (result != OEMCrypto_SUCCESS) { - std::cerr << "Failed to get build information: result = " << result - << std::endl; - OEMCrypto_Terminate(); - return false; - } - oemcrypto_build_info->resize(oemcrypto_build_info_size); - - // Step 4: Cleanup. - result = OEMCrypto_Terminate(); - if (result != OEMCrypto_SUCCESS) { - std::cerr << "Failed to terminate: result = " << result << std::endl; - return false; - } - return true; -} - -bool GenerateBccRecord(const std::vector& bcc, - const std::string& oemcrypto_build_info, - std::string* bcc_record) { - std::map record; - record["company"] = kDeviceMake; - record["model"] = kDeviceModel; - - record["architecture"] = kDeviceArchitecture; - record["name"] = kDeviceName; - record["product"] = kDeviceProduct; - record["build_info"] = kDeviceBuildInfo; - record["bcc"] = wvutil::Base64Encode(bcc); - record["oemcrypto_build_info"] = oemcrypto_build_info; - - const std::string record_json = StringMapToJson(record); - bcc_record->assign(record_json.begin(), record_json.end()); - return true; -} - bool OutputBccRecord(const std::string& path, const std::string& record) { std::cout << "Writing BCC record to file " << path << std::endl; std::cout << record << std::endl; @@ -154,17 +47,27 @@ int main(int argc, char** argv) { } const std::string bcc_path = argv[1]; - std::vector bcc; - std::string oemcrypto_build_info; - if (!GetBccAndBuildInfo(&bcc, &oemcrypto_build_info)) { - std::cerr << "Failed to get BCC or OEMCrypto build info" << std::endl; + widevine::ClientInfo client_info; + client_info.company_name = kDeviceMake; + client_info.arch_name = kDeviceArchitecture; + client_info.device_name = kDeviceName; + client_info.model_name = kDeviceModel; + client_info.product_name = kDeviceProduct; + client_info.build_info = kDeviceBuildInfo; + + auto extractor = widevine::WidevineFactoryExtractor::Create(client_info); + if (extractor == nullptr) { + std::cerr << "Failed to create WidevineFactoryExtractor" << std::endl; return 1; } + std::string bcc_record; - if (!GenerateBccRecord(bcc, oemcrypto_build_info, &bcc_record)) { - std::cerr << "Failed to generate BCC record" << std::endl; + widevine::Status status = extractor->GenerateUploadRequest(bcc_record); + if (status != widevine::Status::kSuccess) { + std::cerr << "Fail to generate BCC record: " << status << std::endl; return 1; } + if (!OutputBccRecord(bcc_path, bcc_record)) { std::cerr << "Failed to output BCC record" << std::endl; return 1; diff --git a/oemcrypto/test/install_prov30_oem_cert_tool.cpp b/oemcrypto/test/install_prov30_oem_cert_tool.cpp index 25eac6d4..f4fd4084 100644 --- a/oemcrypto/test/install_prov30_oem_cert_tool.cpp +++ b/oemcrypto/test/install_prov30_oem_cert_tool.cpp @@ -26,6 +26,8 @@ format below: +-----------------------+----------------------+--------------------------+ | Private Key | +-----------------------+ +| (DER-encoded PKCS#8) | ++-----------------------+ |oem_private_key| should be a RSA key in PKCS#8 PrivateKeyInfo format. |oem_public_cert| should be a DER-encoded PKCS#7 certificate chain. diff --git a/oemcrypto/test/oec_decrypt_fallback_chain.cpp b/oemcrypto/test/oec_decrypt_fallback_chain.cpp index 6cb7aac0..fe303fc6 100644 --- a/oemcrypto/test/oec_decrypt_fallback_chain.cpp +++ b/oemcrypto/test/oec_decrypt_fallback_chain.cpp @@ -17,7 +17,6 @@ void advance_dest_buffer(OEMCrypto_DestBufferDesc* dest_buffer, size_t bytes) { switch (dest_buffer->type) { case OEMCrypto_BufferType_Clear: dest_buffer->buffer.clear.clear_buffer += bytes; - dest_buffer->buffer.clear.clear_buffer_length -= bytes; break; case OEMCrypto_BufferType_Secure: @@ -99,6 +98,11 @@ OEMCryptoResult DecryptFallbackChain::DecryptSample( const size_t length = subsample.num_bytes_clear + subsample.num_bytes_encrypted; fake_sample.buffers.input_data_length = length; + if (fake_sample.buffers.output_descriptor.type == + OEMCrypto_BufferType_Clear) { + fake_sample.buffers.output_descriptor.buffer.clear.clear_buffer_length = + length; + } fake_sample.subsamples = &subsample; fake_sample.subsamples_length = 1; @@ -144,6 +148,11 @@ OEMCryptoResult DecryptFallbackChain::DecryptSubsample( if (subsample.num_bytes_clear > 0) { fake_sample.buffers.input_data_length = subsample.num_bytes_clear; + if (fake_sample.buffers.output_descriptor.type == + OEMCrypto_BufferType_Clear) { + fake_sample.buffers.output_descriptor.buffer.clear.clear_buffer_length = + subsample.num_bytes_clear; + } fake_subsample.num_bytes_clear = subsample.num_bytes_clear; fake_subsample.num_bytes_encrypted = 0; fake_subsample.block_offset = 0; @@ -167,6 +176,11 @@ OEMCryptoResult DecryptFallbackChain::DecryptSubsample( if (subsample.num_bytes_encrypted > 0) { fake_sample.buffers.input_data_length = subsample.num_bytes_encrypted; + if (fake_sample.buffers.output_descriptor.type == + OEMCrypto_BufferType_Clear) { + fake_sample.buffers.output_descriptor.buffer.clear.clear_buffer_length = + subsample.num_bytes_encrypted; + } fake_subsample.num_bytes_clear = 0; fake_subsample.num_bytes_encrypted = subsample.num_bytes_encrypted; fake_subsample.block_offset = subsample.block_offset; diff --git a/oemcrypto/test/oec_device_features.cpp b/oemcrypto/test/oec_device_features.cpp index b70b3c6d..276e52d0 100644 --- a/oemcrypto/test/oec_device_features.cpp +++ b/oemcrypto/test/oec_device_features.cpp @@ -10,7 +10,9 @@ #include +#include "log.h" #include "oec_test_data.h" +#include "string_conversions.h" #include "test_sleep.h" namespace wvoec { @@ -68,6 +70,12 @@ void DeviceFeatures::Initialize() { provisioning_method == OEMCrypto_BootCertificateChain || provisioning_method == OEMCrypto_DrmReprovisioning; printf("loads_certificate = %s.\n", loads_certificate ? "true" : "false"); + if (rsa_test_key().empty()) { + set_rsa_test_key( + std::vector(kTestRSAPKCS8PrivateKeyInfo2_2048, + kTestRSAPKCS8PrivateKeyInfo2_2048 + + sizeof(kTestRSAPKCS8PrivateKeyInfo2_2048))); + } generic_crypto = (OEMCrypto_ERROR_NOT_IMPLEMENTED != OEMCrypto_Generic_Encrypt(buffer, 0, buffer, 0, iv, @@ -129,6 +137,9 @@ void DeviceFeatures::Initialize() { case LOAD_TEST_RSA_KEY: printf("LOAD_TEST_RSA_KEY: Call LoadTestRSAKey before deriving keys.\n"); break; + case PRELOADED_RSA_KEY: + printf("PRELOADED_RSA_KEY: Device has test RSA key baked in.\n"); + break; case TEST_PROVISION_30: printf("TEST_PROVISION_30: Device provisioned with OEM Cert.\n"); break; @@ -153,9 +164,10 @@ void DeviceFeatures::PickDerivedKey() { return; case OEMCrypto_DrmCertificate: case OEMCrypto_DrmReprovisioning: - if (OEMCrypto_ERROR_NOT_IMPLEMENTED != OEMCrypto_LoadTestRSAKey()) { - derive_key_method = LOAD_TEST_RSA_KEY; - } + derive_key_method = + (OEMCrypto_ERROR_NOT_IMPLEMENTED == OEMCrypto_LoadTestRSAKey()) + ? PRELOADED_RSA_KEY + : LOAD_TEST_RSA_KEY; return; case OEMCrypto_Keybox: if (OEMCrypto_ERROR_NOT_IMPLEMENTED != diff --git a/oemcrypto/test/oec_device_features.h b/oemcrypto/test/oec_device_features.h index 78d1cf69..2eb1dd95 100644 --- a/oemcrypto/test/oec_device_features.h +++ b/oemcrypto/test/oec_device_features.h @@ -38,6 +38,7 @@ class DeviceFeatures { LOAD_TEST_RSA_KEY, // Call LoadTestRSAKey before deriving keys. TEST_PROVISION_30, // Device has OEM Certificate installed. TEST_PROVISION_40, // Device has Boot Certificate Chain installed. + PRELOADED_RSA_KEY, // Device has test RSA key baked in. }; enum DeriveMethod derive_key_method; @@ -65,6 +66,16 @@ class DeviceFeatures { // Get a list of output types that should be tested. const std::vector& GetOutputTypes(); + // If the device has a baked in cert, then this is the public key that should + // be used for testing. + const std::vector& rsa_test_key() const { return rsa_test_key_; }; + void set_rsa_test_key(const std::vector& rsa_test_key) { + rsa_test_key_ = rsa_test_key; + } + void set_rsa_test_key(std::vector&& rsa_test_key) { + rsa_test_key_ = std::move(rsa_test_key); + } + private: // Decide which method should be used to derive session keys, based on // supported featuers. @@ -77,6 +88,7 @@ class DeviceFeatures { // A list of possible output types. std::vector output_types_; bool initialized_ = false; + std::vector rsa_test_key_; }; // There is one global set of features for the version of OEMCrypto being diff --git a/oemcrypto/test/oec_session_util.cpp b/oemcrypto/test/oec_session_util.cpp index c23bd7dd..70297a81 100644 --- a/oemcrypto/test/oec_session_util.cpp +++ b/oemcrypto/test/oec_session_util.cpp @@ -582,7 +582,7 @@ void ProvisioningRoundTrip::VerifyLoadFailed() { } void Provisioning40RoundTrip::PrepareSession(bool is_oem_key) { - const size_t buffer_size = 5000; // Make sure it is large enough. + const size_t buffer_size = 10240; // Make sure it is large enough. std::vector public_key(buffer_size); size_t public_key_size = buffer_size; std::vector public_key_signature(buffer_size); @@ -644,7 +644,7 @@ OEMCryptoResult Provisioning40RoundTrip::LoadDRMCertResponse() { } void Provisioning40CastRoundTrip::PrepareSession() { - const size_t buffer_size = 5000; // Make sure it is large enough. + const size_t buffer_size = 10240; // Make sure it is large enough. std::vector public_key(buffer_size); size_t public_key_size = buffer_size; std::vector public_key_signature(buffer_size); @@ -2041,10 +2041,9 @@ void Session::LoadOEMCert(bool verify_cert) { void Session::SetTestRsaPublicKey() { public_ec_.reset(); - public_rsa_ = util::RsaPublicKey::LoadPrivateKeyInfo( - kTestRSAPKCS8PrivateKeyInfo2_2048, - sizeof(kTestRSAPKCS8PrivateKeyInfo2_2048)); - ASSERT_TRUE(public_rsa_) << "Could not parse test RSA public key #2"; + public_rsa_ = + util::RsaPublicKey::LoadPrivateKeyInfo(global_features.rsa_test_key()); + ASSERT_TRUE(public_rsa_) << "Could not parse test RSA public key"; } void Session::SetPublicKeyFromPrivateKeyInfo(OEMCrypto_PrivateKeyType key_type, diff --git a/oemcrypto/test/oemcrypto_basic_test.cpp b/oemcrypto/test/oemcrypto_basic_test.cpp index ef438846..36dccb95 100644 --- a/oemcrypto/test/oemcrypto_basic_test.cpp +++ b/oemcrypto/test/oemcrypto_basic_test.cpp @@ -2,17 +2,79 @@ // source code may only be used and distributed under the Widevine // License Agreement. // - #include "oemcrypto_basic_test.h" +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include "OEMCryptoCENC.h" #include "clock.h" -#include "jsmn.h" #include "log.h" #include "oemcrypto_corpus_generator_helper.h" #include "oemcrypto_resource_test.h" #include "test_sleep.h" +void PrintTo(const jsmntype_t& type, std::ostream* out) { + switch (type) { + case JSMN_UNDEFINED: + *out << "Undefined"; + return; + case JSMN_OBJECT: + *out << "Object"; + return; + case JSMN_ARRAY: + *out << "Array"; + return; + case JSMN_STRING: + *out << "String"; + return; + case JSMN_PRIMITIVE: + *out << "Primitive"; + return; + } + *out << "Unknown(" << static_cast(type) << ')'; +} + namespace wvoec { +namespace { +// Counts the number of ancestor tokens of the provided |root_index| token. +// The result does not count the root itself. +// +// JSMN tokens specify the count of immediate ancessor tokens, but +// not the total. +// - Primitives never have children +// - Strings have 0 if they are a value, and 1 if they are the +// name of an object member +// - Objects have the count of members (each key-value pair is 1, +// regardless of the value's children elements) +// - Arrays have the count of elements (regardless of the values members) +// +int32_t JsmnAncestorCount(const std::vector& tokens, + int32_t root_index) { + if (root_index >= static_cast(tokens.size())) return 0; + int32_t count = 0; + int32_t iter = root_index; + int32_t remainder = 1; + while (remainder > 0 && iter < static_cast(tokens.size())) { + const int32_t child_count = tokens[iter].size; + remainder += child_count; + count += child_count; + iter++; + remainder--; + } + return count; +} +} // namespace + void OEMCryptoClientTest::SetUp() { ::testing::Test::SetUp(); wvutil::TestSleep::SyncFakeClock(); @@ -180,16 +242,14 @@ TEST_F(OEMCryptoClientTest, FreeUnallocatedSecureBufferNoFailure) { */ TEST_F(OEMCryptoClientTest, VersionNumber) { const std::string log_message = - "OEMCrypto unit tests for API 19.2. Tests last updated 2024-06-24"; + "OEMCrypto unit tests for API 19.3. Tests last updated 2024-09-04"; cout << " " << log_message << "\n"; - cout << " " - << "These tests are part of Android U." - << "\n"; + cout << " " << "These tests are part of Android V." << "\n"; LOGI("%s", log_message.c_str()); // If any of the following fail, then it is time to update the log message // above. EXPECT_EQ(ODK_MAJOR_VERSION, 19); - EXPECT_EQ(ODK_MINOR_VERSION, 2); + EXPECT_EQ(ODK_MINOR_VERSION, 3); EXPECT_EQ(kCurrentAPI, static_cast(ODK_MAJOR_VERSION)); OEMCrypto_Security_Level level = OEMCrypto_SecurityLevel(); EXPECT_GT(level, OEMCrypto_Level_Unknown); @@ -316,29 +376,154 @@ TEST_F(OEMCryptoClientTest, CheckNullBuildInformationAPI17) { } } +// Verifies that OEMCrypto_BuildInformation() is behaving as expected +// by assigning appropriate values to the build info size. +TEST_F(OEMCryptoClientTest, CheckBuildInformation_OutputLengthAPI17) { + if (wvoec::global_features.api_version < 17) { + GTEST_SKIP() << "Test for versions 17 and up only."; + } + + constexpr size_t kZero = 0; + constexpr char kNullChar = '\0'; + + // Allocating single byte to avoid potential null dereference. + std::string build_info(1, kNullChar); + size_t build_info_length = 0; + + OEMCryptoResult result = + OEMCrypto_BuildInformation(&build_info[0], &build_info_length); + + ASSERT_EQ(result, OEMCrypto_ERROR_SHORT_BUFFER); + ASSERT_GT(build_info_length, kZero) + << "Signaling ERROR_SHORT_BUFFER should have assigned a length"; + + // Force a ERROR_SHORT_BUFFER using a non-zero value. + // Note: It is assumed that vendors will provide more than a single + // character of info. + const size_t second_attempt_length = + (build_info_length >= 2) ? build_info_length / 2 : 1; + build_info.assign(second_attempt_length, kNullChar); + build_info_length = build_info.size(); + + result = OEMCrypto_BuildInformation(&build_info[0], &build_info_length); + ASSERT_EQ(result, OEMCrypto_ERROR_SHORT_BUFFER) + << "second_attempt_length = " << second_attempt_length + << ", build_info_length" << build_info_length; + // OEM specified build info length should be larger than the + // original length if returning ERROR_SHORT_BUFFER. + ASSERT_GT(build_info_length, second_attempt_length); + + // Final attempt with a buffer large enough buffer, padding to + // ensure the caller truncates. + constexpr size_t kBufferPadSize = 42; + const size_t expected_length = build_info_length; + const size_t final_attempt_length = expected_length + kBufferPadSize; + build_info.assign(final_attempt_length, kNullChar); + build_info_length = build_info.size(); + + result = OEMCrypto_BuildInformation(&build_info[0], &build_info_length); + + ASSERT_EQ(result, OEMCrypto_SUCCESS) + << "final_attempt_length = " << final_attempt_length + << ", expected_length = " << expected_length + << ", build_info_length = " << build_info_length; + // Ensure not empty. + ASSERT_GT(build_info_length, kZero) << "Build info cannot be empty"; + // Ensure it was truncated down from the padded length. + ASSERT_LT(build_info_length, final_attempt_length) + << "Should have truncated from oversized buffer: expected_length = " + << expected_length; + // Ensure the real length is within the size originally specified. + // OK if final length is smaller than estimated length. + ASSERT_LE(build_info_length, expected_length); +} + +// Verifies that OEMCrypto_BuildInformation() is behaving as expected +// by checking the resulting contents. +// Does not validate whether output if valid JSON for v18. +TEST_F(OEMCryptoClientTest, CheckBuildInformation_OutputContentAPI17) { + if (wvoec::global_features.api_version < 17) { + GTEST_SKIP() << "Test for versions 17 and up only."; + } + + constexpr size_t kZero = 0; + constexpr char kNullChar = '\0'; + + // Allocating single byte to avoid potential null dereference. + std::string build_info(1, kNullChar); + size_t build_info_length = 0; + OEMCryptoResult result = + OEMCrypto_BuildInformation(&build_info[0], &build_info_length); + ASSERT_EQ(result, OEMCrypto_ERROR_SHORT_BUFFER); + ASSERT_GT(build_info_length, kZero) + << "Signaling ERROR_SHORT_BUFFER should have assigned a length"; + + // Expect successful acquisition of build information. + const size_t expected_length = build_info_length; + build_info.assign(expected_length, kNullChar); + result = OEMCrypto_BuildInformation(&build_info[0], &build_info_length); + ASSERT_EQ(result, OEMCrypto_SUCCESS) + << "expected_length = " << expected_length + << ", build_info_length = " << build_info_length; + // Ensure not empty. + ASSERT_GT(build_info_length, kZero) << "Build info cannot be empty"; + // Ensure the real length is within the size originally specified. + ASSERT_LE(build_info_length, expected_length) + << "Cannot specify success if buffer was too small"; + build_info.resize(build_info_length); + + // Ensure there isn't a trailing null byte. + ASSERT_NE(build_info.back(), kNullChar) + << "Build info must not contain trailing null byte"; + + // Ensure all build info characters are printable, or a limited + // set of white space characters (case of JSON build info). + const auto is_valid_build_info_white_space = [](const char& ch) -> bool { + constexpr char kSpace = ' '; + constexpr char kLineFeed = '\n'; + constexpr char kTab = '\t'; + return ch == kLineFeed || ch == kTab || ch == kSpace; + }; + const auto is_valid_build_info_char = [&](const char& ch) -> bool { + return ::isprint(ch) || is_valid_build_info_white_space(ch); + }; + ASSERT_TRUE(std::all_of(build_info.begin(), build_info.end(), + is_valid_build_info_char)) + << "Build info is not printable: " << wvutil::b2a_hex(build_info); + + // Ensure build info isn't just white space. + ASSERT_FALSE(std::all_of(build_info.begin(), build_info.end(), + is_valid_build_info_white_space)) + << "Build info is just white space: " << wvutil::b2a_hex(build_info); +} + TEST_F(OEMCryptoClientTest, CheckJsonBuildInformationAPI18) { if (wvoec::global_features.api_version < 18) { GTEST_SKIP() << "Test for versions 18 and up only."; } - std::string build_info; - OEMCryptoResult sts = OEMCrypto_BuildInformation(&build_info[0], nullptr); - ASSERT_EQ(OEMCrypto_ERROR_INVALID_CONTEXT, sts); - size_t buf_length = 0; + constexpr char kNullChar = '\0'; + constexpr size_t kZero = 0; + + // Step 1: Get Build Info + size_t buffer_length = 0; // OEMCrypto must allow |buffer| to be null so long as |buffer_length| // is provided and initially set to zero. - sts = OEMCrypto_BuildInformation(nullptr, &buf_length); - ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts); - build_info.resize(buf_length); - const size_t max_final_size = buf_length; - sts = OEMCrypto_BuildInformation(&build_info[0], &buf_length); - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - ASSERT_LE(buf_length, max_final_size); - build_info.resize(buf_length); + OEMCryptoResult result = OEMCrypto_BuildInformation(nullptr, &buffer_length); + ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, result); + ASSERT_GT(buffer_length, kZero); + std::string build_info(buffer_length, kNullChar); + const size_t max_final_size = buffer_length; + result = OEMCrypto_BuildInformation(&build_info[0], &buffer_length); + ASSERT_EQ(OEMCrypto_SUCCESS, result); + ASSERT_LE(buffer_length, max_final_size); + build_info.resize(buffer_length); + + // Step 2: Parse as JSON jsmn_parser p; jsmn_init(&p); std::vector tokens; - int32_t num_tokens = + const int32_t num_tokens = jsmn_parse(&p, build_info.c_str(), build_info.size(), nullptr, 0); EXPECT_GT(num_tokens, 0) << "Failed to parse BuildInformation as JSON, parse returned " @@ -346,45 +531,186 @@ TEST_F(OEMCryptoClientTest, CheckJsonBuildInformationAPI18) { tokens.resize(num_tokens); jsmn_init(&p); - int32_t jsmn_result = jsmn_parse(&p, build_info.c_str(), build_info.size(), - tokens.data(), num_tokens); + const int32_t jsmn_result = jsmn_parse( + &p, build_info.c_str(), build_info.size(), tokens.data(), num_tokens); EXPECT_GE(jsmn_result, 0) << "Failed to parse BuildInformation as JSON, parse returned " << jsmn_result << "for following build info: " << build_info; - std::map expected; - expected["soc_vendor"] = JSMN_STRING; - expected["soc_model"] = JSMN_STRING; - expected["ta_ver"] = JSMN_STRING; - expected["uses_opk"] = JSMN_PRIMITIVE; - expected["tee_os"] = JSMN_STRING; - expected["tee_os_ver"] = JSMN_STRING; + // Step 3a: Ensure info is a single JSON object. + const jsmntok_t& object_token = tokens[0]; + ASSERT_EQ(object_token.type, JSMN_OBJECT) + << "Build info is not a JSON object: " << build_info; - // for values in token - // build string from start,end - // check for existence in map - // check if value matches expectation - // remove from map - for (int32_t i = 0; i < jsmn_result; i++) { - jsmntok_t token = tokens[i]; - std::string key = build_info.substr(token.start, token.end - token.start); - if (expected.find(key) != expected.end()) { - EXPECT_EQ(expected.find(key)->second, tokens[i + 1].type) - << "Type is incorrect for key " << key; - expected.erase(key); + // Step 3b: Verify schema of defined fields. + + // Required fields must be present in the build information, + // and be of the correct type. + const std::map kRequiredFields = { + // SOC manufacturer name + {"soc_vendor", JSMN_STRING}, + // SOC model name + {"soc_model", JSMN_STRING}, + // TA version in string format eg "1.12.3+tag", "2.0" + {"ta_ver", JSMN_STRING}, + // [bool] Whether TA was built with Widevine's OPK + {"uses_opk", JSMN_PRIMITIVE}, + // Trusted OS intended to run the TA, eg "Trusty", "QSEE", "OP-TEE" + {"tee_os", JSMN_STRING}, + // Version of Trusted OS intended to run the TA + {"tee_os_ver", JSMN_STRING}, + // [bool] Whether this is a debug build of the TA + // Not forcing behavior until implementations fix + // them self + // {"is_debug", JSMN_PRIMITIVE}, + }; + + const std::string kSpecialCaseReeKey = "ree"; + + // Optional fields may be present in the build information; + // if they are, then the must be the correct type. + const std::map kOptionalFields = { + // Name of company or entity that provides OEMCrypto. + {"implementor", JSMN_STRING}, + // Git commit hash of the code repository. + {"git_commit", JSMN_STRING}, + // ISO 8601 formatted timestamp of the time the TA was compiled + {"build_timestamp", JSMN_STRING}, + // Whether this was built with FACTORY_MODE_ONLY defined + {"is_factory_mode", JSMN_PRIMITIVE}, + // ... provide information about liboemcrypto.so + // Special case, see kOptionalReeFields for details. + {kSpecialCaseReeKey, JSMN_OBJECT}, + // Technically required, but several implementations + // do not implement this fields. + {"is_debug", JSMN_PRIMITIVE}, + }; + + // A set of the required fields found when examining the + // build information, use to verify all fields are present. + std::set found_required_fields; + // Stores the tokens of the "ree" field, if set, used to + // validate its content. + std::vector ree_tokens; + bool has_ree_info = false; + + // Start: first object key token + // Condition: key-value pair (2 tokens) + // Iter: next key-value pair (2 tokens) + for (int32_t i = 1; (i + 1) < jsmn_result; i += 2) { + // JSMN objects consist of pairs of key-value pairs (keys are always + // JSMN_STRING). + const jsmntok_t& key_token = tokens[i]; + ASSERT_EQ(key_token.type, JSMN_STRING) + << "Bad object key: i = " << i << ", build_info = " << build_info; + const jsmntok_t& value_token = tokens[i + 1]; + + const std::string key = + build_info.substr(key_token.start, key_token.end - key_token.start); + if (kRequiredFields.find(key) != kRequiredFields.end()) { + ASSERT_EQ(value_token.type, kRequiredFields.at(key)) + << "Unexpected required field type: field = " << key + << ", build_info = " << build_info; + found_required_fields.insert(key); + } else if (kOptionalFields.find(key) != kOptionalFields.end()) { + ASSERT_EQ(value_token.type, kOptionalFields.at(key)) + << "Unexpected optional field type: field = " << key + << ", build_info = " << build_info; + } // Do not validate vendor fields. + + if (key == kSpecialCaseReeKey) { + // Store the tokens of the "ree" field for additional validation. + const int32_t first_ree_field_index = i + 2; + const int32_t ree_token_count = JsmnAncestorCount(tokens, i + 1); + const auto first_ree_field_iter = tokens.begin() + first_ree_field_index; + ree_tokens.assign(first_ree_field_iter, + first_ree_field_iter + ree_token_count); + has_ree_info = true; } + + // Skip potential nested tokens. + i += JsmnAncestorCount(tokens, i + 1); } - // if map is not empty, return false - if (expected.size() > 0) { - std::string missing; - for (const auto& e : expected) { - missing.append(e.first); - missing.append(" "); + // Step 3c: Ensure all required fields were found. + if (found_required_fields.size() != kRequiredFields.size()) { + // Generate a list of all the missing fields. + std::string missing_fields; + for (const auto& required_field : kRequiredFields) { + if (found_required_fields.find(required_field.first) != + found_required_fields.end()) + continue; + if (!missing_fields.empty()) { + missing_fields.append(", "); + } + missing_fields.push_back('"'); + missing_fields.append(required_field.first); + missing_fields.push_back('"'); } - FAIL() << "JSON does not contain all required keys. Missing keys: [" - << missing << "] in string " << build_info; + + FAIL() << "Build info JSON object does not contain all required keys; " + << "missing_fields = [" << missing_fields + << "], build_info = " << build_info; + return; } + + // If no "ree" field tokens, then end here. + if (!has_ree_info) return; + // Step 4a: Verify "ree" object scheme. + ASSERT_FALSE(ree_tokens.empty()) + << "REE field was specified, but contents were empty: build_info = " + << build_info; + + // The optional field "ree", if present, must follow the required + // format. + const std::map kReeRequiredFields = { + // liboemcrypto.so version in string format eg "2.15.0+tag" + {"liboemcrypto_ver", JSMN_STRING}, + // git hash of code that compiled liboemcrypto.so + {"git_commit", JSMN_STRING}, + // ISO 8601 timestamp for when liboemcrypto.so was built + {"build_timestamp", JSMN_STRING}}; + + found_required_fields.clear(); + for (int32_t i = 0; (i + 1) < static_cast(ree_tokens.size()); + i += 2) { + const jsmntok_t& key_token = ree_tokens[i]; + ASSERT_EQ(key_token.type, JSMN_STRING) + << "Bad REE object key: i = " << i << ", build_info = " << build_info; + const jsmntok_t& value_token = ree_tokens[i + 1]; + + const std::string key = + build_info.substr(key_token.start, key_token.end - key_token.start); + if (kReeRequiredFields.find(key) != kReeRequiredFields.end()) { + ASSERT_EQ(value_token.type, kReeRequiredFields.at(key)) + << "Unexpected optional REE field type: ree_field = " << key + << ", build_info = " << build_info; + found_required_fields.insert(key); + } // Do not validate vendor fields. + + // Skip potential nested tokens. + i += JsmnAncestorCount(ree_tokens, i + 1); + } + + // Step 4b: Ensure all required fields of the "ree" object were found. + if (found_required_fields.size() == kReeRequiredFields.size()) return; + // Generate a list of all the missing REE fields. + std::string missing_ree_fields; + for (const auto& required_field : kReeRequiredFields) { + if (found_required_fields.find(required_field.first) != + found_required_fields.end()) + continue; + if (!missing_ree_fields.empty()) { + missing_ree_fields.append(", "); + } + missing_ree_fields.push_back('"'); + missing_ree_fields.append(required_field.first); + missing_ree_fields.push_back('"'); + } + + FAIL() << "REE info JSON object does not contain all required keys; " + << "missing_ree_fields = [" << missing_ree_fields + << "], build_info = " << build_info; } TEST_F(OEMCryptoClientTest, CheckMaxNumberOfSessionsAPI10) { diff --git a/oemcrypto/test/oemcrypto_decrypt_test.cpp b/oemcrypto/test/oemcrypto_decrypt_test.cpp index b9868d02..6dcf901c 100644 --- a/oemcrypto/test/oemcrypto_decrypt_test.cpp +++ b/oemcrypto/test/oemcrypto_decrypt_test.cpp @@ -121,38 +121,6 @@ TEST_P(OEMCryptoLicenseTest, RejectCensAPI16) { EXPECT_EQ(OEMCrypto_ERROR_INVALID_CONTEXT, sts); } -// 'cbc1' mode is no longer supported in v16 -TEST_P(OEMCryptoLicenseTest, RejectCbc1API16) { - ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); - ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); - ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); - ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); - - vector key_handle; - OEMCryptoResult sts; - sts = GetKeyHandleIntoVector(session_.session_id(), - session_.license().keys[0].key_id, - session_.license().keys[0].key_id_length, - OEMCrypto_CipherMode_CBCS, key_handle); - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - - vector in_buffer(256); - vector out_buffer(in_buffer.size()); - OEMCrypto_SampleDescription sample_description; - OEMCrypto_SubSampleDescription subsample_description; - - GenerateSimpleSampleDescription(in_buffer, out_buffer, &sample_description, - &subsample_description); - - // Create a zero pattern to indicate this is 'cbc1' - OEMCrypto_CENCEncryptPatternDesc pattern = {0, 0}; - - // Try to decrypt the data - sts = OEMCrypto_DecryptCENC(key_handle.data(), key_handle.size(), - &sample_description, 1, &pattern); - EXPECT_EQ(OEMCrypto_ERROR_INVALID_CONTEXT, sts); -} - TEST_P(OEMCryptoLicenseTest, RejectCbcsWithBlockOffset) { ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); diff --git a/oemcrypto/test/oemcrypto_session_tests_helper.cpp b/oemcrypto/test/oemcrypto_session_tests_helper.cpp index 2a3d5250..175bb518 100644 --- a/oemcrypto/test/oemcrypto_session_tests_helper.cpp +++ b/oemcrypto/test/oemcrypto_session_tests_helper.cpp @@ -70,6 +70,9 @@ void SessionUtil::EnsureTestROT() { case DeviceFeatures::TEST_PROVISION_30: // Can use oem certificate to install test rsa key. break; + case DeviceFeatures::PRELOADED_RSA_KEY: + // There is already a key. + break; case wvoec::DeviceFeatures::TEST_PROVISION_40: // OEM certificate is retrieved from the server. break; diff --git a/oemcrypto/test/oemcrypto_usage_table_test.cpp b/oemcrypto/test/oemcrypto_usage_table_test.cpp index fc6bd176..d9ae34f5 100644 --- a/oemcrypto/test/oemcrypto_usage_table_test.cpp +++ b/oemcrypto/test/oemcrypto_usage_table_test.cpp @@ -96,6 +96,9 @@ TEST_F(OEMCryptoSessionTests, License_IncrementCounterAPI18) { if (wvoec::global_features.api_version < 18) { GTEST_SKIP() << "Test for versions 18 and up only."; } + if (OEMCrypto_SecurityLevel() == OEMCrypto_Level3) { + GTEST_SKIP() << "L3 does not support license counter."; + } Session s; s.open(); LicenseRoundTrip license_messages(&s); @@ -141,6 +144,9 @@ TEST_F(OEMCryptoSessionTests, MasterGeneration_IncrementCounterAPI18) { GTEST_SKIP() << "Usage table not supported, so master generation number " "does not need to be checked."; } + if (OEMCrypto_SecurityLevel() == OEMCrypto_Level3) { + GTEST_SKIP() << "L3 does not support license counter."; + } Session s1; s1.open(); LicenseRoundTrip license_messages(&s1); diff --git a/oemcrypto/util/include/bcc_validator.h b/oemcrypto/util/include/bcc_validator.h index 94f07862..77c753c2 100644 --- a/oemcrypto/util/include/bcc_validator.h +++ b/oemcrypto/util/include/bcc_validator.h @@ -7,12 +7,15 @@ #ifndef WVOEC_UTIL_BCC_VALIDATOR_H_ #define WVOEC_UTIL_BCC_VALIDATOR_H_ +#include + #include #include #include #include "cbor_validator.h" #include "cppbor.h" +#include "wv_class_utils.h" namespace wvoec { namespace util { @@ -45,10 +48,10 @@ struct BccPublicKeyInfo { // Google Dice Profile: go/dice-profile class BccValidator : public CborValidator { public: - explicit BccValidator() {} + BccValidator() = default; virtual ~BccValidator() override = default; - BccValidator(const BccValidator&) = delete; - BccValidator& operator=(const BccValidator&) = delete; + WVCDM_DISALLOW_COPY_AND_MOVE(BccValidator); + // Verifies the Cbor struct of a client generated root of trust. This message // is part of an attestation model conforming to the Google Open Dice Profile. // This message is received from a client device to attest it is a valid @@ -75,7 +78,7 @@ class BccValidator : public CborValidator { const std::vector& signature); // Used to generate formatted message. std::stringstream msg_ss_; -}; +}; // class BccValidator } // namespace util } // namespace wvoec #endif // WVOEC_UTIL_BCC_VALIDATOR_H_ diff --git a/oemcrypto/util/include/cbor_validator.h b/oemcrypto/util/include/cbor_validator.h index 7d4c621b..9c67bbf9 100644 --- a/oemcrypto/util/include/cbor_validator.h +++ b/oemcrypto/util/include/cbor_validator.h @@ -7,11 +7,15 @@ #ifndef WVOEC_UTIL_CBOR_VALIDATOR_H_ #define WVOEC_UTIL_CBOR_VALIDATOR_H_ +#include + #include +#include #include #include "cppbor.h" #include "cppbor_parse.h" +#include "wv_class_utils.h" namespace wvoec { namespace util { @@ -33,10 +37,9 @@ std::string CborMessageStatusToString(CborMessageStatus status); class CborValidator { public: - explicit CborValidator() {} + CborValidator() = default; + WVCDM_DISALLOW_COPY_AND_MOVE(CborValidator); virtual ~CborValidator() = default; - CborValidator(const CborValidator&) = delete; - CborValidator& operator=(const CborValidator&) = delete; // Decodes |cbor| and sets |message_status_|. virtual CborMessageStatus Parse(const std::vector& cbor); @@ -80,7 +83,7 @@ class CborValidator { // Internal status of parsing and validating. cppbor::ParseResult parse_result_ = {}; std::vector> validate_messages_; -}; +}; // class CborValidator } // namespace util } // namespace wvoec #endif // WVOEC_UTIL_CBOR_VALIDATOR_H_ diff --git a/oemcrypto/util/include/cmac.h b/oemcrypto/util/include/cmac.h index 85ef7c49..e92702d9 100644 --- a/oemcrypto/util/include/cmac.h +++ b/oemcrypto/util/include/cmac.h @@ -7,18 +7,22 @@ #ifndef WVOEC_UTIL_CMAC_H_ #define WVOEC_UTIL_CMAC_H_ +#include #include -#include #include #include #include +#include "wv_class_utils.h" + namespace wvoec { namespace util { class Cmac { public: + WVCDM_DISALLOW_COPY_AND_MOVE(Cmac); + // Creates an AES-128-CMAC or an AES-256-CMAC depending on |key_size|. // Returns an empty pointer if the key size is not valid. static std::unique_ptr Create(const uint8_t* key, size_t key_size); @@ -48,14 +52,14 @@ class Cmac { ~Cmac(); private: - Cmac() {} + Cmac() = default; // Assumes |key_size| is a valid AES-128 or AES-256 key. bool Init(const uint8_t* key, size_t key_size); CMAC_CTX* ctx_ = nullptr; bool ready_ = false; -}; +}; // class Cmac } // namespace util } // namespace wvoec #endif // WVOEC_UTIL_CMAC_H_ diff --git a/oemcrypto/util/include/device_info_validator.h b/oemcrypto/util/include/device_info_validator.h index d0d298de..bb3373a4 100644 --- a/oemcrypto/util/include/device_info_validator.h +++ b/oemcrypto/util/include/device_info_validator.h @@ -7,12 +7,15 @@ #ifndef WVOEC_UTIL_DEVICE_INFO_VALIDATOR_H_ #define WVOEC_UTIL_DEVICE_INFO_VALIDATOR_H_ +#include + #include #include #include #include "cbor_validator.h" #include "cppbor.h" +#include "wv_class_utils.h" namespace wvoec { namespace util { @@ -21,12 +24,13 @@ namespace util { // https://source.corp.google.com/h/googleplex-android/platform/superproject/main/+/main:hardware/interfaces/security/rkp/aidl/android/hardware/security/keymint/DeviceInfoV3.cddl class DeviceInfoValidator : public CborValidator { public: + DeviceInfoValidator() = delete; + WVCDM_DISALLOW_COPY_AND_MOVE(DeviceInfoValidator); + explicit DeviceInfoValidator(int version_number) : version_number_(version_number) {} - DeviceInfoValidator() = delete; + virtual ~DeviceInfoValidator() override = default; - DeviceInfoValidator(const DeviceInfoValidator&) = delete; - DeviceInfoValidator& operator=(const DeviceInfoValidator&) = delete; // Decodes |device_info| and sets |message_status_|. virtual CborMessageStatus Parse( @@ -48,7 +52,7 @@ class DeviceInfoValidator : public CborValidator { int version_number_; // Saved Cbor-encoded device info. std::vector device_info_bytes_; -}; +}; // class DeviceInfoValidator } // namespace util } // namespace wvoec #endif // WVOEC_UTIL_DEVICE_INFO_VALIDATOR_H_ diff --git a/oemcrypto/util/include/hmac.h b/oemcrypto/util/include/hmac.h index 279cbbf7..dd17623c 100644 --- a/oemcrypto/util/include/hmac.h +++ b/oemcrypto/util/include/hmac.h @@ -7,8 +7,8 @@ #ifndef WVOEC_UTIL_HMAC_H_ #define WVOEC_UTIL_HMAC_H_ +#include #include -#include #include #include diff --git a/oemcrypto/util/include/oemcrypto_drm_key.h b/oemcrypto/util/include/oemcrypto_drm_key.h index 257429da..a02b94ed 100644 --- a/oemcrypto/util/include/oemcrypto_drm_key.h +++ b/oemcrypto/util/include/oemcrypto_drm_key.h @@ -14,6 +14,7 @@ #include "OEMCryptoCENCCommon.h" #include "oemcrypto_ecc_key.h" #include "oemcrypto_rsa_key.h" +#include "wv_class_utils.h" namespace wvoec { namespace util { @@ -21,6 +22,9 @@ namespace util { // OEMCrypto session's RSA/ECC private key. class DrmPrivateKey { public: + WVCDM_DISALLOW_COPY_AND_MOVE(DrmPrivateKey); + ~DrmPrivateKey() = default; + // Create an RSA-based DRM key. static std::unique_ptr Create( std::shared_ptr&& rsa_key); @@ -71,8 +75,6 @@ class DrmPrivateKey { std::vector GenerateRsaSignature( const std::vector& message) const; - ~DrmPrivateKey() {} - private: DrmPrivateKey() {} diff --git a/oemcrypto/util/include/oemcrypto_ecc_key.h b/oemcrypto/util/include/oemcrypto_ecc_key.h index 3542eb2d..ac695051 100644 --- a/oemcrypto/util/include/oemcrypto_ecc_key.h +++ b/oemcrypto/util/include/oemcrypto_ecc_key.h @@ -7,8 +7,8 @@ #ifndef WVOEC_UTIL_ECC_KEY_H_ #define WVOEC_UTIL_ECC_KEY_H_ +#include #include -#include #include #include @@ -17,6 +17,7 @@ #include #include "OEMCryptoCENCCommon.h" +#include "wv_class_utils.h" namespace wvoec { namespace util { @@ -35,6 +36,9 @@ class EccPrivateKey; class EccPublicKey { public: + ~EccPublicKey(); + WVCDM_DISALLOW_COPY_AND_MOVE(EccPublicKey); + // Creates a new public key equivalent of the provided private key. static std::unique_ptr New(const EccPrivateKey& private_key); @@ -173,15 +177,8 @@ class EccPublicKey { const std::vector& message, const std::vector& signature) const; - ~EccPublicKey(); - - EccPublicKey(const EccPublicKey&) = delete; - EccPublicKey(EccPublicKey&&) = delete; - const EccPublicKey& operator=(const EccPublicKey&) = delete; - EccPublicKey& operator=(EccPublicKey&&) = delete; - private: - EccPublicKey() {} + EccPublicKey() = default; // Initializes the public key object using the provided |buffer|. // In case of any failure, false is return and the key should be @@ -207,6 +204,9 @@ class EccPublicKey { class EccPrivateKey { public: + ~EccPrivateKey(); + WVCDM_DISALLOW_COPY_AND_MOVE(EccPrivateKey); + // Creates a new, pseudorandom ECC private key belonging to the // curve specified. static std::unique_ptr New(EccCurve curve); @@ -318,7 +318,7 @@ class EccPrivateKey { size_t SignatureSize() const; // Special test method used to generate a raw ECDSA signature. - // A raw ECDSA signature is a concatination of a same-width-big-endian + // A raw ECDSA signature is a concatenation of a same-width-big-endian // encoding of the ECDSA signature point components r and s. std::vector GenerateRawSignature( const std::vector& message) const; @@ -339,15 +339,8 @@ class EccPrivateKey { // by DeriveSymmetricKey(). size_t SessionKeyLength() const; - ~EccPrivateKey(); - - EccPrivateKey(const EccPrivateKey&) = delete; - EccPrivateKey(EccPrivateKey&&) = delete; - const EccPrivateKey& operator=(const EccPrivateKey&) = delete; - EccPrivateKey& operator=(EccPrivateKey&&) = delete; - private: - EccPrivateKey() {} + EccPrivateKey() = default; // Initializes the public key object using the provided |buffer|. // In case of any failure, false is return and the key should be diff --git a/oemcrypto/util/include/oemcrypto_key_deriver.h b/oemcrypto/util/include/oemcrypto_key_deriver.h index fdcf3ecf..37a6fe53 100644 --- a/oemcrypto/util/include/oemcrypto_key_deriver.h +++ b/oemcrypto/util/include/oemcrypto_key_deriver.h @@ -7,18 +7,22 @@ #ifndef WVOEC_UTIL_KEY_DERIVER_H_ #define WVOEC_UTIL_KEY_DERIVER_H_ +#include #include -#include #include #include #include "cmac.h" +#include "wv_class_utils.h" namespace wvoec { namespace util { class KeyDeriver { public: + ~KeyDeriver() = default; + WVCDM_DISALLOW_COPY_AND_MOVE(KeyDeriver); + // Create a new key deriver using either the session key or the device // key. // Returns an empty pointer if the key size is not valid. @@ -52,15 +56,13 @@ class KeyDeriver { bool DeriveRenewedDeviceKey(const std::vector& context, std::vector* renewed_device_key); - ~KeyDeriver() {} - private: KeyDeriver() {} bool Init(const uint8_t* key, size_t key_size); std::unique_ptr cmac_; -}; +}; // class KeyDeriver } // namespace util } // namespace wvoec #endif // WVOEC_UTIL_KEY_DERIVER_H_ diff --git a/oemcrypto/util/include/oemcrypto_oem_cert.h b/oemcrypto/util/include/oemcrypto_oem_cert.h index f6332808..cd17791c 100644 --- a/oemcrypto/util/include/oemcrypto_oem_cert.h +++ b/oemcrypto/util/include/oemcrypto_oem_cert.h @@ -7,10 +7,13 @@ #ifndef WVOEC_UTIL_OEM_CERT_H_ #define WVOEC_UTIL_OEM_CERT_H_ +#include + #include #include #include "OEMCryptoCENCCommon.h" +#include "wv_class_utils.h" namespace wvoec { namespace util { @@ -39,6 +42,9 @@ class OemCertificate { kRsa = 1 }; + ~OemCertificate(); + WVCDM_DISALLOW_COPY_AND_MOVE(OemCertificate); + // Creates a new OEM Certificate and performs basic validation // to ensure that the private key and public cert are well-formed. // The |public_cert| provided is parsed as an X.509 Certificate @@ -84,13 +90,6 @@ class OemCertificate { // (ie, same modulos and public exponent). OEMCryptoResult IsCertificateValid() const; - ~OemCertificate(); - - OemCertificate(const OemCertificate&) = delete; - OemCertificate(OemCertificate&&) = delete; - const OemCertificate& operator=(const OemCertificate&) = delete; - OemCertificate& operator=(OemCertificate&&) = delete; - private: OemCertificate(); diff --git a/oemcrypto/util/include/oemcrypto_rsa_key.h b/oemcrypto/util/include/oemcrypto_rsa_key.h index 35b93ab4..0eb7e77d 100644 --- a/oemcrypto/util/include/oemcrypto_rsa_key.h +++ b/oemcrypto/util/include/oemcrypto_rsa_key.h @@ -7,8 +7,8 @@ #ifndef WVOEC_UTIL_RSA_KEY_H_ #define WVOEC_UTIL_RSA_KEY_H_ +#include #include -#include #include #include @@ -17,6 +17,7 @@ #include #include "OEMCryptoCENC.h" +#include "wv_class_utils.h" namespace wvoec { namespace util { @@ -61,6 +62,9 @@ class RsaPrivateKey; class RsaPublicKey { public: + ~RsaPublicKey(); + WVCDM_DISALLOW_COPY_AND_MOVE(RsaPublicKey); + // Creates a new public key equivalent of the provided private key. static std::unique_ptr New(const RsaPrivateKey& private_key); @@ -176,15 +180,8 @@ class RsaPublicKey { std::vector EncryptEncryptionKey( const std::string& encryption_key) const; - ~RsaPublicKey(); - - RsaPublicKey(const RsaPublicKey&) = delete; - RsaPublicKey(RsaPublicKey&&) = delete; - const RsaPublicKey& operator=(const RsaPublicKey&) = delete; - RsaPublicKey& operator=(RsaPublicKey&&) = delete; - private: - RsaPublicKey() {} + RsaPublicKey() = default; // Initializes the public key object using the provided |buffer|. // In case of any failure, false is return and the key should be @@ -222,6 +219,9 @@ class RsaPublicKey { class RsaPrivateKey { public: + ~RsaPrivateKey(); + WVCDM_DISALLOW_COPY_AND_MOVE(RsaPrivateKey); + // Creates a new, pseudorandom RSA private key. static std::unique_ptr New(RsaFieldSize field_size); @@ -342,15 +342,8 @@ class RsaPrivateKey { std::vector DecryptEncryptionKey( const std::string& enc_encryption_key) const; - ~RsaPrivateKey(); - - RsaPrivateKey(const RsaPrivateKey&) = delete; - RsaPrivateKey(RsaPrivateKey&&) = delete; - const RsaPrivateKey& operator=(const RsaPrivateKey&) = delete; - RsaPrivateKey& operator=(RsaPrivateKey&&) = delete; - private: - RsaPrivateKey() {} + RsaPrivateKey() = default; // Initializes the public key object using the provided |buffer|. // In case of any failure, false is return and the key should be diff --git a/oemcrypto/util/include/scoped_object.h b/oemcrypto/util/include/scoped_object.h index d9cdc6f2..84631242 100644 --- a/oemcrypto/util/include/scoped_object.h +++ b/oemcrypto/util/include/scoped_object.h @@ -7,6 +7,8 @@ #ifndef WVOEC_UTIL_SCOPED_OBJECT_H_ #define WVOEC_UTIL_SCOPED_OBJECT_H_ +#include "wv_class_utils.h" + namespace wvoec { namespace util { // A generic wrapper around pointer. This allows for automatic @@ -25,8 +27,7 @@ class ScopedObject { } // Copy construction and assignment are not allowed. - ScopedObject(const ScopedObject& other) = delete; - ScopedObject& operator=(const ScopedObject& other) = delete; + WVCDM_DISALLOW_COPY(ScopedObject); // Move construction and assignment are allowed. ScopedObject(ScopedObject&& other) : ptr_(other.ptr_) { @@ -65,7 +66,7 @@ class ScopedObject { private: Type* ptr_ = nullptr; -}; +}; // class ScopedObject } // namespace util } // namespace wvoec #endif // WVOEC_UTIL_SCOPED_OBJECT_H_ diff --git a/oemcrypto/util/include/signed_csr_payload_validator.h b/oemcrypto/util/include/signed_csr_payload_validator.h index a2d8127f..911943bc 100644 --- a/oemcrypto/util/include/signed_csr_payload_validator.h +++ b/oemcrypto/util/include/signed_csr_payload_validator.h @@ -12,6 +12,7 @@ #include "cbor_validator.h" #include "cppbor.h" +#include "wv_class_utils.h" namespace wvoec { namespace util { @@ -23,9 +24,7 @@ class SignedCsrPayloadValidator : public CborValidator { public: explicit SignedCsrPayloadValidator() {} virtual ~SignedCsrPayloadValidator() override = default; - SignedCsrPayloadValidator(const SignedCsrPayloadValidator&) = delete; - SignedCsrPayloadValidator& operator=(const SignedCsrPayloadValidator&) = - delete; + WVCDM_DISALLOW_COPY_AND_MOVE(SignedCsrPayloadValidator); // Verifies the Cbor struct of a client generated SignedData. virtual CborMessageStatus Validate() override; @@ -38,7 +37,7 @@ class SignedCsrPayloadValidator : public CborValidator { CborMessageStatus ValidateDataToBeSigned(const cppbor::Bstr* data); // Used to generate formatted message. std::stringstream msg_ss_; -}; +}; // class SignedCsrPayloadValidator } // namespace util } // namespace wvoec #endif // WVOEC_UTIL_SIGNED_CSR_PAYLOAD_VALIDATOR_H_ diff --git a/oemcrypto/util/include/wvcrc32.h b/oemcrypto/util/include/wvcrc32.h index 3d913ef3..67f5ed05 100644 --- a/oemcrypto/util/include/wvcrc32.h +++ b/oemcrypto/util/include/wvcrc32.h @@ -7,7 +7,7 @@ #ifndef WVOEC_UTIL_WVCRC32_H_ #define WVOEC_UTIL_WVCRC32_H_ -#include +#include namespace wvoec { namespace util { diff --git a/oemcrypto/util/src/bcc_validator.cpp b/oemcrypto/util/src/bcc_validator.cpp index e74354f8..a09f1a8c 100644 --- a/oemcrypto/util/src/bcc_validator.cpp +++ b/oemcrypto/util/src/bcc_validator.cpp @@ -8,7 +8,6 @@ #include -#include #include #include #include @@ -358,7 +357,7 @@ CborMessageStatus BccValidator::ProcessSubjectPublicKeyInfo( "MAP_KEY_DEVICE_KEY_TYPE: " + std::to_string(value)); } - fmt_msgs.push_back(kv); + fmt_msgs.push_back(std::move(kv)); } break; case MAP_KEY_DEVICE_KEY_ALGORITHM: { if (entry.second->type() != cppbor::NINT) { @@ -386,7 +385,7 @@ CborMessageStatus BccValidator::ProcessSubjectPublicKeyInfo( "MAP_KEY_DEVICE_KEY_ALGORITHM: " + std::to_string(value)); } - fmt_msgs.push_back(kv); + fmt_msgs.push_back(std::move(kv)); } break; case MAP_KEY_DEVICE_KEY_OPS: // The OPS is an array. Ignored for now. @@ -417,7 +416,7 @@ CborMessageStatus BccValidator::ProcessSubjectPublicKeyInfo( "MAP_KEY_DEVICE_KEY_CURVE: " + std::to_string(value)); } - fmt_msgs.push_back(kv); + fmt_msgs.push_back(std::move(kv)); } break; case MAP_KEY_DEVICE_KEY_BYTES_0: case MAP_KEY_DEVICE_KEY_BYTES_1: diff --git a/oemcrypto/util/src/cbor_validator.cpp b/oemcrypto/util/src/cbor_validator.cpp index dce72a82..608f2875 100644 --- a/oemcrypto/util/src/cbor_validator.cpp +++ b/oemcrypto/util/src/cbor_validator.cpp @@ -8,7 +8,6 @@ #include -#include #include #include #include diff --git a/oemcrypto/util/src/oemcrypto_ecc_key.cpp b/oemcrypto/util/src/oemcrypto_ecc_key.cpp index a311614b..7f986877 100644 --- a/oemcrypto/util/src/oemcrypto_ecc_key.cpp +++ b/oemcrypto/util/src/oemcrypto_ecc_key.cpp @@ -785,7 +785,7 @@ OEMCryptoResult EccPublicKey::VerifyRawSignature( if (!s || !r) { LOGE("Failed to parse R/S values: r = %s, s = %s, r_s_size = %d", BoolToStatus(r.ok()), BoolToStatus(s.ok()), r_s_size); - // Technically, any byte string should be convertable to an + // Technically, any byte string should be convertible to an // integer. This must be an OpenSSL/BoringSSL error. return OEMCrypto_ERROR_UNKNOWN_FAILURE; } diff --git a/oemcrypto/util/src/oemcrypto_oem_cert.cpp b/oemcrypto/util/src/oemcrypto_oem_cert.cpp index 619ad68c..e4ec3f95 100644 --- a/oemcrypto/util/src/oemcrypto_oem_cert.cpp +++ b/oemcrypto/util/src/oemcrypto_oem_cert.cpp @@ -15,6 +15,7 @@ #include "log.h" #include "oemcrypto_rsa_key.h" #include "scoped_object.h" +#include "wv_class_utils.h" namespace wvoec { namespace util { @@ -53,6 +54,9 @@ OEMCryptoResult VerifyRsaKey(const RSA* public_key, // Certificate. class OemPublicCertificate { public: + ~OemPublicCertificate() = default; + WVCDM_DISALLOW_COPY_AND_MOVE(OemPublicCertificate); + // Loads a PKCS #7 signedData message with certificate chain. // Minimum validation is performed. Only checks that the // device's public key is of a known type (RSA). @@ -81,15 +85,8 @@ class OemPublicCertificate { return EVP_PKEY_get0_RSA(device_public_key_.get()); } - ~OemPublicCertificate() = default; - - OemPublicCertificate(const OemPublicCertificate&) = delete; - OemPublicCertificate(OemPublicCertificate&&) = delete; - const OemPublicCertificate& operator=(const OemPublicCertificate&) = delete; - OemPublicCertificate& operator=(OemPublicCertificate&&) = delete; - private: - OemPublicCertificate() {} + OemPublicCertificate() = default; bool InitFromBuffer(const uint8_t* public_cert, size_t public_cert_size) { // Step 1: Parse the PKCS7 certificate chain as signedData. diff --git a/oemcrypto/util/test/oem_cert_test.h b/oemcrypto/util/test/oem_cert_test.h index a19b9520..35661b8d 100644 --- a/oemcrypto/util/test/oem_cert_test.h +++ b/oemcrypto/util/test/oem_cert_test.h @@ -6,8 +6,8 @@ #ifndef OEM_CERT_TEST_H_ #define OEM_CERT_TEST_H_ +#include #include -#include namespace wvoec { namespace util { diff --git a/oemcrypto/util/test/oemcrypto_ref_test_utils.h b/oemcrypto/util/test/oemcrypto_ref_test_utils.h index 3f625674..2edd6646 100644 --- a/oemcrypto/util/test/oemcrypto_ref_test_utils.h +++ b/oemcrypto/util/test/oemcrypto_ref_test_utils.h @@ -7,8 +7,8 @@ #ifndef WVOEC_UTIL_REF_TEST_UTILS_H_ #define WVOEC_UTIL_REF_TEST_UTILS_H_ +#include #include -#include #include diff --git a/platforms/example/no_oemcrypto.cpp b/platforms/example/no_oemcrypto.cpp index 7b013649..45d4c430 100644 --- a/platforms/example/no_oemcrypto.cpp +++ b/platforms/example/no_oemcrypto.cpp @@ -502,6 +502,12 @@ OEMCryptoResult OEMCrypto_GetOEMKeyToken(OEMCrypto_SESSION key_session UNUSED, return OEMCrypto_ERROR_NOT_IMPLEMENTED; } +OEMCryptoResult OEMCrypto_SetSessionUsage(OEMCrypto_SESSION session UNUSED, + uint32_t intent UNUSED, + uint32_t mode UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + OEMCryptoResult OEMCrypto_GetDeviceInformation( uint8_t* device_info UNUSED, size_t* device_info_length UNUSED) { return OEMCrypto_ERROR_NOT_IMPLEMENTED; @@ -600,7 +606,8 @@ OEMCryptoResult OEMCrypto_UseSecondaryKey(OEMCrypto_SESSION session_id UNUSED, } OEMCryptoResult OEMCrypto_WrapClearPrivateKey( - const uint8_t* clear_private_key_bytes UNUSED, size_t clear_private_key_length UNUSED, - uint8_t* wrapped_private_key UNUSED, size_t* wrapped_private_key_length UNUSED) { + const uint8_t* clear_private_key_bytes UNUSED, + size_t clear_private_key_length UNUSED, uint8_t* wrapped_private_key UNUSED, + size_t* wrapped_private_key_length UNUSED) { return OEMCrypto_ERROR_NOT_IMPLEMENTED; } diff --git a/util/include/string_conversions.h b/util/include/string_conversions.h index 461ef41e..7c480c7d 100644 --- a/util/include/string_conversions.h +++ b/util/include/string_conversions.h @@ -56,6 +56,18 @@ inline int64_t ntohll64(int64_t x) { return htonll64(x); } // Encode unsigned integer into a big endian formatted string. std::string EncodeUint32(uint32_t u); +// Converts a byte string representing an ID into a log-friendly form. +// +// Conversion rules: +// 1) empty - returns +// 2) printable ASCII only - original content, surrounded by double +// quotes; double quotes and backslashes +// are escaped, like C/C++ string literals. +// 3) otherwise - Unquoted, hexadecimal encoded string. +// +// Intended to be used on ID strings which are provided/generated +// from sources outside of the CDM. +std::string SafeByteIdToString(const std::string& id); +std::string SafeByteIdToString(const std::vector& id); } // namespace wvutil - #endif // WVCDM_UTIL_STRING_CONVERSIONS_H_ diff --git a/util/include/wv_class_utils.h b/util/include/wv_class_utils.h new file mode 100644 index 00000000..aae285fe --- /dev/null +++ b/util/include/wv_class_utils.h @@ -0,0 +1,112 @@ +// Copyright 2024 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +#ifndef WVCDM_UTIL_CLASS_UTILS_HPP_ +#define WVCDM_UTIL_CLASS_UTILS_HPP_ + +// ==== Disallow Operators ==== + +#define WVCDM_DISALLOW_COPY(ClassName) \ + ClassName(const ClassName&) = delete; \ + ClassName& operator=(const ClassName&) = delete + +#define WVCDM_DISALLOW_MOVE(ClassName) \ + ClassName(ClassName&&) = delete; \ + ClassName& operator=(ClassName&&) = delete + +#define WVCDM_DISALLOW_COPY_AND_MOVE(ClassName) \ + WVCDM_DISALLOW_COPY(ClassName); \ + WVCDM_DISALLOW_MOVE(ClassName) + +// ==== Default Operators ==== + +#define WVCDM_DEFAULT_COPY(ClassName) \ + ClassName(const ClassName&) = default; \ + ClassName& operator=(const ClassName&) = default + +#define WVCDM_DEFAULT_MOVE(ClassName) \ + ClassName(ClassName&&) = default; \ + ClassName& operator=(ClassName&&) = default + +#define WVCDM_DEFAULT_COPY_AND_MOVE(ClassName) \ + WVCDM_DEFAULT_COPY(ClassName); \ + WVCDM_DEFAULT_MOVE(ClassName) + +// ==== Constexpr Default Operators ==== + +#define WVCDM_CONSTEXPR_DEFAULT_COPY(ClassName) \ + constexpr ClassName(const ClassName&) = default; \ + constexpr ClassName& operator=(const ClassName&) = default + +#define WVCDM_CONSTEXPR_DEFAULT_MOVE(ClassName) \ + constexpr ClassName(ClassName&&) = default; \ + constexpr ClassName& operator=(ClassName&&) = default + +#define WVCDM_CONSTEXPR_DEFAULT_COPY_AND_MOVE(ClassName) \ + WVCDM_CONSTEXPR_DEFAULT_COPY(ClassName); \ + WVCDM_CONSTEXPR_DEFAULT_MOVE(ClassName) + +// ==== Comparison Operators ==== + +// If a class defines the comparison method "bool IsEqualTo() const", +// this macro will define the == and != operators for that class. +#define WVCDM_DEFINE_EQ_OPERATORS(ClassName) \ + bool operator==(const ClassName& other) const { return IsEqualTo(other); } \ + bool operator!=(const ClassName& other) const { return !IsEqualTo(other); } + +// If a class defines the comparison method "int CompareTo() const", +// this macro will define the < <= != == >= > operators for that class. +#define WVCDM_DEFINE_CMP_OPERATORS(ClassName) \ + bool operator<(const ClassName& other) const { \ + return CompareTo(other) < 0; \ + } \ + bool operator<=(const ClassName& other) const { \ + return CompareTo(other) <= 0; \ + } \ + bool operator>(const ClassName& other) const { \ + return CompareTo(other) > 0; \ + } \ + bool operator>=(const ClassName& other) const { \ + return CompareTo(other) >= 0; \ + } + +// If a class defines the comparison methods "bool IsEqualTo() const" +// and "int CompareTo() const", this macro will define the < <= != ==, +// >= > operators. +#define WVCDM_DEFINE_EQ_AND_CMP_OPERATORS(ClassName) \ + WVCDM_DEFINE_EQ_OPERATORS(ClassName) \ + WVCDM_DEFINE_CMP_OPERATORS(ClassName) + +// ==== Constexpr Comparison Operators ==== + +// Same as WVCDM_DEFINE_EQ_OPERATORS, but requires class to define +// "constexpr bool IsEqualTo() const" method. +#define WVCDM_DEFINE_CONSTEXPR_EQ_OPERATORS(ClassName) \ + constexpr bool operator==(const ClassName& other) const { \ + return IsEqualTo(other); \ + } \ + constexpr bool operator!=(const ClassName& other) const { \ + return !IsEqualTo(other); \ + } + +// Same as WVCDM_DEFINE_CMP_OPERATORS, but requires class to define +// "constexpr int CompareTo() const" method. +#define WVCDM_DEFINE_CONSTEXPR_CMP_OPERATORS(ClassName) \ + constexpr bool operator<(const ClassName& other) const { \ + return CompareTo(other) < 0; \ + } \ + constexpr bool operator<=(const ClassName& other) const { \ + return CompareTo(other) <= 0; \ + } \ + constexpr bool operator>(const ClassName& other) const { \ + return CompareTo(other) > 0; \ + } \ + constexpr bool operator>=(const ClassName& other) const { \ + return CompareTo(other) >= 0; \ + } + +#define WVCDM_DEFINE_CONSTEXPR_EQ_AND_CMP_OPERATORS(ClassName) \ + WVCDM_DEFINE_CONSTEXPR_EQ_OPERATORS(ClassName) \ + WVCDM_DEFINE_CONSTEXPR_CMP_OPERATORS(ClassName) + +#endif // WVCDM_UTIL_CLASS_UTILS_HPP_ diff --git a/util/src/string_conversions.cpp b/util/src/string_conversions.cpp index 0470d3a6..bb3427f3 100644 --- a/util/src/string_conversions.cpp +++ b/util/src/string_conversions.cpp @@ -5,10 +5,11 @@ #include "string_conversions.h" #include -#include +#include #include #include +#include #include #include "log.h" @@ -155,6 +156,61 @@ std::vector Base64DecodeInternal(const char* encoded, size_t length, result.resize(out_index); return result; } + +// To prevent ID character type conversion errors, these +// NormalizeIdChar() functions will safely return the +// a char type without changing the byte values. +constexpr char NormalizeIdChar(char ch) { return ch; } +constexpr char NormalizeIdChar(uint8_t ch) { return static_cast(ch); } + +// An escapable ID char is a character which is considered +// "human readable" and can be escaped by inserting a +// backslash character before it. +template +constexpr bool IsEscapableIdChar(CharType ch) { + const char nch = NormalizeIdChar(ch); + return nch == '"' || nch == '\\'; +} + +// A "human readable" ID character is any ASCII character +// which produces a glyph or a space character. +template +bool IsHumanReadableIdChar(CharType ch) { + const char nch = NormalizeIdChar(ch); + return isgraph(nch) || nch == ' '; +} + +template +std::string SafeByteIdToStringInternal(const Container& id) { + using CharType = typename Container::value_type; + if (id.empty()) { + return ""; + } + + // Check if already human readable. + const bool human_readable = + std::all_of(id.begin(), id.end(), IsHumanReadableIdChar); + if (!human_readable) { + // For non-human readable IDs, just return the hex encoded version. + return b2a_hex(id); + } + + // For human readable IDs, add quotes, and escape any double quotes + // and backslashes. + const size_t escape_count = + std::count_if(id.begin(), id.end(), IsEscapableIdChar); + std::string result; + result.reserve(id.size() + escape_count + 2); + result.push_back('"'); + for (const auto& ch : id) { + if (IsEscapableIdChar(ch)) { + result.push_back('\\'); + } + result.push_back(ch); + } + result.push_back('"'); + return result; +} } // namespace // converts an ascii hex string(2 bytes per digit) into a decimal byte string @@ -340,4 +396,11 @@ std::string EncodeUint32(uint32_t u) { return s; } +std::string SafeByteIdToString(const std::string& id) { + return SafeByteIdToStringInternal(id); +} + +std::string SafeByteIdToString(const std::vector& id) { + return SafeByteIdToStringInternal(id); +} } // namespace wvutil diff --git a/util/test/base64_test.cpp b/util/test/base64_test.cpp index d96b0765..be75196c 100644 --- a/util/test/base64_test.cpp +++ b/util/test/base64_test.cpp @@ -189,4 +189,115 @@ TEST_F(HtoNLL64Test, NegativeNumber) { int64_t host_byte_order = htonll64(*network_byte_order); EXPECT_EQ(-0x01FdFcFbFaF9F8F8, host_byte_order); } + +TEST(SafeByteIdToString, EmptyId) { + const std::string kEmptyString; + EXPECT_EQ(SafeByteIdToString(kEmptyString), ""); + + const std::vector kEmptyVector; + EXPECT_EQ(SafeByteIdToString(kEmptyVector), ""); +} + +TEST(SafeByteIdToString, AllAlphaNumeric_NoEscape) { + const std::string sid("Hello, World!"); + const std::vector vid(sid.begin(), sid.end()); + EXPECT_EQ(SafeByteIdToString(sid), "\"Hello, World!\""); + EXPECT_EQ(SafeByteIdToString(vid), "\"Hello, World!\""); +} + +TEST(SafeByteIdToString, AllAlphaNumeric_NoEscape_Exhaustive) { + for (char ch = 'a'; ch <= 'z'; ch++) { + const std::string sid(1, ch); + const std::vector vid(1, ch); + std::string expected(1, '"'); + expected.push_back(ch); + expected.push_back('"'); + EXPECT_EQ(SafeByteIdToString(sid), expected); + EXPECT_EQ(SafeByteIdToString(vid), expected); + } + + for (char ch = 'A'; ch <= 'Z'; ch++) { + const std::string sid(1, ch); + const std::vector vid(1, ch); + std::string expected(1, '"'); + expected.push_back(ch); + expected.push_back('"'); + EXPECT_EQ(SafeByteIdToString(sid), expected); + EXPECT_EQ(SafeByteIdToString(vid), expected); + } + + for (char ch = '0'; ch <= '9'; ch++) { + const std::string sid(1, ch); + const std::vector vid(1, ch); + std::string expected(1, '"'); + expected.push_back(ch); + expected.push_back('"'); + EXPECT_EQ(SafeByteIdToString(sid), expected); + EXPECT_EQ(SafeByteIdToString(vid), expected); + } +} + +TEST(SafeByteIdToString, AllSymbols_NoEscape) { + for (char ch = ' '; ch <= '/'; ch++) { + if (ch == '"') continue; + const std::string sid(1, ch); + const std::vector vid(1, ch); + std::string expected(1, '"'); + expected.push_back(ch); + expected.push_back('"'); + EXPECT_EQ(SafeByteIdToString(sid), expected); + EXPECT_EQ(SafeByteIdToString(vid), expected); + } + + for (char ch = ':'; ch <= '@'; ch++) { + const std::string sid(1, ch); + const std::vector vid(1, ch); + std::string expected(1, '"'); + expected.push_back(ch); + expected.push_back('"'); + EXPECT_EQ(SafeByteIdToString(sid), expected); + EXPECT_EQ(SafeByteIdToString(vid), expected); + } + + for (char ch = '['; ch <= '`'; ch++) { + if (ch == '\\') continue; + const std::string sid(1, ch); + const std::vector vid(1, ch); + std::string expected(1, '"'); + expected.push_back(ch); + expected.push_back('"'); + EXPECT_EQ(SafeByteIdToString(sid), expected); + EXPECT_EQ(SafeByteIdToString(vid), expected); + } + + for (char ch = '{'; ch <= '~'; ch++) { + const std::string sid(1, ch); + const std::vector vid(1, ch); + std::string expected(1, '"'); + expected.push_back(ch); + expected.push_back('"'); + EXPECT_EQ(SafeByteIdToString(sid), expected); + EXPECT_EQ(SafeByteIdToString(vid), expected); + } +} + +TEST(SafeByteIdToString, Escapable_NoEscape) { + const std::string quote_sid(1, '"'); + const std::vector quote_vid(1, '"'); + EXPECT_EQ(SafeByteIdToString(quote_sid), "\"\\\"\""); + EXPECT_EQ(SafeByteIdToString(quote_vid), "\"\\\"\""); + + const std::string backslash_sid(1, '\\'); + const std::vector backslash_vid(1, '\\'); + EXPECT_EQ(SafeByteIdToString(backslash_sid), "\"\\\\\""); + EXPECT_EQ(SafeByteIdToString(backslash_vid), "\"\\\\\""); +} + +TEST(SafeByteIdToString, AllNonPrintable) { + const std::vector vid = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + const std::string sid(vid.begin(), vid.end()); + + EXPECT_EQ(SafeByteIdToString(vid), "00010203040506070809"); + EXPECT_EQ(SafeByteIdToString(sid), "00010203040506070809"); +} } // namespace wvutil